Ядро EF выдает ошибку после добавления функции внутри «Где» лямбда-выражения [ЯДРО EF 3.1]

#c# #entity-framework #entity-framework-core #ef-core-3.1

Вопрос:

Я добавил функцию UserHasFilter function, чтобы я мог фильтровать и видеть, есть ли у пользователя фильтр, следующий логике, как вы можете видеть, но когда я запускаю его, он выдает следующую ошибку:

введите описание изображения здесь

Я не знаю, правильно ли я использую метод фильтрации или есть лучший способ ? Также я не знаю, как происходит ошибка.

Вот мой код:

  public async Task<IEnumerable<ConditionDataModel>> GetUserFilters(string pageName)
        {
            var user = await _configurationService.GetCurrentUser();
            if (user == null)
            {
                return null;
            }
            var conditions = _context.FilterUserGroups
                .Include(f => f.CompanyDataRight).ThenInclude(d => d.Page)
                .Include(f => f.FilterUsers).ThenInclude(d => d.User)
                .Include(f => f.FilterGroups).ThenInclude(d => d.Group).ThenInclude(g => g.UserGroups).ThenInclude(ug => ug.User)
                .Where(f => f.CompanyDataRight.Page.ClassName == pageName amp;amp; UserHasFilter(user.Id, f))
                .Include(f => f.Conditions)
                .SelectMany(f => f.Conditions)
                .Distinct()
                .AsEnumerable();
            return conditions;
        }

        public virtual bool UserHasFilter(Guid userId, FilterUserGroupDataModel filterUserGroup)
        {
            if(filterUserGroup == null)
            {
                return false;
            }
            if (filterUserGroup.FilterUsers?.Any(u => u.User.Id == userId) == true)
            {
                return true;
            }

            return false;
        }
 

Редактировать:

Благодаря @MindSwipe я внес изменения в запрос:

 var conditions = _context.FilterUserGroups
                .Include(f => f.CompanyDataRight).ThenInclude(d => d.Page)
                .Include(f => f.FilterUsers).ThenInclude(d => d.User)
                .Include(f => f.FilterGroups).ThenInclude(d => d.Group).ThenInclude(g => g.UserGroups).ThenInclude(ug => ug.User)
                .Where(f => f.CompanyDataRight.Page.ClassName == pageName
                    amp;amp; (f.FilterUsers != null amp;amp; f.FilterUsers.Any(u => u.User.Id == user.Id) // checks if a filter user contains the current user
                            || (f.FilterGroups != null amp;amp; f.FilterGroups.Any(g => g.Group != null amp;amp; g.Group.UserGroups.Any(ug => ug.UserId == user.Id))))) // checks if user group has the current user
                .Include(f => f.Conditions)
                .SelectMany(f => f.Conditions)
                .Distinct()
                .AsEnumerable();
 

Поскольку мне нужно, чтобы запрос выполнялся в базе данных, этот запрос не должен занимать много времени (в памяти).

Обновления:

Это работает для EF 5, они добавили этот конкретный запрос.

Комментарии:

1. Не могли бы вы добавить сведения об ошибке в виде текста к вопросу, пожалуйста, вместо скриншота?

2. потому UserHasFilter что это функция C#, которая не может быть переведена на SQL, вот почему вы получаете эту ошибку.

3. Я не смог добавить текстовую ошибку, она выдает ошибку в stackoverflow, так как я помещаю не форматированный код

4. Как указано в ошибке, UserHasFilter не может быть преобразовано в SQL. Попробуйте переместить Where в после .Include(f => f.Conditions) того, как это не удастся, вам придется переписать логику вашего запроса.

5. @Chetan как я могу добавить функцию, которая делает то же самое ?

Ответ №1:

Не все функции C#, и особенно «пользовательские» функции C#, не могут быть переведены на SQL поставщиком Entity Framework, и, начиная с EF Core 3.x Entity Framework создаст исключение, когда попытается автоматически переключиться с оценки на стороне сервера на оценку на стороне клиента. Для решения вашей проблемы есть 2 решения.

  1. Переключитесь на оценку на стороне клиента вручную, позвонив AsEnumerable() ранее.
  2. Перепишите свой запрос LINQ, чтобы EF Core мог перевести его на SQL.

Вот как это сделать #2:

 var conditions = _context.FilterUserGroups
    .Include(f => f.CompanyDataRight).ThenInclude(d => d.Page)
    .Include(f => f.FilterUsers).ThenInclude(d => d.User)
    .Include(f => f.FilterGroups).ThenInclude(d => d.Group).ThenInclude(g => g.UserGroups).ThenInclude(ug => ug.User)
    .Where(f => f.CompanyDataRight.Page.ClassName == pageName amp;amp; (f.FilterUsers != null amp;amp; f.FilterUsers.Any(u => u.User.Id == user.Id)))
    .Include(f => f.Conditions)
    .SelectMany(f => f.Conditions)
    .Distinct()
    .AsEnumerable();
 

Это должно сработать (в настоящее время у меня нет возможности это проверить). Что я сделал, так это переписал ваш вызов метода в виде инструкции, EF Core должен быть в состоянии перевести это в SQL. Если нет (и вы не можете исправить это самостоятельно), всегда есть вариант № 1: Переход на оценку на стороне клиента, вот как вы «оптимально» это сделаете:

 var conditions = _context.FilterUserGroups
    .Include(f => f.CompanyDataRight).ThenInclude(d => d.Page)
    .Include(f => f.FilterUsers).ThenInclude(d => d.User)
    .Include(f => f.FilterGroups).ThenInclude(d => d.Group).ThenInclude(g => g.UserGroups).ThenInclude(ug => ug.User)
    .Include(f => f.Conditions)
    .SelectMany(f => f.Conditions)
    .Distinct()
    .AsEnumerable()
    .Where(f => f.CompanyDataRight.Page.ClassName == pageName amp;amp; UserHasFilter(user.Id, f));
 

Посмотрите , как я переместил Where после AsEnumerable , EF загружает объект в память при вызове AsEnumerable , что означает, что вы можете делать с ними все, что хотите. Это в лучшем случае неоптимально, потому что прямо сейчас он загружает в память больше объектов, чем следовало бы, но иногда единственный способ выполнить более сложные запросы-выполнить их в памяти*. Однако у этого решения есть одно преимущество: класс, производный от этого класса, может переопределить UserHasFilter метод, изменяя логику запроса без необходимости повторного создания указанного запроса.


* Не то чтобы вы не могли достичь этого только с помощью SQL, просто EF не может перевести каждый отдельный запрос LINQ в SQL

Комментарии:

1. Вы действительно можете это сделать, но вы должны определить вызов метода как выражение, а не функцию.

2. Ха, это на самом деле довольно круто. Позвольте мне попробовать определить выражение

3. Спасибо, я отредактирую и объясню, как это работало