EF Core - фильтрация по дочерней коллекции пар ключ-значение чрезвычайно медленная - PullRequest
0 голосов
/ 03 октября 2018

Моя основная системная сущность «помечена» дочерней коллекцией пар «ключ-значение», которые я хочу использовать для фильтрации списка основных сущностей.Однако основной запрос EF, который я написал ниже, слишком медленный для приемлемого использования.

Упрощенные классы сущностей

 public class MainEntity
 {
    public int Id { get; set; }
    public DateTimeOffset Created { get; set; }
    public string Stuff {get; set;}
    public virtual List<Tag> Tags { get; set; }
 }

 public class Tag
 {
    public int Id { get; set; }
    public string Key { get; set; }
    public string Value { get; set; }

    public int MainEntityId { get; set; }
    public virtual MainEntity MainEntity { get; set; }
 }

Упрощенный запрос

//filter params passed into the query function
//String? stuffFilter
//List<Tag> tagSearchValues

var query = _dbContext.MainEntities.Where(
    me => ((!stuffFilter.HasValue || me.Stuff == stuffFilter.Value)                    
    && (tagSearchValues == null || tagSearchValues.Count == 0 ||
    (
    (me.Tags.Select(t => t.Key).Any(tk => tagSearchValues.Select(s => s.Key).Any(sk => sk == tk))) &&
    (me.Tags.Select(t => t.Value).Any(tv => tagSearchValues.Select(s => s.Value).Any(sv => sv == tv)))
    )
    ).                    
    OrderByDescending(l => me.Created).AsNoTracking();

Я немного устал от EF (и впервые использую EF Core), но проблема заключается в том, как я фильтрую по коллекции дочерних тегов с кратным.Любые () команды (запрос выполняется отлично, когда не указаны фильтры тегов).

Я не могу придумать другого способа фильтрации коллекции дочерних объектов Tag по выбранным объектам фильтра Tag - один тег Tag был бы намного проще и быстрее, я думаю.

Единственная альтернатива, о которой я сейчас могу подумать, - это сделать собственный запрос SQL самостоятельно, но, кажется, стыдно прибегать к этому уже при составлении моего первого запроса EF Core!

Ответы [ 2 ]

0 голосов
/ 04 октября 2018

Первое, на что нужно обратить внимание, - это то, что предложенный вами запрос не может быть полностью оценен как SQL, поскольку для коллекции, содержащей не примитивные значения, нет эквивалента SQL, tagSearchValues.Это заставляет EF автоматически переключаться на оценку на стороне клиента .То есть он извлекает в память все сущности, которые удовлетворяют условию stuffFilter, и все их теги, а затем применяет предикат тегов.Это, очевидно, неэффективно.

Во-вторых, запрос неточный.Объекты, содержащие теги с определенными ключами и теги с определенными значениями, не совпадают с тегами, содержащими определенные комбинации ключ / значение .Требуется запрос, соответствующий каждой комбинации, например:

db.MainEntities.Where(...)
    .Where(m => tagSearchValues
       .Any(t => m.Tags.Any(mt => mt.Key == t.Key 
                               && mt.Value == t.Value)))

Однако, если вы это сделаете, EF снова превратится в неэффективную оценку на стороне клиента, и вам даже придется применить Include илиленивая загрузка себя, чтобы вытащить теги в память.(Более того, по какой-то причине EF запускает тонны избыточных запросов).

Дело в том, что EF (как и другие ORM) плохо ориентируется на такие парные сравнения на стороне сервера.Поэтому вам нужен построитель предикатов для построения предикатов тегов.Есть несколько предикатов, например, в Linqkit .Я использую этот , потому что это красиво и просто.Рецепт таков: создайте предикат и примените его в Where():

var tagPredicate = PredicateBuilder.True<MainEntity>();
if (tagSearchValues.Any())
{
    tagPredicate = PredicateBuilder.False<MainEntity>();
    foreach (var tag in tagSearchValues)
    {
        tagPredicate = tagPredicate.Or(m => m.Tags
                           .Any(t => t.Key == tag.Key
                                  && t.Value == tag.Value));
    }
}

var query = _dbContext.MainEntities
    .Where(m => string.IsNullOrWhiteSpace(stuff) || m.Stuff == stuff)
    .Where(tagPredicate);
... // Use query

Я использую Or, потому что я предполагаю (из вашего запроса), что вы хотите, чтобы сущности имели any тег в поисковых тегах.Вот почему я начинаю с предиката PredicateBuilder.True, поэтому запрос будет возвращать результаты, если нет поисковых тегов, аналогичных вашему исходному запросу.

0 голосов
/ 04 октября 2018

Знаете ли вы, какой SQL генерируется EF Core Any?EF Core обладает неудачным свойством конструирования - выполнять запросы на стороне клиента без вывода сообщений, если они не могут быть переведены в SQL.

Что если консолидировать тестирование Key и Value?

(me.Tags.Any(met => tagSearchValues.Any(st => st.Tag == met.Tag && st.Value == met.Value)))

Или что, если вы используете Contains вместо?

(me.Tags.Select(t => t.Key).Any(tk => tagSearchValues.Select(s => s.Key).Contains(tk))) &&
(me.Tags.Select(t => t.Value).Any(tv => tagSearchValues.Select(s => s.Value).Contains(tv)))
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...