4

У меня есть расширение на IQueryable, которое позволяет передавать в разделительной строке имена свойств, которые при использовании заставляют запрос не создавать JOINs и эффективно вызывает SELECT N + 1 выпуск.DbSet <T> .Include() вызывает SELECT N + 1 при использовании в методе расширения

Что я заметил, так это то, что если я называю встроенное расширение EF .Include ("property") непосредственно от DbSet, все работает нормально. Но если я использую мое расширение (я даже упростили ее просто называют .INCLUDE («свойство») ВЫБРАТЬ N + 1 происходит ...

Мои вопросы почему? Что я делаю неправильно?

Здесь вызова метода (от службы)

public MyModel[] GetAll(int page, out int total, int pageSize, string sort, string filter) 
{ 
    return _myModelRepository 
     .Get(page, out total, pageSize, sort, filter, "PropertyOnMyModelToInclude") 
     .ToArray(); 
} 

Вот метод, который использует репозиторий добавочные

public virtual IQueryable<T> Get(int page, out int total, int pageSize, string sort, string filter = null, string includes = null) 
{ 
    IQueryable<T> query = DatabaseSet; 
    if (!String.IsNullOrWhiteSpace(includes)) 
    { 
     //query.IncludeMany(includes); // BAD: SELECT N+1 
     //query.Include(includes); // BAD: SELECT N+1 
    } 
    if (!String.IsNullOrWhiteSpace(filter)) 
    { 
     query.Where(filter); 
    } 
    total = query.Count(); // needed for pagination 
    var order = String.IsNullOrWhiteSpace(sort) ? DefaultOrderBy : sort; 
    var perPage = pageSize < 1 ? DefaultPageSize : pageSize; 

    //return query.OrderBy(order).Paginate(page, total, perPage); // BAD: SELECT N+1 (in both variations above) 
    //return query.IncludeMany(includes).OrderBy(order).Paginate(page, total, perPage); // BAD: SELECT N+1 
    return query.Include(includes).OrderBy(order).Paginate(page, total, perPage);  // WORKS! 
} 

Вот расширение (уменьшенное только для вызова Include(), чтобы проиллюстрировать проблему)

public static IQueryable<T> IncludeMany<T>(this IQueryable<T> query, string includes, char delimiter = ',') where T : class 
{ 
    // OPTION 1 
    //var propertiesToInclude = String.IsNullOrWhiteSpace(includes) 
    //        ? new string[0] 
    //        : includes.Split(new[] {delimiter}, StringSplitOptions.RemoveEmptyEntries).Select(p => p.Trim()).ToArray(); 
    //foreach (var includeProperty in propertiesToInclude) 
    //{ 
    // query.Include(includeProperty); 
    //} 
    // OPTION 2 
    //if (!String.IsNullOrWhiteSpace(includes)) 
    //{ 
    // var propertiesToInclude = includes.Split(new[] { delimiter }, StringSplitOptions.RemoveEmptyEntries).AsEnumerable(); //.Select(p => p.Trim()); 
    // propertiesToInclude.Aggregate(query, (current, include) => current.Include(include)); 
    //} 

    // OPTION 3 - for testing 
    query.Include(includes); 

    return query; 
} 

ответ

7

Я думаю, что основная проблема здесь заключается в том, как вы используете метод Include, а также, кстати, метод Where. Эти методы, как это типично для методов расширения LINQ, не изменяют объект, на который они вызывают. Вместо этого они возвращают новый объект, который представляет запрос после того, как оператор был применен. Так, например, в этом коде:

var query = SomeQuery(); 
query.Include(q => q.Bing); 
return query; 

Включить метод в основном ничего не делает, потому что новый запрос, возвращаемый Включить отбрасывается. С другой стороны, это:

var query = SomeQuery(); 
query = query.Include(q => q.Bing); 
return query; 

применяет Включить в запрос, а затем обновляет переменную запроса с новым объектом запроса, возвращаемый из Include.

Это не в коде, который вы опубликовали, но я думаю, что вы все еще видите N + 1 с кодом, потому что Include игнорируется, и связанные коллекции поэтому все еще загружаются с использованием ленивой загрузки.

+0

спасибо. есть ли удобный чит-лист или какой-то другой способ легко узнать, какие методы расширения изменяют существующие и возвращают новый запрос? – zam6ak

+2

Насколько я знаю все в LINQ и все методы расширения на IQueryable, которые мы написали, возвращают новые запросы. Некоторые старые методы ObjectQuery изменяют существующий запрос, но, надеюсь, вы не будете использовать их. –