我有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();
答案 0 :(得分: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 */ });
答案 1 :(得分: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的例子。
答案 2 :(得分: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;
}
答案 3 :(得分:0)
另一种替代方法,可能是查询时最快的方法是将CSV文件中的数字添加到数据库中的临时表中,然后进行连接查询。
以块的形式进行查询意味着客户端和数据库之间会进行大量的往返。如果您感兴趣的ID列表是静态的或很少更改,我建议使用临时表的方法。
答案 4 :(得分:0)
如果您不介意将工作从数据库移动到应用程序服务器并拥有内存,请尝试此操作。
int total = context.Clients.AsEnumerable()。Where(x =&gt; allClients.Contains(x.ClientID)&amp;&amp; x.BirthDate!= null).Count();