5

次のエラーを返す LINQ クエリがあります。

必要なのは、リストに ID がある BirthDate を持つすべてのクライアントを数えることだけです。クライアント ID のリストは膨大になる可能性があります (数百万のレコード)。

クエリは次のとおりです。

List<int> allClients = GetClientIDs();

int total = context.Clients.Where(x => allClients.Contains(x.ClientID) && x.BirthDate != null).Count();

このようにクエリを書き直すと、

int total = context
    .Clients
    .Count(x => allClients.Contains(x.ClientID) && x.BirthDate != null);

同じエラーが発生します。

また、別の方法で作成しようとしましたが、すべてのメモリを消費します:

List<int> allClients = GetClientIDs();

total = (from x in allClients.AsQueryable()
         join y in context.Clients
         on x equals y.ClientID
         where y.BirthDate != null
         select x).Count();
4

5 に答える 5

1

職場でも同じ問題に遭遇しました。問題は、ステートメントをlist.Contains()作成するWHERE column IN (val1, val2, ... valN)ため、そこに入れることができる値の数に制限があることです。私たちが最終的にやったのは、実際にはあなたがしたようにバッチで行うことでした.

ただし、これを行うためのよりクリーンでエレガントなコードを提供できると思います。通常使用する他の Linq メソッドに追加される拡張メソッドを次に示します。

public static IEnumerable<IEnumerable<T>> BulkForEach<T>(this IEnumerable<T> list, int size = 1000)
{
    for (int index = 0; index < list.Count() / size + 1; index++)
    {
        IEnumerable<T> returnVal = list.Skip(index * size).Take(size).ToList();
        yield return returnVal;
    }
}

次に、次のように使用します。

foreach (var item in list.BulkForEach())
{
    // Do logic here. item is an IEnumerable<T> (in your case, int)
}  

編集
または、必要に応じて、次のように通常の List.ForEach() のように動作させることができます。

public static void BulkForEach<T>(this IEnumerable<T> list, Action<IEnumerable<T>> action, int size = 1000)
{
    for (int index = 0; index < list.Count() / size + 1; index++)
    {
        IEnumerable<T> returnVal = list.Skip(index * size).Take(size).ToList();
        action.Invoke(returnVal);
    }
}

次のように使用します。

list.BulkForEach(p => { /* Do logic */ });
于 2013-02-03T17:31:36.710 に答える
0

上で述べたように、クエリはおそらく次のように翻訳されています。

select count(1)
from Clients
where ClientID = @id1 or ClientID = @id2 -- and so on up to the number of ids returned by GetClientIDs.

あまり多くのパラメーターを渡さないようにクエリを変更する必要があります。

生成された SQL をClients.Log = Console.Out表示するには、実行時にデバッグ ウィンドウに書き込まれるように設定できます。

編集:

チャンクに代わる方法として、ID を区切られた文字列としてサーバーに送信し、その文字列をリストに変換できる UDF をデータベースに作成することが考えられます。

var clientIds = string.Jon(",", allClients);

var total = (from client in context.Clients
            join clientIds in context.udf_SplitString(clientIds)
                on client.ClientId equals clientIds.Id
            select client).Count();

Google には、文字列を分割する UDF の例がたくさんあります。

于 2013-02-03T16:23:43.083 に答える
0

もう 1 つの代替手段であり、おそらくクエリ時に最も高速なのは、CSV ファイルから数値をデータベースの一時テーブルに追加してから、結合クエリを実行することです。

チャンクでクエリを実行すると、クライアントとデータベースの間で多くのラウンドトリップが発生します。関心のある ID のリストが静的であるか、めったに変更されない場合は、一時テーブルのアプローチをお勧めします。

于 2013-02-03T17:37:01.190 に答える
0

Gert Arnold が前に述べたように、チャンクでクエリを作成すると問題は解決しますが、見栄えが悪くなります。

List<int> allClients = GetClientIDs();

int total = 0;

const int sqlLimit = 2000;

int iterations = allClients.Count() / sqlLimit;

for (int i = 0; i <= iterations; i++)
{
    List<int> tempList = allClients.Skip(i * sqlLimit).Take(sqlLimit).ToList();

    int thisTotal = context.Clients.Count(x => tempList.Contains(x.ClientID) && x.BirthDate != null);

    total = total + thisTotal;
}
于 2013-02-03T16:47:07.343 に答える