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 のリクエストを実行することです。1 回のリクエストで必要なものを取得する必要があります。これを行うには、正しい 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

クエリのプロファイルを作成することをお勧めします。データベースが毎回ヒットするため、n+1の問題が発生していると思われます。Firstが呼び出されます。これをループで実行しているため、データベースで過度のヒットが発生する可能性があります。ループ外の状態を評価し、ループのない状態に基づいて個別のクエリを設定します。

于 2012-10-09T14:50:00.023 に答える
0

問題は、ループ内のアイテムごとにデータベースを呼び出すことです。
クエリを 1 つのクエリにまとめる必要があります。
このようにして、クエリはデータベース上で 1 つの大きなクエリとして実行され、その結果は 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;

        // ...
    }
}
于 2012-10-09T14:46:24.817 に答える
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 に答える