今天遇到一个LINQ的异常: 传入的表格格式数据流(TDS)远程过程调用(RPC)协议流不正确。此 RPC 请求中提供了过多的参数。最多应为 2100。
代码是:
Table<MyEntity> tbl = dataContext.GetTable<MyEntity>();
tbl.where(t => AnotherList.Contains(t.EntityID)).select(....)
报错的地方就是.Contains函数,当AnotherList的元素个数超过2100,LINQ的.Contains最大支持2100,超过就会报这个异常。这句LINQ背后的SQL语句可能是这样的:
SELECT [t0].....
FROM MyEntity AS [t0]
WHERE [t0].[EntityID] IN (@p0, @p1, @p2, @p3, @p4......2100)
所以就遇到SQL过长的问题,微软为了防止这个问题,就人为LINQ.Contains()限制了2100这个数量,很是奇怪。
解决办法很简单,只要将调用数据源的AsEnumerable()运算符就可以了。例如:
var tbl = dataContext.GetTable<MyEntity>().AsEnumerable();
tbl.where(t => AnotherList.Contains(t.EntityID)).select(....)
其它方法也有,就是转变思路,用JOIN的方式,而不要用WHERE ... IN (...,...,...)的方式。
为什么.AsEnumerable()这么神奇?
从MSDN看到,调用这个方法会返回一个 IEnumerable<T> 对象,而大家知道,和IQueryable比较起来,IEnumerable<T>是用了LINQ2Object的方式,而不是LINQ2SQL的方式,前者数据是在内存中处理的,而后者是在server上运行的。换句话说,本地数据源用IEnumerable<T>,数据在内存中,并且查询的逻辑可以直接用你所定义的方法的逻辑(因为有上下文);远程数据源用IQueryable<T>,无法直接使用你所定义的方法的逻辑,必须先生成表达式树,查询由源对象处理;IQueryable<T>类型可以在最终的执行前加一些方法,然后再返回最终的IQueryable<T>;而IEnumerable<T>已经包含了你执行查询后的所有结果,这时你的添加方法都是在已经有的结果集里进行。一言以蔽之,上面的2100的问题解决方法就是避免了LINQ2SQL生成表达式树查询远程服务器!
这个方法有个副作用:调用dataContext.GetTable<MyEntity>().AsEnumerable()的副作用是吃内存,如果这个table很大,估计内存空间会消耗很大。
此外,调用AsEnumerable()会导致Enumerable.Where运行,而不是原生的.Where运行,看个例子:
1 public class ProductList : List<Product>
2 {
3 public IEnumerable<Product> Where(Func<Product, bool> predicate)
4 {
5 Console.WriteLine("Got Here");
6 foreach (var product in this)
7 {
8 if (predicate(product))
9 {
10 yield return product;
11 }
12 }
13 }
14 }
15
16
17 var products = productList
18 .Where(p => p.Name.Contains(productName))
19 .ToArray();
20
21 //Console输出 “Got Here”
22
23 var products = productList
24 .AsEnumerable()
25 .Where(p => p.Name.Contains(productName))
26 .ToArray();
27 //Console没有输出