C#实体框架+Linq——如何加速慢查询?

7
这是一个与C# MVC2 Jqgrid - what is the correct way to do server side paging?相关的问题,我在那里询问并找到了如何改进具有2000行左右的表格查询性能的方法。性能从10秒提高到了1秒。

现在我正在尝试执行完全相同的查询,其中表格有20,000行 - 查询需要30秒。我该如何进一步改进呢?20000行仍然不算很多。

我有一些可能的想法:

  • 通过去除连接和求和来去规范化以改善它
  • 创建视图并查询该视图,而不是查询和连接表格
  • 不查询整个表格,让用户先选择某些过滤器(例如A | B | C..等过滤器)
  • 向表格添加更多索引
  • 其他什么方法?

这是需要30秒的MVC动作:(参数由jqgrid提供,其中sidx =排序列,sord =排序顺序)

public ActionResult GetProductList(int page, int rows, string sidx, string sord, 
string searchOper, string searchField, string searchString)
{
    if (sidx == "Id") { sidx = "ProductCode"; }
    var pagedData = _productService.GetPaged(sidx, sord, page, rows);
    var model = (from p in pagedData.Page<Product>()
            select new
            {
                p.Id, p.ProductCode, p.ProductDescription,
                Barcode = p.Barcode ?? string.Empty, 
                UnitOfMeasure = p.UnitOfMeasure != null ? p.UnitOfMeasure.Name : "",
                p.PackSize, 
                AllocatedQty = p.WarehouseProducts.Sum(wp => wp.AllocatedQuantity),
                QtyOnHand = p.WarehouseProducts.Sum(wp => wp.OnHandQuantity)
            });

    var jsonData = new
    {
        Total = pagedData.TotalPages, Page = pagedData.PageNumber,
        Records = pagedData.RecordCount, Rows = model
    };

    return Json(jsonData, JsonRequestBehavior.AllowGet);
}

ProductService.GetPaged() 调用 ProductRepository.GetPaged ,它又调用了 genericRepository.GetPaged() ,该方法执行如下操作:

public ListPage GetPaged(string sidx, string sord, int page, int rows)
{
    var list = GetQuery().OrderBy(sidx + " " + sord);
    int totalRecords = list.Count();

    var listPage = new ListPage
    {
        TotalPages = (totalRecords + rows - 1) / rows,
        PageNumber = page,
        RecordCount = totalRecords,
    };

    listPage.SetPageData(list
        .Skip((page > 0 ? page - 1 : 0) * rows)
        .Take(rows).AsQueryable());

    return listPage;
}

.OrderBy()子句使用LinqExtensions,以便我可以传递字符串而不是谓词 - 这可能会减慢速度吗?

最后,ListPage只是一个方便地封装了jqgrid所需的分页属性的类:

public class ListPage
{
    private IQueryable _data;
    public int TotalPages { get; set; }
    public int PageNumber { get; set; }
    public int RecordCount { get; set; }

    public void SetPageData<T>(IQueryable<T> data) 
    {
        _data = data;
    }

    public IQueryable<T> Page<T>()
    {
        return (IQueryable<T>)_data;
    }
}

GetQuery是:

public IQueryable<T> GetQuery()
{
    return ObjectSet.AsQueryable();
}

自定义的.OrderBy方法由以下两个方法组成:
public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, 
    string ordering, params object[] values)
{
    return (IQueryable<T>)OrderBy((IQueryable)source, ordering, values);
}

public static IQueryable OrderBy(this IQueryable source, string ordering, 
    params object[] values)
{
    if (source == null) throw new ArgumentNullException("source");
    if (ordering == null) throw new ArgumentNullException("ordering");
    ParameterExpression[] parameters = new ParameterExpression[] {
        Expression.Parameter(source.ElementType, "") };
    ExpressionParser parser = new ExpressionParser(parameters, ordering, values);
    IEnumerable<DynamicOrdering> orderings = parser.ParseOrdering();
    Expression queryExpr = source.Expression;
    string methodAsc = "OrderBy";
    string methodDesc = "OrderByDescending";
    foreach (DynamicOrdering o in orderings)
    {
        queryExpr = Expression.Call(
            typeof(Queryable), o.Ascending ? methodAsc : methodDesc,
            new Type[] { source.ElementType, o.Selector.Type },
            queryExpr, Expression.Quote(Expression.Lambda(o.Selector, parameters)));
        methodAsc = "ThenBy";
        methodDesc = "ThenByDescending";
    }
    return source.Provider.CreateQuery(queryExpr);
}

通过修改后,我对它如何跳回基于泛型的代码感到非常困惑,因为OrderBy使用非泛型代码…你能澄清一下吗?我是否漏掉了一些显而易见的东西? - Marc Gravell
另外 - 问题有点傻,但是排序时是否已经定义了索引?这可能只需要在数据库列上添加一个索引即可解决。 - Marc Gravell
关于泛型和非泛型的问题问得好。我不知道,但当我将鼠标悬停在.OrderBy上时,它显示为(extension) IQuerable<T>,并且还显示var listIQueryable<T>。ProductCode列上没有索引-它是nvarchar(500),因此太大而无法建立索引,就我所知。 - JK.
@JK - 索引肯定会有帮助,但我认为你发布的那个OrderBy方法可能不是正确的;你可以尝试使用[F12](转到定义)功能。 - Marc Gravell
啊,对了,按F12键可以从通用模式切换到非通用模式,然后再切回来。我会再次编辑问题的(我可以编辑几次?)。 - JK.
显示剩余2条评论
1个回答

6

我关心的部分是:

.Take(rows).AsQueryable()

您需要添加 AsQueryable() 表明当前类型为 IEnumerable<T>,这意味着您可能在查询的错误端进行分页操作(在网络上返回了过多的数据)。没有 GetQuery() 和自定义 OrderBy(),很难确定 - 但是像往常一样,首先要通过跟踪对查询进行分析。查看执行的查询和返回的数据。 EFProf 可能会使此操作变得更加容易,但 SQL 跟踪也足够使用。

1
还有一个鲜为人知的ObjectQuery<T>扩展方法,叫做.ToTraceString(),它可以显示要执行的SQL查询。对于日志记录非常方便。 - RPM1984
抱歉我错过了,GetQuery()只有一行代码:return ObjectSet.AsQueryable();。我会编辑并添加OrderBy()的代码 - 我不知道它是从哪里获取的,可能是这里:linqextensions.codeplex.com/。 - JK.
@JK - 说实话,如果你删除.AsQueryable()并检查代码是否正常工作,可能就足够了。 - Marc Gravell
我在哪里可以添加.ToTraceString()呢?我没有任何类型为ObjectQuery<T>的变量。 - JK.
1
@Marc Gravell - ObjectQuery<T> 实现了 IQueryable<T> 接口。ObjectQuery<T> 是特定于 EF 的,因此由于他正在使用 EF,他可以进行显式转换为 ObjectQuery<T>var trace = ((ObjectQuery)query).ToTraceString()。他正在执行 ObjectSet.AsQueryable()。如果他没有执行 AsQueryable 并在 ObjectSet 上执行了一些 LINQ 操作,则结果将是一个 ObjectQuery<T>。我错了吗? - RPM1984
显示剩余9条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接