#c# #sql-server #entity-framework
#c# #sql-сервер #entity-framework
Вопрос:
Работа с тремя таблицами — Company, Employee и User.
В компании 0 или много сотрудников. У сотрудника есть обнуляемый int FK для Company. На практике это всегда имеет значение. У сотрудника есть ненулевой int FK для пользователя. У пользователя отключено битовое поле AccountIsDisabled.
В моей модели данных у меня есть частичный класс, расширяющий класс модели EF для Company. При этом вызов ActiveEmployees возвращает всех сотрудников, которые активны для компании.
Моя проблема в том, что этот код генерирует взрыв запроса. Для компании из 1 тыс. сотрудников я получаю 1 тыс. вызовов в БД. Кажется, EF создает вызов для каждого сотрудника при переходе к таблице пользователей.
Я перепробовал много методов для принудительной загрузки, но безрезультатно.
Кто-нибудь там видит причину, по которой я получаю этот взрыв запроса?
namespace JCS.Data
{
public partial class Company : IIdentifiable
{
public IEnumerable<Employee> ActiveEmployees
{
get
{
return Employees.Where(e => !e.User.AccountIsDisabled);
}
}
}
}
Извините за недостающую информацию.
Взрыв запросов происходит при обращении к свойству bool в соответствующем классе Employee. Вот так
namespace JCS.Data
{
public partial class Employee : IIdentifiable
{
public bool ApprovesTimesheets
{
get
{
return Company.ActiveEmployees.Any(
employee => employee.TimesheetApproverEmployeeID == ID
amp;amp; employee.TimesheetsEnabled);
}
}
}
}
Так что в любом месте кода я иду
bool approvesTimesheets = employee.ApprovesTimesheets;
Я получаю 1 тыс. запросов.
Я попытался добавить ToLis() в компанию.Активные сотрудники. Никакой радости.
например, в классе Employee
var activeEmployees = Company.ActiveEmployees.ToList();
var approvesTimesheets = activeEmployees .Any(
employee => employee.TimesheetApproverEmployeeID == ID
amp;amp; employee.TimesheetsEnabled);
последняя в длинной череде неудачных попыток:
public List<Employee> ActiveEmployees
{
get
{
var employees = Employees.AsQueryable().Include(x => x.User).ToList();
return employees.Where(e => !e.User.AccountIsDisabled).ToList();
//return Employees.Where(e => !e.User.AccountIsDisabled);
}
}
Комментарии:
1. Выполняется ли этот запрос linq медленно?
2. Какую быструю загрузку вы пробовали?
3. Можете ли вы показать нам, как вы используете
ActiveEmployees
property?4. перечислите значения там с помощью . ToList(), в противном случае объект запроса будет возвращен (не выполнен), а затем при перечислении он будет выполняться для каждого перечисляемого объекта
5. Вопрос в том, как вы извлекаете этот
Company
класс из базы данных, потому что это единственное место, где вы можете применить быструю загрузку.
Ответ №1:
Вам нужно вызвать .ToList()
or ToListAsync()
, чтобы получить все данные сразу, иначе он будет получать данные «на лету» для каждой записи.
Это проблема с отложенным выполнением по сравнению с немедленным выполнением. Когда вы не материализуете список, .Where(foo).ToList()
он загружает каждую запись всякий раз, когда вы пытаетесь получить к ней доступ, поэтому вызывает 1000 DB.
редактировать: Обратите внимание, что вы также используете навигационное свойство, которое указывает на другой объект (я предполагаю, что это объект, сопоставленный непосредственно с таблицей), поэтому при попытке получить этот объект вы также выполняете дополнительные вызовы DB. чтобы избежать этого, сделайте что-то вроде этого :
public partial class Company : IIdentifiable
{
public IEnumerable<Employee> ActiveEmployees
{
get
{
return Employees.Where(e => !e.User.AccountIsDisabled).Include(x=>x.User).ToList();
}
}
}
Комментарии:
1. это не отложенная загрузка (то есть вы не исправляете ее этим), но это «отложенное выполнение» LINQ. конечно, отложенная загрузка несет те же риски, но вы бы исправили это с помощью быстрой загрузки (.Include()), а не . ToList().
2. @MisterPositive Ответ? Любой, кто работает с EF, знал бы, что
Employees
это свойство навигации (что-то вродеICollection<Employee>
), которое представляет собой лениво загруженную коллекцию памяти, поэтомуToList
или нетToList
, на самом деле не имеет значения. По-видимому, проблема с операционной системой связана с отложенной загрузкойEmployee.User
свойства. И это не может быть исправлено в этом месте кода, что бы вы ни делали. Смотрите мой комментарий к сообщению.3. Иван, ты прав, я не заметил свойство навигации, которое должно быть с.
.Include()
4. ‘EntityCollection<Сотрудник>. Include(bool, bool)’ недоступен из-за его уровня защиты
5. @Aviatrix эта ошибка была вызвана попыткой вашего решения. спасибо …. пытаюсь разобраться. Таким образом, Compay имеет связанную EntityCollection сотрудников.
Ответ №2:
ХОРОШО, проблема возникла из-за использования свойств навигации в POCOs.
Как указал @IvanStoev, я мог только принудительно выполнять быструю загрузку при первоначальном вызове БД.
Поэтому, когда я загружаю начальный объект Employee, мне нужно загрузить все связанные объекты. Итак..
_currentUser = Repository.Context.Employees.Include("User").Include("Company.Employees.User").FirstOrDefault(e => e.User.Person.Email == HttpContext.User.Identity.Name);
Решает проблему. Теперь я беспокоюсь, что у меня загружено много данных. Company.Employee — это объект 1K для большой компании.
Требуется еще некоторое тестирование, но исследование значительно улучшило мое понимание EF. Спасибо за помощь.