Запись условий WHERE со значениями Enum с использованием AsQueryable в Entity Framework

#.net #entity-framework #linq #enums

#.net #entity-framework #linq #перечисления

Вопрос:

Ошибка, сгенерированная для предупреждения

‘Microsoft.EntityFrameworkCore.Query.Предупреждение о проверке запроса: выражение LINQ ‘where Convert([p].Status, Enum).getDisplayName().Содержит(__searchBy_8)’не удалось перевести и будет оцениваться локально.’. Это исключение можно подавить или зарегистрировать, передав идентификатор события ‘RelationalEventId.QueryClientEvaluationWarning’ в метод ‘ConfigureWarnings’ в ‘DbContext.OnConfiguring’ или ‘AddDbContext’.

Я сохраняю значение status enum в таблице базы данных Complaints

 public enum Status
{
    Pending = 0,        
    Error = 1,
    Cancelled = 2,
    Delayed = 3,        
    Resolved = 4
}
 

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

 // Apply Relevant SearchModel filters first
var query = context.Complaints
                   .Include(s => s.Messages)
                   .ThenInclude(p => p.User).AsQueryable();
        
if (dtParams.StartDate != null amp;amp; dtParams.EndDate != null)
{
    query = query.Where(s => s.CreatedAt >= dtParams.StartDate.Value.Date amp;amp; 
                             s.CreatedAt <= dtParams.EndDate.Value.Date);
}

string searchBy = dtParams.Search?.Value;

if (!string.IsNullOrEmpty(searchBy))
{
    query = query.Where(r => r.ComplaintNo.Contains(searchBy) ||                                                                                      
                             r.CreatorUsername.Contains(searchBy) ||                                                                                      
                             (r.CreatedAt != null amp;amp;  r.CreatedAt.ToString().Contains(searchBy)) ||                                                                                                                                    
                             (r.Status.GetDisplayName().Contains(searchBy)) ||
                             r.Messages.Any(p => p.StatusDescription.Contains(searchBy))
                        );
}

// Convert the Db context to Custom ViewModel which will then be rendered on to a DataTable
var dtQuery = query.SelectMany(x => x.Messages, (complaint, message) => new { complaint, message })
                       .Select(p => new ListTableViewModel
                       {
                           ComplaintNo = $"<a href="{Url.Action("GetLabels", new { orderNo = p.complaint.ComplaintNo })}" target="_blank"> {p.complaint.ComplaintNo}</a>",                               
                            
                           Tracking = GenerateTrackingUrl(p.complaint),                               
                           Creator = p.complaint.CreatorUsername,
                           CreatedAt = p.complaint.CreatedAt.ToString("dd/MM/yy"),                               
                           Status = $"<span class="badge label-{p.complaint.Status}">{p.complaint.Status.GetDisplayName()}</span>",
                           Info = p.message.StatusDescription
                       }).ToList(); 
                       
 

Я получал исключение, подобное приведенному ниже

Ошибка, сгенерированная для предупреждения «Microsoft.EntityFrameworkCore.Query.Предупреждение о проверке запроса:
Выражение LINQ ‘where Преобразовать([p].Статус, перечисление).getDisplayName().Содержит(__searchBy_8)’не удалось перевести и будет оцениваться локально.’. Это исключение можно подавить или зарегистрировать, передав идентификатор события ‘RelationalEventId.QueryClientEvaluationWarning’ в метод ‘ConfigureWarnings’ в ‘DbContext.OnConfiguring’ или ‘AddDbContext’.

Строка, вызывающая это исключение, — это то, где я сравниваю текст поиска со значением enum

  (r.Status.GetDisplayName().Contains(searchBy))
 
 
 

Как я могу сравнить отображаемое имя перечисления со строкой поиска, если кто-то ищет разрешенный текст, я должен извлечь все записи о состоянии, разрешенные из БД

Ответ №1:

Это предупреждение возникает потому, что getDisplayName() — это пользовательский метод, реализованный в вашем коде, к которому база данных не может получить доступ или преобразовать в какой-либо оператор sql. Поэтому EF необходимо загрузить все объекты из базы данных и выполнить фильтрацию в памяти (чего следует избегать, поскольку фильтрация в базе данных выполняется намного быстрее).

Что вам нужно, так это «перепроектировать» термин, который искал пользователь, для фактического значения enum, например, вот так

 var filteredStatus = Enum.GetValues<Status>()
                         .Where(value => value.GetDisplayName().Contains(searchBy))
                         .ToList();

 

а затем в вашем запросе вместо использования r.Status.GetDisplayName().Contains(searchBy) use filteredStatus.Contains(r.Status)

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

1. Здесь Enum.getValues — это массив, и я не уверен, как мы можем применить Where к массиву. Я получаю исключение компиляции в Where, поэтому я добавил, как показано ниже, и теперь пытаюсь это решение Enum.getValues(typeof(Status)). Приведение <Status>().Где(v

2. @Mat Прошу прощения, что я использовал неправильную перегрузку getValues() — я обновил свой ответ до правильного

3. Можем ли мы применить это обратное проектирование и к перечислениям дочерних классов? r.Messages. Любой (p => p.Status. getDisplayName().Содержит(searchBy) )

4. @Mat Я не вижу кода, который вы упоминаете в своем комментарии в своем первоначальном вопросе, и поэтому у меня нет контекста, но в целом да, это решение будет работать и для других перечислений дочерних классов