深入浅出谈LINQ
 

2009-04-30 作者:Peak Wong 来源:IT专家网

 

引子:案例

某君被邀为一超市设计数据库,用来存储数据。该君根据该超市中实际出现的对象,设计了Customer, Employee,Order, Product等表,用来保存相应的客户,员工,订单,货品等。太好了,该君很有oo的思想吗。

如果,你被要求用类及对象,来描述该关系型数据,你该如何做呢?在linq推出之前,ADO.NET被用来做数据访问层。而后,程序员需要自己去编写事务逻辑层中所出现的类。比如,Customer, Employee,Order, Product等。然后,程序员组装所需的sql语句,通过ADO.NET,将返回的记录,来初始化Customer等类的对象。在这里,你已经自己动手将Customer表和Customer类关联了起来。从Linq To Sql的设计来看,它主要是为了解决data!=objects 的问题而产生的。现在,有了Table和Class之间的映射,数据和对象之间就可以有一个一一对应的关系了。

在Linq To Sql之前,在java领域有Hibernate,在net领域有NHibernate技术,来实现object/relational 持久和查询服务。无论是Hibernate还是NHibernate,其配置复杂,上手时间长,已经不能适应快速开发的需要。而Linq To Sql的推出,恰恰弥补了它们的缺点,彻底降低了程序开发门槛。

LINQ,语言级集成查询(Language Integrated Query)

经过了最近 20 年,面向对象编程技术( object-oriented (OO) programming technologies )在工业领域的应用已经进入了一个稳定的发展阶段。程序员现在都已经认同像类(classes)、对象(objects)、方法(methods)这样的语言特性。考察现在和下一代的技术,一个新的编程技术的重大挑战开始呈现出来,即面向对象技术诞生以来并没有解决降低访问和整合信息数据( accessing and integrating information )的复杂度的问题。其中两个最主要访问的数据源与数据库和 XML 相关。

LINQ 提供了一条更常规的途径即给 .Net Framework 添加一些可以应用于所有信息源( all sources of information )的具有多种用途( general-purpose )的语法查询特性( query facilities ),这是比向开发语言和运行时( runtime )添加一些关系数据( relational )特性或者类似 XML 特性( XML-specific )更好的方式。这些语法特性就叫做 .NET Language Integrated Query (LINQ) 。

LINQ在开发中的地位

DLINQ *.dbml文件该属于哪一层,的确Linq to Sql存在问题,DLINQ中,虽然可以在语言层级定义查询逻辑。但是依然没有将数据库持久化数据映射为领域对象,所以还是一种针对数据库的编程模型。

LINQ是微软将在C# 3.0中将推出的语言集成查询技术,许多人也将LINQ叫做微软的ORM。LINQ不仅仅针对关系数据库,它只是解决一个问题Data!=Object,也就是说他解决的就是Data=Object.。作为底层框架,可以为ORM实现提供更强大的基础平台。在Linq之前在.net领域最流行的框架就是Nhibernate。是不是在LINQ之后Nhibernate就要消失呢?答案自然是否定的。

这里有个帖子Microsoft LINQ + NHibernate,在那篇帖子中,作者列举了三大原因:

在DLINQ中,虽然可以在语言层级定义查询逻辑。但是依然没有将数据库持久化数据映射为领域对象,所以还是一种针对数据库的编程模型。而Nhibernate则可以直接将关系数据映射为领域模型,这是DLINQ的主要问题。

DLINQ不支持继承类的映射。

Nhibernate已经提供了许多帮助进行领域面向对象建模的特征。而DLINQ目前还无法拥有。

而LINQ + NHibernate的好处则是:类型安全的查询,并且能使用智能提示功能!这样可以不用学习HQL了。能获得所有NHibernate所拥有的能力。假如你已经从数据库中查询出了一个数据集,那么可以使用LINQ来进行过滤,排序和分页操作。

从原理上来说,DLINQ是将Lambda查询表达式解析为SQL语句:DLINQ => SQL,而NHLINQ则是将Lambda查询表达式解析为HQL语句:NHLINQ => HQL。

开源社区的智慧是无穷的,并且和微软也不是你死我活的关系。反倒应该是一种互相补充的关系。开源社区可以做一个.net framework这样大的东西Mono,开源社区的这些闪烁的创造性思维火花也为沉闷的软件开发带来了很多灵气。也保持了我们这些没有多少机会能做创造性工作的普通程序员的一些创作热情! LINQ没有提供在多层应用程序中应用的功能,这一个功能可以通过序列化Expression Tree 来解决,Expression Tree 就可以通过Remoting或者WCF发布出去,正好有一个开源项目解决这个问题:http://www.codeplex.com/interlinq。这个项目也包含着上面所说的NHibernate.Linq.

Implementing Linq for NHibernate: A How To Guide - Part 1

LINQ to SQL集成到应用程序中需考虑的一些问题

1、LINQ to SQL集成到应用程序中需考虑的一个问题, 到底应该返回IQueryable还是IQueryable? 或许这个列表还应该继续扩展为T, List, 对于Business Layer来说, 到底应该选择哪一种?

2、需要一个分页功能; 到这一步的时候我又有几个选择, 利用LINQ to SQL可以执行自定义存储过程的功能, 完全自己写, LINQ to SQL本身已经有API提供了分页功能了,不过只有排序或包含标识列的查询中支持Skip方法, 我有什么理由放弃, 除非性能真的到了非常Critical的时候, 看看下面的分页API, 多么简单:

return q.Skip((currentPage - 1) * pageSize).Take< Order >(pageSize)

生成的T-SQL语句:

 SELECT [t2].[OrderID], [t2].[OrderNumber], [t2].[OrderName], [t2].[DateSubmitted], [t2].[SubmittedBy], ([t2].[LastName] + @p2) + [t2].[FirstName] AS [SubmittedUserName]
 FROM (
 SELECT TOP (10) [t0].[OrderID], [t0].[OrderNumber], [t0].[OrderName], [t0].[SubmittedBy], [t0].[DateSubmitted], [t1].[FirstName], [t1].[LastName]
 FROM [dbo].[Order] AS [t0]
 INNER JOIN [dbo].[Users] AS [t1] ON [t1].[UserID] = [t0].[SubmittedBy]
 WHERE ([t0].[NextProcessedBy] = @p0) AND ([t0].[CurrentState] = @p1)
 ) AS [t2]
 – @p0: Input NVarChar (Size = 3; Prec = 0; Scale = 0) [jlv]
 – @p1: Input NVarChar (Size = 3; Prec = 0; Scale = 0) [New]
 – @p2: Input NVarChar (Size = 2; Prec = 0; Scale = 0) [, ]

DLINQ生成的SQL语句是利用TOP和嵌套子查询, 这种方法已经被证明是比较高效的做法(相比于临时表的做法), 所以完全有理由可以一试.到这里, List, IQueryable, IQueryable都没有任何问题.

3、需要一个动态排序功能, 这里List的局限性出来了, 传统的做法可能需要用一个dynamic参数来传递需要排序的列然后到SP当中来执行, 但我们已经不打算使用SP了, 也没有动态sql语句, 所有的东西都是强类型的, 然后有LINQ to SQL在运行时来帮我们转换为T-SQL语句。首先List的话, 我们不知道到底哪个字段要排序, 如果使用字符串作为参数的话, 例如放一个string sortBy作为方法的参数, 那么在方法体内就需要做if…else或者switch的判断, 而且还要考虑倒序还是正序的排序要求, 而且你还要hard code,很明显麻烦来了.然而如果使用IQueryable却可以很好的解决所有的这些问题. 但是IQueryable不能跨assembly, 一旦跨了assembly的话, 你无法使用var来引用匿名类里面的property, 绑定到control是没有问题的, 但是客户端的动态查询却成了问题, 因为你根本不知道匿名类是什么. 那么选择IQueryable, 我们选择返回IQueryable给客户端, 分页/排序都没有任何问题.

LINQ to SQL 辅助工具

当你在项目中使用LINQ to SQL的时候, 有一个事情是必须要非常注意的, 那就是要关心一下LINQ to SQL帮你生成的SQL语句, 尤其在你还不是非常熟悉LINQ to SQL的语法, API的时候, 这点是非常重要的。

一、DataContext本身有提供Log属性来将LINQ to SQL生成的SQL语句格式化并输出到控制台窗口, 通常是这样:  DataContextInstance.Log = Console.Out;

这个用法对于控制台, Winform应用程序没有问题, 但对于ASP.NET这类的非控制台应用程序不起作用,asp.net可以选择将Log信息直接发送到Debug的输出窗口, Kris Vandermotten 已经创建好了一个这个工具类, 你只要使用这样的语法:

MyDataContext db = new MyDataContext();

db.Log = new DebuggerWriter();

就可以在启动Debug调试的时候将Log信息发送到Debug的output窗口了, 非常方便, 强力推荐你使用.关于这个工具类的更多细节, 点这里查看 。

二、可以看到所能生成的 SQL 语句了,Debug过程中想查看结果怎么办,大牛ScottGu为我们准备了个好东东,参看http://weblogs.asp.net/scottgu/archive/2007/07/31/linq-to-sql-debug-visualizer.aspx

工具下载:http://www.scottgu.com/blogposts/linqquery/SqlServerQueryVisualizer.zip

安装方法

1. 关闭 VS2008。

2. 将压缩包中的 SqlServerQueryVisualizer.dll 拷贝到 \Program Files\Microsoft Visual Studio 9.0\Common7\Packages\Debugger\Visualizers。

3. 重启 VS2008 即可。

Linq语法本质

简单一点的说,Linq的本质=语法糖(自动属性)+语法糖(初始化器)+语法糖(具有隐式类型的局部变量)+语法糖(匿名类型)+语法糖(扩展方法)+语法糖(更强的类型自动推断)+语法糖(Lambda表达式)+语法糖(编译为Lambda表达式树)+类库(Lambda表达式树)+类库(Linq to Sql)+类库(Linq to Object)+类库(Linq to Xml)+类库(Linq to 其他扩展),看一下下面的实例:

1、自动属性

class   Class
{
        //C#2.0   属性  
        //private   int   _id;
        //public   int   ID
        //{
        //         get   {
        //                 return   _id;
        //         }  

        //         set  
        //         {
        //                 _id   =   value;
        //         }
        //}  

        //C#3.0   属性   可以给get   set加访问修饰符
        public   int   ID   {   get;   private   set;   }
        public   string   Name   {   get;   set;   }
        public   Class(int   id)
        {
                //加了private之后的属性只能在类的内部访问
                this.ID   =   id;
        }
}  

本质:和原来的属性没啥两样 ,简化了语法而已。

对Linq的意义:无。

2、初始化器

private   static   void   Initializer()
{
        //C#2.0  
对象初始化
        //Class   c   =   new   Class(1);
        //c.Name   =   "
终极一班";
        //C#3.0  
对象初始化器
        Class   c   =   new   Class(1)   {   Name   =   "
终极一班"   };
        //C#2.0  
集合初始化
        //ClassCollection   list   =   new   ClassCollection();
        //list.Add(c);
        //C#3.0  
集合初始化器
        ClassCollection   list   =   new   ClassCollection  
        {
                new   Class(1)   {   Name="
终极一班"},
                new   Class(2){Name="
终极二班"}
        };
        foreach   (Class   item   in   list)
        {
                Console.WriteLine(item.ID   +   "   "   +   item.Name);  

        }
}  

相关的班级集合类代码:

  class ClassCollection : List 
  { }

本质:和原来的构造函数初始化或构造后通过属性初始化没啥两样 ,简化了语法而已。

对Linq的意义:和匿名类型结合起来构造查询结果集合里面的新元素类型。

3、具有隐式类型的局部变量

private   static   void   Var()
{
        var   i   =   1;//  
编译过后的结果实际是   int   i=1;   var并不是动态变量,它的类型实际上是c#编译器通过上下文推断是int
        //var   i   =   DateTime.Now;   //
编译不过,和JavaScript不一样
        var   d   =   DateTime.Now;//=
后面支持各种类型
        var   a   =   new   int[]   {   1,   2,   3   };//var
也支持数组
        foreach   (var   item   in   a)//item
的类型通过C#编译器推断得知是int
        {
                Console.WriteLine(i);
        }

//var   x;                                   //  
错误,没有用来推断类型的初始化器
//var   y   =   {   1,   2,   3   };     //  
错误,不允许使用集合初始化器
//var   z   =   null;                 //  
错误,不允许出现空类型
}  

本质:var并非动态类型 ,C#仍然是静态语言,引入var方便我们写代码了,可以不管“=”后面的赋值表达式类型了,由编译器自己去推断生成对应类型了。

对Linq的意义:可以自动推断出Linq查询返回的集合类型。

4、匿名类型
private   static   void   AnonymousType()
{
        var   v   =   new   {   Name   =   "
张三",   Sex   =   true   };//无须显示声明一个类,而且在初始化器里面可以获取上下文的变量——闭包
        Console.WriteLine(v.Name);
}  

本质:有了匿名类型后我们不需要显示的声明一个类型了,这个类型由C#编译器自动生成,而且利用了初始化器和var的新特性

对Linq的意义:和初始化器结合起来构造查询结果集合里面的新元素类型。

5、扩展方法

比如我们现在想给int类型增加(扩展)一个方法,判断一个整数自身是否偶数,我们期望的语法是这样的:

private   static   void   ExtendMethod()
{
        int   i   =   2;
        Console.WriteLine(i.IsEven());
}

注意原来int原来是没有IsEven()这个方法的,要实现这个方法,必须写一个静态类和一个静态方法。

static   class   MyExtention
      {
              public   static   bool   IsEven(this   int   num)//this  
表示针对int的实例和索引器的this的含义是一样的,int表示给int这种类型进行扩展
              {
                      return   num   %   2   ==   0;
              }  

}

本质:编译i.IsEven()的本质是C#编译器生成了了MyExtention.IsEven(i)的代码,实际上仍然没有破坏类型的结构,并不是真的象语法那样平白无故给int增加了一个IsEven()方法,和设计模式里面的Visitor模式动态注入方法还是有区别的。

对Linq的意义:用来对集合类型扩展不同的查询方法。

6、Lambda表达式和Linq查询

接下来我们通过一个例子来看一下Lambda表达式和Linq查询的关系:我们现在想给ClassCollection增加一个过滤方法,方法的目的是能够过滤返回班级名称为“终极一班”的集合来。

0) 首先给MyExtention增加这么一个静态方法:  
public   static   ClassCollection   Filter(this   ClassCollection   classes)
{
        var   newlist   =   new   ClassCollection();
        foreach   (var   item   in   classes)
        {
                if   (item.Name=="
终极一班")

                {
                        newlist.Add(item);
                }
        }
        return   newlist;
}

private   static   void   LambdaLinq()
              {
                      var   classes   =   GetClasses();
                      //var   students   =   GetStudents();
                      //0  
原始版本
                      var   result   =   classes.Filter();  

                    foreach   (var   item   in   result)
                  {
                        Console.WriteLine(item.ID+   "   "   +   item.Name);
                  }  

}  

相关的工厂方法:  

static   ClassCollection   GetClasses()
{
        return   new   ClassCollection{
        new   Class(1){   Name   =   "
终极一班"},
        new   Class(2){   Name   =   "
终极二班"},
        };
}  

1) 现在需求发生了变化,需要上面的红色部分需要发生变化,也就是说我们希望这个查询条件可以在我们调用Filter方法的时候动态的指定,这时候我们可以把这个变化封装成一个接口,当然还可以封装成一个委托,这是.net的非常好用的独有特性,委托的最直接的作用可以把一个具体的方法引用封装成一个变量传递。好,开始变形!

delegate   bool   FilterHandler(Class   c);   //注意这个要放到namespace下面,不要放到Program类里面

public   static   ClassCollection   Filter(this   ClassCollection   classes,FilterHandler   f)
{
        var   newlist   =   new   ClassCollection();
        foreach   (var   item   in   classes)
        {
                if   (f(item))

                {
                        newlist.Add(item);
                }
        }
        return   newlist;
}

static   bool   F(Class   c)
{
        return   c.Name   ==   "
终极一班";
}  

private   static   void   LambdaLinq()
              {
                      var   classes   =   GetClasses();
                      //   C#1.0  
使用委托封装过滤条件
                      FilterHandler   f=new   FilterHandler(F);
                    var   result   =   classes.Filter(f);
                    foreach   (var   item   in   result)
                  {
                        Console.WriteLine(item.ID+   "   "   +   item.Name);
                  }  

}  

我们声明了一个委托FilterHandler,只要满足这个委托的方法我们都可以传递给Filter方法,这样就实现了动态的改变查询条件的目的,F方法内部可以是任意的查询条件比如return c.Name != "终极一班";同时我们不需要改变Filter方法内部稳定的部分。 2) c#2.0里面也支持直接把一个方法传给一个委托,但本质上也是编译器把方法转换成了一个委托,例如上面:

private   static   void   LambdaLinq()
              {
                      var   classes   =   GetClasses();
                      //   C#2.0  
直接传递方法
                    var   result   =   classes.Filter(F);
                    foreach   (var   item   in   result)
                  {
                        Console.WriteLine(item.ID+   "   "   +   item.Name);
                  }  

}

3) C#2.0里面有个新特性,叫匿名方法,我们可以直接传递匿名方法:

private   static   void   LambdaLinq()
              {
                      var   classes   =   GetClasses();
                      //   C#2.0  
传递匿名方法
                    var   result   =   classes.Filter(delegate(Class   c)   {   return   c.Name   ==   "
终极一班";   });
                    foreach   (var   item   in   result)
                  {
                        Console.WriteLine(item.ID+   "   "   +   item.Name);
                  }  

}  

好,变形到这里,我们发现这个匿名其实不仅仅可以给我们带来不用给方法命名的好处,在这个方法内部我们还可以使用外部上下文环境的变量成员,这个特性也叫“闭包(Closure)”,JavaScript也支持这个特性,比如:

private   static   void   LambdaLinq()
              {
                      var   classes   =   GetClasses();                    

                      string   className   =   "
终极一班";
                      //   C#2.0  
传递匿名方法

                      var   result   =   classes.Filter(delegate(Class   c)   {   return   c.Name   ==   className;   });
                  foreach   (var   item   in   result)
                  {
                        Console.WriteLine(item.ID+   "   "   +   item.Name);
                  }  

}  

4) 大家发现没有,上面的语法还是有点拖沓,伟大的Microsoft又给我们提供了一种更简洁的写法(我怎么说了个“又”呢?^_^),这也就是我们所说的Lambda表达式了:  

private   static   void   LambdaLinq()
              {
                      var   classes   =   GetClasses();


                    string   className   =   "
终极一班";  

                    //4   C#3.0   Lambda
表达式
                    var   result   =   classes.Filter(c=>   c.Name   ==   className);
                    foreach   (var   item   in   result)
                  {
                        Console.WriteLine(item.ID+   "   "   +   item.Name);
                  }  

}

“=> ”左边的就是我们上面匿名方法的参数列表,右边的是方法里,实际上lambda表达式也可以写成如下形式:

  Class c => c.Name == className
  (Class c) => c.Name == className
  (Class c) => {return c.Name == className;}
  (x,y)=> x+y;//多参数

等等,函数的返回类型也是由编译器根据"=> "右边的表达式自动推断出来的。

而且需要提到的是由于Filter是扩展方法的缘故,而且Filter方法返回类型是ClassCollection,所以可以无限扩展下去,例如

var result = classes.Filter( c => c.Name == className).Filter(c=> c.ID> 1);

这就是扩展方法的魅力所在!

5) 实际上不知不觉,我们已经实现了Linq里面的一个Where功能了

我们现在导入命名空间 

using System.Linq;

然后会发现classes这个实例会增加了很多扩展方法例如Where,OrderBy,这些方法实际上就是一些给实现了IEnumerable接口的类型的扩展方法,说白了就是针对集合类型的一些相关方法,比如过滤、排序、合并、分组等方法,这些方法的返回类型依然是IEnumerable(大家可以把光标移动到Where方法上,然后调用“转到定义”去看看这些方法的定义就明白了)当然这些方法都离不开我们的Lambda表达式做参数。  

private   static   void   LambdaLinq()
              {
                      var   classes   =   GetClasses();
                      string   className   =   "
终极一班";

                    //5   C#3.0
里面的Where扩展方法(专门给实现了IEnumerable接口的类做扩展)
                      var   result   =   classes.Where(c   =>   c.Name   ==   className);
                    foreach   (var   item   in   result)
                  {
                        Console.WriteLine(item.ID+   "   "   +   item.Name);
                  }  

}  

我们还可以这样可以无限扩展下去:var result = classes.Where(c => c.Name == className).OrderBy(c=> c.ID);

6) 这样写针对IEnumarable类型的查询其实已经不错了,微软觉得还不过瘾,又提供了我们传说中的Linq查询表达式(又是“又”?!)

private   static   void   LambdaLinq()
              {
                      var   classes   =   GetClasses();
                      string   className   =   "
终极一班";  

                    //6   Linq
查询表达式

                  var   result   =   from   c   in   classes   where   c.Name==className   orderby   c.ID   select   c;
                        foreach   (var   item   in   result)
                        {
                                Console.WriteLine(item.ID+"   "+item.Name);
                        }

}  

到这时候你还认识原来的扩展方法吗?所以说语言的抽象确实很好用,和人的思维很接近,但是我们还是要看到它的本质,其实它的本质都是面向对象的一些东西,并没有创造出一些什么新的东西来,这样我们才可以真正理解语言。

7) 最后一个稍微复杂一些的Linq查询,就是班级和学生结合的一个连接,连接的条件是班级的id和学生的所属班级id,然后生成一个新的集合,这个集合里面的元素成员包括班级名称和学生名称。涉及到的相关类如下:

class   Student
        {
                public   int   ID   {   get;   set;   }
                public   string   Name   {   get;   set;   }
                public   int   ClassID   {   get;   set;   }
        }
        class   StudentCollection   :   List <Student>
        {
        }

获取学生集合的工厂方法:  

static   StudentCollection   GetStudents()
{
        return   new   StudentCollection()
        {
                new   Student   {   ID=1,Name="
大东",   ClassID=1},
                new   Student{ID=2,Name="
亚瑟",ClassID=1},
                new   Student   {   ID=3,Name="
小雨",   ClassID=1},
                new   Student{ID=4,Name="
雷克斯",ClassID=1},
                new   Student{ID=2,Name="
张三",ClassID=2},
                new   Student   {   ID=3,Name="
李四",   ClassID=2},
                new   Student{ID=4,Name="
王二麻子",ClassID=2}
        };
}  

 

private   static   void   LambdaLinq()
              {
                      var   classes   =   GetClasses();

                      var   students=GetStudents();  

                    //7   Linq
查询表达式   Join

var   result   =   from   c   in   classes
                          join   s   in   students   on   c.ID   equals   s.ClassID
                          select   new   {   ClassName   =   c.Name,   StudentName   =   s.Name   };//
匿名类型和初始化器新特性的使用
//var   result   =   classes.Join(students,c=> c.ID,s=> s.ClassID,(c,s)=> new   {ClassName=c.Name,StudentName=s.Name}
foreach   (var   item   in   result)
{
        Console.WriteLine(item.ClassName   +   "   "   +   item.StudentName);//
注意元素的属性成员已经改变
}  

}

Linq To Sql集成数据库语言的优劣

1、Linq To Sql的优点

在Linq To Sql推出之前,我们只是把sql语句形成一个string,然后,通过ado.net传给sql server,返回结果集.这里的缺陷就是,如果你sql语句写的有问题,只有到运行时才知道.而且并不所有的人都懂数据库的。Linq To SQl 在一切围绕数据的项目内都可以使用。特别是在项目中缺少SQL Server方面的专家时,Linq To SQl的强大的功能可以帮我们快速的完成项目。Linq To SQl的推出,是让大家从烦琐的技术细节中解脱出来,更加关注项目的逻辑。Linq To Sql的出现,大大降低了数据库应用程序开发的门楷,它实质是事先为你构架了数据访问层,势必将加快数据库应用程序的开发进度。Linq To Sql解放了众多程序员,让他们的把更多的精力放到业务逻辑以及code上,而不是数据库。对于初学者来讲,Linq To Sql可以让他们迅速进入数据库应用程序开发领域,节约了培训成本。

Linq To SQl 的实现,是在ado.net和C#2.0的基础上的。它通过自动翻译sql语句,并把结果集创建成对象并返回。这里我们可以看出,发送到SQL Server端的sql语句是Linq To Sql自动生成的。这对不懂sql的人来说,无疑是个福音。第二,Linq To Sql语句是在编译期间就做检查的。而不是运行时检查。这样,那里出了问题,可以及时更改,而不是到了运行时才发现问题。第三,Linq To Sql是针对对象操作的,更符合今天的oo呼声。 在Linq To SQl 之前,在Java领域有Hibernate,在net领域有NHibernate技术,来实现object/relational 持久和查询服务。那和NHibernate比起来,它又有那些优势呢.第一,影射代码自动生成。VS2008提供了SqlMetal和OR Designer两个工具来完成此步骤。而在NHibernate中,你不得不自己手工写。第二,影射代码有更多的选择.NHibernate只能把数据库的信息配置在一个xml中,而Linq To Sql有两种方式,一个是放到XML中,我们称为Externl Mapping, 再一种就是以Attribute的形式,存在于各个property中。当然,笔者本人并没有使用过NHibernate,只是从资料上得到这些消息,所以无法给出更多的比较。

2、Linq To Sql的缺点

很久前,有个网友问到这么一个问题。他在界面上有个DataView,里面绑定了一些Column,然后他勾选那一列就按某列排序。其传回的参数是列的名字。然后问我该怎么用Dlinq 来实现。

在以前拼接Sql语句的年代,这个很简单,一个" order by " + string,想按什么排就按什么来排。而现在dlinq是用是一个对象的属性,已经不可能拼接了。我当时给他的答案是这样的。

private   void   Methods(string   orderId)  
  {  
     var   q   =   db.Customers.Select(c=> c);  
    
     switch(orderId)  
     {  
     case   "ID":  
     q   =   q.OrderBy(c=> c.ID);  
     break;  
     case   "Name":  
     q   =   q.OrderBy(c=> c.Name);  
     break;  
     default:  
     break;  
     }  
    
     var   result   =   q.ToList();  
  }

我那时也没有想出一个更好的方案来。而后告诉他去查下Compiled Query,说不定可以找到更方便的。后来我才在这个例子中,看到更方便的。

var   query   =  
     db.Customers.Where("City   ==   @0   and   Orders.Count   > =   @1",   "London",   10).  
     OrderBy("CompanyName").  
     Select("New(CompanyName   as   Name,   Phone)");

在这里OrderBy直接接收的就是列的名字。再仔细一看,好像Where里是Linq的语句哎,那OrderBy也该是linq语句。后来,我把CompanyName换成小写的,一跑过了。莫非真的是列的名字?出个难题吧。找了一个列名,是带空格的,重新来建这个工程.一跑,错了!把列名用中国扩号扩起来了,也是错了。咳,只是动态构造Expression Tree而已,永远都不能直接接收列的名字。这个例子看着是很简单,可不知道你有没有注意到它有一个80多k的Dynamic.cs文件。更有意思的事情是,它的名称空间是System.Linq.Dynamic.看样子,ms本来是打算把它加在.net3.5中吗.不晓得为什么放到了例子中了。这个名称空间下,其主要内容就是动态构造Expression Tree. 和Linq To Sql进阶系列(七)动态查询 一文中的方法类似。只是,它还包含了解析字符串部分.

从上面那个例子中,可以看出,Linq To Sql在这种动态构造语句时,比拼接sql麻烦很多。在Linq To Sql进阶系列(七)动态查询 一文中,笔者极力推荐使用object的查询。这符合Linq To Sql的设计原则。因为,它主要是为了解决data!=objects 的问题而产生的.它所有的操作均针对object,那就让我们使用object的查询吧。 当然,依然会有人习惯拼接字符串.我并不认为这是个坏毛病。只是有点不符合oo思想而已。事实上,在Linq To Sql中,你依然可以使用拼接字符串的形式,而不使用它提供的Query Expression. 它提供了这么两个接口,一个是,db.ExecuteQuery(string sql); 另一个是,db.ExecuteCommand(string sql);这两个函数都是直接接收 sql语句的.习惯拼接的人,依然可以调用它们来实现你的程序。特别是第一个,其返回的就是一个对象的集合,多少还是有点oo思想的。

看下面的例子:

var   products   =   db.ExecuteQuery(  
     "SELECT   [Product   List].ProductID,   [Product   List].ProductName   "   +  
     "FROM   Products   AS   [Product   List]   "   +  
     "WHERE   [Product   List].Discontinued   =   0   "   +  
     "ORDER   BY   [Product   List].ProductName;"  
     ).ToList();

它返回的就是product的集合。而不是什么dataset和datatable之类的。这里,你可以大胆的使用该函数继续拼接你的sql吧,再看下面这个:

  db.ExecuteCommand("UPDATE Products SET UnitPrice = UnitPrice + 1.00");

它在做批处理的时候,你想不用它,都不行.当然,你如果觉得性能不是问题的话,那就用submitchange方法来做更新好了。简单明了的说,Linq To Sql在批处理更新的时候,SubmitChange只会一个个的更新。浪费时间资源.而这个接口,恰好满足了批处理更新或删除的问题。从这两个例子,我们可以看出。没有任何方案是万能的。各有各的优点。

3、Linq To Sql的性能

Linq 的性能已经被好多人提及.Linq To Object 的性能大家讨论的比较多些.它确实比自己实现的查找要慢.但是当数据量特别大时,更多是时间是花在分配虚拟内存上了,那么他们的差别就不是那么明显了。Linq To Sql是又如何提升性能的?第一,采用延迟加载(deferred loading)技术。语句是声明了,但是并不立即执行,而是在真正需要的时候才执行。第二,采用缓存技术。已经取到内存的数据,再依次提取时,会先从缓存中返回,而不是再次访问数据库。当然,笔者建议,不要对象的时候,没有必要使用Linq To Sql.比如,只是填充DataView或DataGrid时,返回dataset或datatable要比用Linq To Sql实现的快很多。

结论:各种技术都有其自身的优点和缺点。使用什么样的技术,完全在于项目对性能和开发进度的要求,以及程序员自身的爱好有关。扬长避短,才是正道。

后记

个人感觉,linq的最大好处在于智能提示,保证你不会拼错。

如果微软在开发环境中集成了sql语法字段的智能提示功能,那么对于写熟了sql 的人来讲,写sql也没有什么不方便的地方。

但是事物是发展的,也许等linq支持的数据库种类多起来时,我们还会发现linq的另一个好处,那就是在某个层面上将不规范的sql进行了统一(只与linq打交道,生成的sql依据连接数据库不同而不同)。

另外,对于很多应用场合,内存数据的查询,排序,分组等功能需要写大量代码来实现,linq之后,会有统一的方法来处理这些问题,这也是提高生产率的一个方面。

至于效率问题,小的应用系统,linq不会很慢,就算是慢点,硬件也完全可以在另一方面进行了提升。对于大的系统,整个的运行速度,并不是完全取决于数据库提交过程,也不会因为生成表达式过程而受到太大影响。所以开发者应当从另外的角度来考虑问题,而不是执着于那几ms的差异。正如存贮过程与直接在程序中写定的sql之间的区别一样,这种区别大多数情况下只是理论上的,并没有哪个系统因为应用了存贮过程而狂奔,也没有哪个系统因为没有用存贮过程而当机。

最后一句:看好linq,相信开发速度的提升,是构建更复杂更智能应用的前提。


火龙果软件/UML软件工程组织致力于提高您的软件工程实践能力,我们不断地吸取业界的宝贵经验,向您提供经过数百家企业验证的有效的工程技术实践经验,同时关注最新的理论进展,帮助您“领跑您所在行业的软件世界”。
资源网站: UML软件工程组织