Ich habe eine Erweiterung für IQueryable, die das Übergeben einer begrenzten Zeichenfolge von Eigenschaftennamen ermöglicht, die bei Verwendung dazu führt, dass die Abfrage keine JOINs erstellt und das Problem SELECT N + 1 effektiv verursacht.
Was mir aufgefallen ist, ist, dass, wenn ich die native EF-Erweiterung .Include ("property") direkt von DbSet aus anrufe, alles einwandfrei funktioniert. Aber wenn ich meine Nebenstelle benutze (ich habe es sogar vereinfacht, einfach .Include ("property") aufzurufen, tritt SELECT N + 1 auf ...
Meine Fragen sind warum? Was mache ich falsch?
Hier ist Aufrufmethode (vom Dienst)
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();
}
Hier ist die Repository-Methode, die die Erweiterung verwendet
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!
}
Hier ist die Erweiterung (reduziert, um Include () aufzurufen, um das Problem zu veranschaulichen)
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;
}
Ich denke, das grundlegende Problem hier liegt in der Art, wie Sie die Include-Methode verwenden, und nebenbei auch die Where-Methode. Diese Methoden ändern, wie bei LINQ-Erweiterungsmethoden üblich, das Objekt, für das sie aufgerufen werden, nicht. Sie geben stattdessen ein neues Objekt zurück, das die Abfrage darstellt, nachdem der Operator angewendet wurde. So zum Beispiel in diesem Code:
var query = SomeQuery();
query.Include(q => q.Bing);
return query;
Die Include-Methode führt im Grunde nichts aus, da die von Include zurückgegebene neue Abfrage verworfen wird. Auf der anderen Seite:
var query = SomeQuery();
query = query.Include(q => q.Bing);
return query;
wendet das Include auf die Abfrage an und aktualisiert dann die Abfragevariable mit dem neuen Abfrageobjekt, das von Include zurückgegeben wird.
Es ist nicht in dem von Ihnen geposteten Code enthalten, aber ich denke, Sie sehen immer noch N + 1 mit Ihrem Code, da das Include ignoriert wird und die zugehörigen Sammlungen daher weiterhin mit Lazy Loading geladen werden.