5

我的 LINQ 查询返回以下错误:“传入的表格数据流 (TDS) 远程过程调用 (RPC) 协议流不正确。此 RPC 请求中提供了太多参数。最大值为 2100”。

我所需要的只是计算所有具有 BirthDate 且我在列表中有其 ID 的客户。我的客户 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,该 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

正如 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 回答
0

另一种选择并且可能是查询时最快的方法是将您的数字从 CSV 文件添加到数据库中的临时表中,然后执行连接查询。

分块进行查询意味着在客户端和数据库之间进行大量往返。如果您感兴趣的 ID 列表是静态的或很少更改,我建议使用临时表的方法。

于 2013-02-03T17:37:01.190 回答
0

如果您不介意将工作从数据库移动到应用程序服务器并拥有内存,请尝试此操作。

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

于 2014-02-21T19:46:07.773 回答