#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 решения.
- Переключитесь на оценку на стороне клиента вручную, позвонив
AsEnumerable()
ранее. - Перепишите свой запрос 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. Спасибо, я отредактирую и объясню, как это работало