linq to sql中慎用Where<T>(Func<TSource, bool> predicate),小心被Linq给"骗"了!

近日在一个大型Web项目中,采用Linq to Sql替换原来的sqlcommand/sqldatareader方式来获取数据,上线后刚开始一切正常,但是随着访问量的增加,网站明显慢了很多,监测服务器CPU占用率/内存使用情况等性能指标却发现均在正常范围内,无意中在SqlServer Profier中跟踪数据库执行的sql语句时,发现有大量语句直接将整个表的数据全部提取出来了,而非仅返回分页中的当前页数据!

而这些SQL都是Linq自动翻译并最终提交到数据库的,查看了相关的代码,明明写着Skip(n).Take(m)类似的语句,为何还会生成这么“傻”的sql呢?

于是写了以下测试代码[测试环境:vs.net2008 + sqlsever2005 + win2003],最终发现是Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);使用后,导致这个问题的产生

1.测试表T_Test:

CREATE TABLE [dbo].[T_Test](

    [F_ID] [int] IDENTITY(1,1) NOT NULL,

    [F_Name] [nvarchar](50) COLLATE Chinese_PRC_CI_AS NULL,

    [F_Age] [int] NULL,

 CONSTRAINT [PK_T_Test] PRIMARY KEY CLUSTERED 

(

    [F_ID] ASC

)WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]

) ON [PRIMARY]

录入了几条测试数据:

F_ID F_Name F_Age

15 Jimmy 20

16 Mary 14

17 Jack 30

18 张三 35

19 李四 24

2.新建一个"控制台应用程序",把T_Test拖到dbml中,Program.cs文件中输入如下代码:

 using System;

 using System.Collections.Generic;

 using System.Linq;

 using System.Linq.Expressions;

 using CNTVS.LINQ;

 namespace TestLinq

 {
   class Program
   {
       static void Main(string[] args)
       {           

           //Expression<Func<T_Test, bool>> _Expression = PredicateBuilder.True<T_Test>();
           //_Expression = _Expression.And(t => t.F_Age >= 20).And(t => t.F_Name.Contains("J"));
           //var Data = GetData(_Expression, 1, 1);

           var Data = GetData(1, 1);

           ShowData(Data);
           
          
       }

       /**//// <summary>
       /// 用Where<T>(Expression)方式获取数据
       /// </summary>
       /// <param name="ExpWhere"></param>
       /// <param name="PageSize"></param>
       /// <param name="CurrentPageIndex"></param>
       /// <returns></returns>
       static List<T_Test> GetData(Expression<Func<T_Test,bool>> ExpWhere,int PageSize,int CurrentPageIndex) 
       {
           List<T_Test> _Result = null;
           using (DBDataContext db = new DBDataContext())
           {
               try
               {
                   var query = db.T_Test.Where<T_Test>(ExpWhere.Compile()).Skip((CurrentPageIndex - 1) * PageSize).Take(PageSize);
                   _Result = query.ToList();
               }
               finally { db.Connection.Close(); }
           }
           return _Result;
       }


       /**//// <summary>
       /// 用Where(Lambda)方式获取数据
       /// </summary>
       /// <param name="PageSize"></param>
       /// <param name="CurrentPageIndex"></param>
       /// <returns></returns>
       static List<T_Test> GetData(int PageSize, int CurrentPageIndex)
       {
           List<T_Test> _Result = null;
           using (DBDataContext db = new DBDataContext())
           {
               try
               {
                   var query = db.T_Test.Where(t => t.F_Age >= 20 && t.F_Name.Contains("J")).Skip((CurrentPageIndex - 1) * PageSize).Take(PageSize);
                   _Result = query.ToList();
               }
               finally { db.Connection.Close(); }
           }
           return _Result;
       }
       

       /**//// <summary>
       /// 显示数据
       /// </summary>
       /// <param name="Data"></param>
       static void ShowData(List<T_Test> Data) 
       {
           foreach (var item in Data)
           {
               Console.WriteLine("Name:{0}\t,Age:{1}", item.F_Name, item.F_Age.ToString());
           }
           Console.ReadKey();
       }
   }

}

代码很简单,找出F_Name中包含字母"J",F_Age大于20的记录,并且跳过第一个后,仅获取一条记录

注:PredicateBuilder是一个老外写的用于动态构造Expression表达式的工具类,在查询条件不确定,需要动态创建时,非常有用,完整代码如下:

 1 using System;
 2 using System.Linq;
 3 using System.Linq.Expressions;
 4
 5 namespace CNTVS.LINQ
 6 {    
 7
 8    public static class PredicateBuilder
 9    {
10      public static Expression<Func<T, bool>> True<T> ()  
11      { 
12          return f => true;  
13      }
14
15      public static Expression<Func<T, bool>> False<T> () 
16      { 
17          return f => false; 
18      }
19     
20      public static Expression<Func<T, bool>> Or<T> (this Expression<Func<T, bool>> expr1,
21                                                          Expression<Func<T, bool>> expr2)
22      {
23        var invokedExpr = Expression.Invoke (expr2, expr1.Parameters.Cast<Expression> ());
24        return Expression.Lambda<Func<T, bool>>
25              (Expression.Or (expr1.Body, invokedExpr), expr1.Parameters);
26      }
27     
28      public static Expression<Func<T, bool>> And<T> (this Expression<Func<T, bool>> expr1,
29                                                           Expression<Func<T, bool>> expr2)
30      {
31        var invokedExpr = Expression.Invoke (expr2, expr1.Parameters.Cast<Expression> ());
32        return Expression.Lambda<Func<T, bool>>
33              (Expression.And (expr1.Body, invokedExpr), expr1.Parameters);
34      }
35    }
36 }

以下是输出结果:

Name:Jimmy      ,Age:20

用Sql Server Profiler跟踪提交到数据库的语句为:

exec sp_executesql N'SELECT [t1].[F_ID], [t1].[F_Name], [t1].[F_Age]

FROM (

    SELECT ROW_NUMBER() OVER (ORDER BY [t0].[F_ID], [t0].[F_Name], [t0].[F_Age]) AS [ROW_NUMBER], [t0].[F_ID], [t0].[F_Name], [t0].[F_Age]

    FROM [dbo].[T_Test] AS [t0]

    WHERE ([t0].[F_Age] >= @p0) AND ([t0].[F_Name] LIKE @p1)

    ) AS [t1]

WHERE [t1].[ROW_NUMBER] BETWEEN @p2 + 1 AND @p2 + @p3

ORDER BY [t1].[ROW_NUMBER]',N'@p0 int,@p1 nvarchar(3),@p2 int,@p3 int',@p0=20,@p1=N'%J%',@p2=1,@p3=1

一切都很完美,跟我们想象的一样仅取了一条记录

3.但是,我们稍微把代码改一下:

把Main方法中的前三行注释去掉,同时把var Data = GetData(1, 1);注释掉,即

1static void Main(string[] args)
       {
           Expression<Func<T_Test, bool>> _Expression = PredicateBuilder.True<T_Test>();
           _Expression = _Expression.And(t => t.F_Age >= 20).And(t => t.F_Name.Contains("J"));
           var Data = GetData(_Expression, 1, 1);

           //var Data = GetData(1, 2);

           ShowData(Data);          
       }

修改的用意在换一种方法(即Where<T>(Expression))取数据,运行后输出结果跟上一种方式完全相同,而且这种方式可以在调用方法前动态创建需要的查询条件表达式,用法更灵活,但是我跟踪到的sql语句却是:

SELECT [t0].[F_ID], [t0].[F_Name], [t0].[F_Age]
 FROM [dbo].[T_Test] AS [t0]

即采用Where<T>(Expression)方式取数据时,居然先把所有数据取回来,再利用Expression来进行结果筛选以及Skip/Take操作,真是令人大跌眼镜!(或许仅仅是我水平有限,理解不了而已),这样的方式,在单表数据量很大时,性能当然极低。

恳请园子里的哪位linq达人,能解释一二?

知道了最终结果,处理方法自然也就明朗了,当时为了快速解决问题,只能把这类操作回归到最原始的SqlCommand/SqlDataReader方式读取,也许有更好的办法,欢迎大家指点。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏用户画像

MySQL数据库中的5种数据类型简介

MySQL数据库中的5种数据类型是:字符型,文本型,数值型,逻辑型与日期型,以下就是文章的详细内容介绍,希望在你今后的学习中会有所帮助。

9120
来自专栏码洞

Guice快速入门

官方文档里给出的例子又臭又长,我不使用官方的例子,下面我们来写个最简单的HelloWorld

26800
来自专栏行者常至

019.数据交换格式:Json、XML

数据交换格式中,最核心的就是Json和XML。 其中,Json是一种轻量级数据交换格式,XML是一种重量级的数据交换格式。 相比于xml这种数据交换格式来说...

19230
来自专栏C语言及其他语言

【优秀题解】一道题目的递归与非递归两种解法

下面分享大牛一道题的两种(递归+非递归)的解法,供大家学习!也欢迎贡献你的题解! 原题链接:发工资咯 http://www.dotcpp.com/oj/pro...

30780
来自专栏鸿的学习笔记

sql解析的一些计划

关于sql解析的一些概述: 因为最近在研究如何将oracle的sql语句迁移到hive上去,前期是准备写一些udf函数去弥补hive缺失oracle函数...

9120
来自专栏码匠的流水账

聊聊jpa的动态查询

使用springside的DynamicSpecifications,再把mvc的参数映射为SearchFilter,也可以自己实现一套端到端的动态查询。

45010
来自专栏王小雷

SAS学习笔记之《SAS编程与数据挖掘商业案例》(5)SAS宏语言、SQL过程

SAS学习笔记之《SAS编程与数据挖掘商业案例》(5)SAS宏语言、SQL过程 1. 一个SAS程序可能包含一个或几个语言成分: DATA步或PROC步 全程语...

35680
来自专栏风口上的猪的文章

.NET面试题系列[14] - LINQ to SQL与IQueryable

"理解IQueryable的最简单方式就是,把它看作一个查询,在执行的时候,将会生成结果序列。" - Jon Skeet

15610
来自专栏前端侠2.0

c# 的析构以及垃圾回收2、3事!

看书时,自己写的例子代码,了解到几个知识点,记载下来。同时发现自己手写代码的能力比较弱,还是得多写一下。

8110
来自专栏技术博客

编写高质量代码改善C#程序的157个建议[IEnumerable<T>和IQueryable<T>、LINQ避免迭代、LINQ替代迭代]

本文已更新至http://www.cnblogs.com/aehyok/p/3624579.html 。本文主要学习记录以下内容:

12550

扫码关注云+社区

领取腾讯云代金券