Entity Framework: проблема со свойствами навигации
Автор вопроса: aschermanОтветов: 4
Я работаю с кодом Entity Framework в первую очередь, и у меня есть класс Course, который имеет свойство навигации Students:
public virtual Collection<Student> Students { get; set;}
Это работает нормально, но когда я обращаюсь к этому свойству навигации, все данные извлекаются из базы данных:
var allStudents = course.Students; // Here it retrieves the data
var activeStudents = allStudents.Where(n => n.Active); // Here it filter the data on memory
var listOfActiveStudents = activeStudents.ToList(); // It already has the data on memory.
Как вы понимаете, мне нужно, чтобы запрос выполнялся, когда я делаю .ToList(), потому что я не хочу выводить все Students из базы данных, а только активные.
Ленивая загрузка загружает весь набор в память. Если вы этого не хотите, отключите ленивую загрузку, удалив ключевое слово virtual, и используйте объект Query для DbEntry:
public GetCourseWithActiveStudentsLoaded(int courseid)
{
var course= context.Courses.Find(courseid);
context.Entry(course)
.Collection(c => c.Students)
.Query()
.Where(s => s.Active)
.Load();
return user
}
Является ли флаг "Активно" индикатором того, что вы пытаетесь реализовать обратимое удаление? Если это так, здесь есть решение: Soft Delete в Entity Framework
Вы можете проголосовать за отфильтрованные включения здесь: Разрешить фильтрацию для метода расширения Include
Другой способ сделать это - наследование. У вас может быть свойство ActiveStudent, наследуемое от Student, и свойство навигации ActiveStudents, а также свойство навигации AllStudents в классе Course.
public virtual Collection<Student> AllStudents { get; set;}
public virtual Collection<ActiveStudent> ActiveStudents { get; set;}
Если бы вы использовали правильную типизацию переменных, вы бы увидели, что происходит. Весь набор лениво загружается в память с помощью свойства навигации.
//user is an instance of the class User referenced by DbSet<User>
//when you lazy load a navigation property in that set, it loads the data
ICollection<Student> allStudents = user.Students;
//At this point, all of the data was lazy loaded
//But the Where creates an IEnumerable of the in memory set
IEnumerable<Student> activeStudents = allStudents.Where(n => n.Active);
//At this point, the IEnumerable is iterated, and a List is returned
List<Student> listOfActiveStudents = activeStudents.ToList();
Ответ 3 · рейтинг 3 · Moho
используйте dbContext.Entry( user ).Collection( u => u.Students ).Query(), чтобы получить IQueryable<Student> для свойства навигации коллекции студентов, после чего вы можете добавить свой фильтр и перечислить, когда будете готовы к данным
Ответ 4 · рейтинг 1 · Dave Cousineau
Один из способов обхода этого - перевернуть ваш запрос, хотя это означает отказ от использования свойств навигации в целом. (Фактическая реализация зависит от вашей модели.)
var allStudents =
context
.Students
.Where(s => s.CourseID == course.ID) // depends on your model
.Where(s => s.Active)
.ToList();
Я предпочитаю это использованию метода Entry, так как я использую общий интерфейс со своими моделями и не хочу раскрывать типы EF6.
Другой способ избежать раскрытия типов EF6 - написать такой метод:
public IQueryable<TChild> Nav<TParent, TChild>(
TParent pParent,
Expression<Func<TParent, ICollection<TChild>>> pNavigationExpression
) where TParent : class
where TChild : class =>
Entry(pParent)
.Collection(pNavigationExpression)
.Query();
Использовал что-то вроде этого:
var allStudents =
context
.Nav(course, c => c.Students)
.Where(s => s.Active)
.ToList()