我一直使用Linq To SQL,以前一直没有问题,但前两天发生了一件怪事。先写个示例代码有助于理解。

 1 /// <summary>根据指定的参数和分页信息搜索文章,并输出分页情况。</summary>
 2 /// <param name="title">文章标题包含的内容。</param>
 3 /// <param name="keyword">文章关键字包含的内容。</param>
 4 /// <param name="pageIndex">从0开始的页码。</param>
 5 /// <param name="pageSize">每页包含的记录数。</param>
 6 /// <param name="pageCount">输出总页数。</param>
 7 /// <param name="total">输出匹配记录的总数目。</param>
 8 /// <return>返回匹配文章的数组。</return>
 9 public static Article[] QueryArticles(string title, string keyword, int pageIndex, int pageSize, out int pageCount, out int total)
10 {
11     using (FormusDataContext dc = new ForumsDataContext())
12     {
13         Dictionary<stringobject> args = new Dictionary<stringobject>();
14         args["Title"= title;
15         args["Keyword"= keyword;
16         var q = ParseQuery(args, dc.Articles);
17 
18         total = q.Count();
19         pageCount = (int)Math.Ceiling((double)total / (double)pageSize);
20 
21         q = q.OrderByDescending(o => o.PostTime).Skip(pageIndex * pageSize).Take(pageSize);
22         return q.ToArray();
23     }
24 }
25 
26 // 根据参数字典生成查询。
27 private static IEnumerable<Article> ParseQuery(Dictionary<stringobject> args, IEnumerable<Article> source)
28 {
29     var q = source;
30 
31     if (args.ContainsKey("Title"))
32     {
33         string value = (string)args["Title"];
34         q = q.Where(o => o.Title.Contains(value));
35     }
36 
37     if (args.ContainsKey("Keyword"))
38     {
39         string value = (string)args["Keyword"];
40         if (!string.IsNullOrEmpty(keyword))
41         {
42             q = q.Where(o => o.Keywords.Contains(value));
43         }
44     }
45 
46     // 解析其它参数
47 
48     return q;

49 } 

粗粗一看,这段程序并没有什么大问题,它的主要功能是提供了一个文章搜索的功能,通过指定文章的标题和关键字来检索数据。可在实际运行时,我发现如果提供了keyword这个参数时,程序就会在第18行抛出一个NullReferenceException,而错误是由Linq中的o.Keywords.Contains(value)引起的,因为Keywords这个字段在数据库里是可能有空值的。起始我很纳闷,q.Count()应该是由Linq To SQL生成一条SQL语句在数据库里执行,怎么会在C#的代码里出现这个错误呢。通过一番猜想和试验,我找到了问题的所在。 

罪魁祸首就是ParseQuery这个方法的参数和返回值使用了IEnumerable泛型类,这样整个查询连接起来就是一个混合体:
dc.Articles.Where(o => o.Keyword.Contains("...")).Count()
接下来会发生什么呢?很明显,整条语句的前半部分dc.Articles会通过Linq To  SQL来执行,它会把数据库中表的所有内容都读出来,剩下的一部分会通过Linq To Object来执行,仅仅对已生成的对象统计一个数量。而因为Linq To Object是在C#代码中执行的,就会发生之前所说的问题了。而即使不出现这样的错误,这种情况也是不能容忍的,因为每次执行都会把所有的数据都读出来,那个效率可想而知了。

要解决这个问题很简单,把ParseQuery方法的参数和返回值改成IQueryable<Article>就可以了。在此,也要提醒自己和广大的同行们,在使用扩展方法时应该注意,对于在有继承关系的类型上定义了相同名称和参数的扩展方法,必须准确的明白自己要调用的是哪个类型的扩展方法,而在调用时应该显式地将变量声明为相应的类型,而不应该随意地声明为某个父类型。

PS:以上代码是随手敲的,不保证代码可以通过编译。

posted on 2011-03-24 09:25  一风  阅读(2861)  评论(19编辑  收藏  举报