2

我有一些看起来像这样的代码,使用 .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

4 回答 4

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 发送。这将是单个请求,性能会很好

于 2012-10-09T15:01:39.373 回答
0

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

您可以通过使用 SQL Profiler 轻松发现问题并查看大量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;

        // ...
    }
}
于 2012-10-09T14:46:24.817 回答
0

您可能想要分析您的查询。我怀疑您遇到了 n+1 问题,因为每次调用 .First 时都会点击您的数据库。由于您在循环中执行此操作,因此您的数据库可能会看到过多的命中。评估循环外的状态,并在没有循环的情况下根据状态设置单独的查询。

于 2012-10-09T14:50:00.023 回答
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.
    }
}
于 2012-10-09T14:51:58.150 回答