提高LINQ枚举器内部的condtitional查询性能

时间:2012-10-09 14:25:20

标签: c# performance linq linq-to-sql

我使用.NET 3.5(客户要求)获得了一些看起来像这样的代码:

void Process (ActionState state)
{
    var orderItemQuery = from OrderItem item in order.OrderItems
                           orderby item.OrderLineNumber ascending
                           select item;

    foreach (OrderItem item in orderItemQuery)
    {
        ActionData actionData;
        switch (state)
        {
            case ActionState.Prepare:
                actionData = (
                    from ActionData ad in db.ActionDataTable
                      where ad.ObjectId == item.ProductId
                      select ad
                ).First();

            case ActionState.QualityCheck:
                actionData = (
                    from ActionData ad in db.ActionDataTable
                      where ad.ObjectId == item.OrderItemId
                      select ad
                ).First();

            default:
                throw new InvalidOperationException();
        }

        // ...
    }
}

基本上,迭代第一个查询的结果,并使用特定的外键根据当前ActionData从数据库中获取ActionState记录。实际上,这里和那里有更多的嵌套和一些检查,但它基本上是相同的。

这最初适用于测试数据库,但客户只是向我发送了他们的实时数据的副本,而且速度非常慢。运行整个批次大约需要15分钟,包括其他一些处理。通过性能测试器运行代码后,我发现整个过程中最慢的部分是对每个案例的.First()调用。

如果这是普通的SQL,我会根据state的值编译循环外的存储过程,并使用它。既然我做不到,那还有什么选择呢?我怎样才能加快速度呢?

4 个答案:

答案 0 :(得分:2)

您的问题是您的代码对db执行100500个请求。您需要在一个请求中获得所需内容。为此,您需要编写正确的linq查询。像这样的东西:

if(state!=ActionState.Prepare&&state!=ActionState.QualityCheck)
    throw new InvalidOperationException();
var orderSelector = state == ActionState.Prepare
                             ?o=>o.ProductId
                             :?o=>o.OrderItemId
var orderWithActionQuery  = order.OrderItems.Orderby(o => o.OrderLineNumber).GroupJoin(
        db.ActionDataTable,
        orderSelector,
        ad => ad.ObjectId,
        (x, y) => new { item = x, actionDatas = y })
        .Select(c => new {item = c.item, actionData = c.actionDatas.FirstOrDefault()});    
foreach(var orderWithActionData in orderWithActionQuery)
{
     //orderWithActionData.item is orderItem
     //orderWithActionData.actionData is ation data of this item
}

在此代码中,对数据库的请求将与foreach一致发送。这将是单一的请求,性能会很好

答案 1 :(得分:0)

问题是您为循环中的每个项目调用数据库 您应该在一个查询中一起烘焙查询 这样,查询应作为数据库上的一个大型查询执行,结果应该从SQL服务器中获取。

您可以使用SQL事件探查器轻松发现问题并查看大量select x,y,z,... from ActionData个查询。

这个选择OrderItem和ActionData到包含两个值的临时匿名类型。

void Process (ActionState state)
{
    var orderItemQuery = from OrderItem item in order.OrderItems
                           orderby item.OrderLineNumber ascending
                           select item;
    var orderItemWithActionDataQuery = 
                from item in orderItemQuery
                select new{Item = item,
                           ActionData = (
                    from ActionData ad in db.ActionDataTable
                      where ad.ObjectId == item.ProductId
                      select ad
                ).First()};

        switch (state)
        {
            case ActionState.Prepare:
                // done
                break;

            case ActionState.QualityCheck:
                orderItemWithActionDataQuery = 
                from item in orderItemQuery
                select new{Item = item,
                           ActionData = (
                    from ActionData ad in db.ActionDataTable
                      where ad.ObjectId == item.OrderItemId
                      select ad
                ).First()};

            default:
                throw new InvalidOperationException();
        }



    foreach (var combinedItem in orderItemWithActionDataQuery)
    {
        OrderItem item = combinedItem.Item;
        ActionData actionData = combinedItem.ActionData;

        // ...
    }
}

答案 2 :(得分:0)

您可能想要查询您的查询。我怀疑你遇到了n + 1问题,因为每次都会遇到你的数据库.First被调用。由于您是在循环中执行此操作,因此您的数据库可能会看到过多的命中。评估循环外的状态,并根据状态设置单独的查询,而不进行循环。

答案 3 :(得分:0)

您可能会发现这非常有效:

不是将Select放入For循环,而是生成OrderItemIds数组并使用Array.Contains();

void Process (ActionState state)
{
    var orderItemQuery = from OrderItem item in order.OrderItems
                           orderby item.OrderLineNumber ascending
                           select item;

    IEnumerable<ActionData> actionDatas = null;

    if (state ==  ActionState.Prepare)
    {
        var productIds = orderItemQuery.Select(o => o.ProductId).ToArray();
        actionDatas = db.ActionDataTable.Where(a => productIds.Contains(a.ObjectId));
    }
    else if(state == ActionState.QualityCheck)
    {
        var orderItemIds = orderItemQuery.Select(o => o.OrderItemId).ToArray();
        actionDatas = db.ActionDataTable.Where(a => orderItemIds.Contains(a.ObjectId));
    }
    else
    {
        throw new InvalidOperationException();
    }

        // Do stuff against the complete list of actionDatas without requerying.
    }
}