我最近用Linq2Sql比较多,积累了一些小小的经验,也遇到一些挫折。今天只讲其中一个挫折:Linq2SQL与AsQueryable水土不服。
在我的一个项目里面,经常发现系统无故Down掉。这种Down掉以前从来没有遇到过,因为系统的EventLog里面没有任何记录,try...catch也不管用——即使用了try...catch,也仿佛是遭遇了逻辑黑洞,整个进程就在那一点突然消失掉。由于第一次发现这个问题的时候,是在一个能够根据用户输入的条件拼装查询条件(Expression)的地方,因此怀疑可能是表达式太复杂了,.NET的某些部分出错了(比如说编译出错)。由于时间比较紧迫,最终直接改成不是产生表达式,而是直接进行Where等函数的调用。由于Linq2Sql用的是IQueryable接口,实际上是要到最后才产生最终的执行调用的,于是这么改性能不会变差,甚至可能会变得更好也不一定。这么改了之后倒是正确了,于是也没有仔细考究。
可是,后来程序又莫名其妙的崩溃了。有了之前一次经验,立刻把问题定位到Linq2Sql的部分。该页面唯一一个比较特殊的地方,就是用了一个AsQueryable。具体的情况类似网上的一个已知的问题反馈:
http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=355026
int[] ids = new [] { 1, 2, 3 };
IQueryable<int> queryableIds = ids.AsQueryable();
var query = dataContext.MyTable.Where(x => queryableIds.Contains(x.Id));
Console.WriteLine(query.ToString()); // This fails
网上其实还有很多其他的人也遇到了类似的情况,例如:
int[] ids = dataContext.MyOtherTable.Select(x => x.Id).ToArray();
IQueryable<int> queryableIds = ids.AsQueryable();
var query = dataContext.MyTable.Where(x => queryableIds.Contains(x.Id));
Console.WriteLine(query.ToString()); // This fails
有人(貌似是MS的人)就说,这个改成join,或者改成 var ids = dataContext.MyOtherTable.Select(x => x.Id) 就好了。得到这样答案的人固然欢天喜地,确实不出问题了,只是大家都会有困惑,为什么原来的写法就有问题呢?实际上是一个Bug造成的,而且似乎是Native部分的堆栈溢出,只是这个Bug在我上面查到的页面里面说是已经给修正了。可问题是,这个修正发布了没有?在不在VS2008/.NET 3.5的SP1里面?没有说。而且这个问题怪就怪在,似乎遇到问题的人不多,连国外的网站都找不到这个问题的解决方案。(难道我太孤陋寡闻了?)
找不到补丁,又没有人明白说如何解决,只好自己想办法定位一下。既然是在使用了AsQueryable后才出的问题,那么在AsQueryable那边出问题的可能性就比较大了。由于实际上是一个native的stackoverlow,而且try...catch失效,无法定位出问题的位置。假如重做整个代码,然后进行调试,也许能够在中间某个断点被截获,最终可以发现问题所在。AsQueryable实际上是用EnumerableQuery<T>(一个internal的类,用Reflector才能看到)把IEnumerable<T>包装了一层,于是呢,我找了个周末,把整个EnumerableQuery相关的代码复制出来重做了一份,除了ExpressionCompiler是直接通过反射调用的之外。结果让我非常诧异,竟然完全没有任何作用——我自己实现的EnumerableQuery里面的任何地方的断点都没有到达!换句话说,问题可能不在AsQueryable里面,而更可能是Linq2Sql的某个地方。目前我的分析是,由于System.Core.dll已经本地化为NativeCode,因此某些.NET CLR的保护机制在某些特殊情况下可能会失效,所以才造成了自己实现EnumerableQuery无法得到断点,同时整个进程也还是悄无声息的崩溃掉。关于.NET CLR保护机制失效,请参见MSDN里面的CER。
目前这些都是猜测,想看看博客园是否有人能够找得到这个问题的答案。我更想知道的是,除了放弃使用AsQueryable之外,还有什么解决办法。目前似乎也只能够是尽可能把各函数的参数和返回值修改为都是IQueryable的,但是估计难度有点大。
P.S.:
似乎Linq2Sql还有别的地方有Bug,我忘了具体的情况了,当时情况类似:
int[] ids ={1,2,3};
var q = from item in SomeCallWithIQueryableResult()
where ids.Contains(item.Id)
select item;
需要说明的是,SomeCallWithIQueryableResult()这部分内部也有一些处理。最终报告的是不支持IEumerable的Contains操作,这个错误网上也有见到,建议的解决方案是想办法用join来替代。这个Bug我没有尝试进行简化重现,时间也稍微久远一点,所以描述的可能不太准确。但可以肯定地是,当时确实报错了,而且感觉上比较莫名其妙。你们还遇到过什么别的Bug吗?