【问题标题】:DbSet<T>.Include() causes SELECT N+1 when used in extension methodDbSet<T>.Include() 在扩展方法中使用时会导致 SELECT N+1
【发布时间】:2012-06-10 19:39:48
【问题描述】:

我在 IQueryable 上有一个扩展,它允许传入带分隔符的属性名称字符串,使用时会导致查询不构造 JOIN 并有效地导致 SELECT N+1 问题。

我注意到,如果我直接从 DbSet 调用本机 EF 扩展 .Include("property") 一切正常。但是,如果我使用我的扩展程序(我什至将其简化为仅调用 .Include("property") 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;
}

【问题讨论】:

    标签: entity-framework linq-to-entities dynamic-linq objectquery


    【解决方案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 被忽略,因此相关集合仍在使用延迟加载加载。

    【讨论】:

    • 谢谢。有没有方便的备忘单或其他方法可以轻松找出哪些扩展方法修改现有查询与返回新查询?
    • 据我所知,LINQ 中的所有内容以及我们编写的 IQueryable 上的所有扩展方法都会返回新查询。 ObjectQuery 上的一些旧方法会修改现有查询,但希望您不会使用这些方法。
    猜你喜欢
    • 1970-01-01
    • 2019-06-20
    • 1970-01-01
    • 2017-02-20
    • 2014-06-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-02
    相关资源
    最近更新 更多