DbSet<T>.Include () вызывает SELECT N+1 при использовании в методе расширения
У меня есть расширение на IQueryable, которое позволяет передавать строку имен свойств с разделителями, которая при использовании вызывает запрос не для создания JOIN и эффективно вызывает проблему SELECT N+1.
Что я заметил, так это то, что если я вызываю собственное расширение EF.Include("свойство") непосредственно из DbSet, все работает нормально. Но если я использую свое расширение (я даже упростил его, чтобы просто вызвать.Include("свойство"), SELECT 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;
}
1 ответ
Я думаю, что основная проблема здесь заключается в том, как вы используете метод Include, а также, кстати, метод Where. Эти методы, как это характерно для методов расширения LINQ, не изменяют объект, для которого они вызваны. Вместо этого они возвращают новый объект, который представляет запрос после применения оператора. Так, например, в этом коде:
var query = SomeQuery();
query.Include(q => q.Bing);
return query;
метод Include в основном ничего не делает, потому что новый запрос, возвращаемый методом Include, отбрасывается. С другой стороны, это:
var query = SomeQuery();
query = query.Include(q => q.Bing);
return query;
применяет к запросу параметр Include, а затем обновляет переменную запроса, добавляя новый объект запроса, возвращаемый из Include.
Это не в коде, который вы опубликовали, но я думаю, что вы все еще видите N+1 с вашим кодом, потому что Include игнорируется, и поэтому соответствующие коллекции все еще загружаются с использованием отложенной загрузки.