前面一篇介绍了EF4的几个常概 念:Context,SSDL,CSDL,Mapping,Entity,EntitySet,Property,Container,Association,Realationship 等。因为这些概念在后面都会用到,所以在读了概述部分以后,就是该了解这几个概念的时候了。至于EF4内部,如何作状态跟踪,多线程管理,数据同步,实体 与实体集的内存模型等这些深入的知识,我想还是到后面再深入学习。
对于新知识的引用,我比较赞成“从一个根(即:概貌)出发,再脉络(即:一些骨架概念和常规操作),最后细节(即:内部实现框架所用到的设计模式)”这样一个过程,我觉得这样不容易迷失于细节中。所谓:不识庐山真面目,只缘身在此山中。哈哈。
好了,闲言少叙,书归正传。这一篇介绍:EF4的查询方法:Query。
=====================================================================
首先,我们有两种方式去查询:
使用前面EFDemo示例中的代码段:
using (NorthwindEntities context = new NorthwindEntities()) { var products = from product in context.Products join category in context.Categories on product.CategoryID equals category.CategoryID where category.CategoryName == categoryName select new { product.ProductName }; foreach (var p in products) { this.lboxProduct.Items.Add(p.ProductName); } }
代码解释:
第1行:使用using 语句块去new出来一个容器类NorthwindEntities 的对象context。使用using语句块的好处是在语句块结束的时候context对象会销毁,其内部保持的与数据库的连接也将会释放。
第3行:使用 var定义一个临时变量。变量的类型交由编译器推断得知(其实由LINQ表达式返回的是IEnumerable<Object>的类型)。赋 值表达式右边的部分就是LINQ内容了。这部分和我们最熟悉的T-SQL很像:里面也有select, from ,where ,join,等关键词。与T-SQL不同的是select的次序在from后面,from的次序一定是在最前面的。这种顺序可能会让人感觉到不自然,不容 易接受,毕竟我们的第一印象是Select * from table where 。。。。不要奇怪:其实,当我们写完T-SQL以后,数据库在执行T-SQL脚本时的顺序是下面这样(数字代表执行先后的顺序由小到大:由1--》8):
上面这种顺序和LINQ的顺序基本上是一致的。所以大家好容易接受了吧。还有一个原因把from子句写到前面:当我们在写完from子句以 后,visual studio就知道我们要查的是哪个类的实体了,所以就可以给我们智能提示了:我们打完“.”后,就能提示里面的可用属性或方法了。如果把from子句放 到后面,Visual studio再智能,也不可能帮我们提示,因为它并不能猜想出我们想要写出的代码内容是什么。对吧?呵呵。
第4行是作一个连接,和T-SQL一样的。
第5行是筛选条件,如果where子句,则相当于无筛选条件,查询并返回所有记录。
第6行 select new { product.ProductName }; 是指生成一个匿名类的对象。这是.NET3.5的新特性,我会在以后专门写.NET的文章来讲这些内容的。这里只要知道是生成一个新对象,并把 product.ProductName 作为实参传进去用于初始化对象就好了。如果直接使用 select product,则是选出product的所有属性。就像T-SQL的select * 一样。上面是为了顺便演示连接(join)的方式,其实更简单的方式也可在按方法二(如下:)
// 方法二:该方法是指定数据源的方式。不须要用 this.lboxProduct.Items.Clear(); if (this.lboxCategory.SelectedValue != null) { // 得到类别ID号 int categoryID = Convert.ToInt32(this.lboxCategory.SelectedValue.ToString()); // 这次直接使用CategoryID作筛选条件,不需要使用连接(join),因为Product中包含有CategoryID。 using (NorthwindEntities context = new NorthwindEntities()) { var products = from product in context.Products where product.CategoryID == categoryID select new { product.ProductName }; // 注意:给控制指定数据源的时候,对DataSource的赋值语句要在DisplayMember和ValueMember赋值之后, // 否则,DisplayMember和ValueMember的赋值不生效。 this.lboxProduct.DisplayMember = "ProductName"; this.lboxProduct.DataSource = products; } }
LINQ语言直观,易懂。推荐使用。
方法二代码中查询语句块
1 var products = from product in context.Products
2 where product.CategoryID == categoryID
3 select new { product.ProductName };
,也可以换成下面的:
1 var products = context.Products.
2 Where<Product>(p => p.CategoryID == categoryID).
3 OrderBy(d => d.ProductName).
4 Select(s => new { s.ProductName});
现在的Where(),OrderBy(),Select()都成了方法名,而不是关键字了,是由静态类Queryable里面的静态方法。而且这 些方法全是扩展方法(关于扩展方法也是.NET3.5的新特性)。使用扩展方法,可以给一个把一个静态方法添加到某一个已有的类中。静态类 Queryable就是把Where(),OrderBy(),Select()等这些静态方法扩展给了IQueryable<T>和 IQueryable<T>的子类们。代码中由容器类NorthwindEntities的对象得到的context.Products 就是ObjectSet<TEntity>,而 ObjectSet<TEntity>,实现了IQueryable<T>接口。所以可以调用这些静态方法。方法参数是 Lambda表达式。Lambda表达式就是一个简化的匿名委托的实现函数:
p => p.CategoryID == categoryID 代表的意思是:
虽然该方法没有名字,但可在被定义的同时,作为实参,传给一个和该匿名方法的签名一样的委托作形参的函数,如 Expression<Func<TSource, bool>> ,where函数原型如下:
1 public static IQueryable<TSource> Where<TSource>( 2 this IQueryable<TSource> source, 3 Expression<Func<TSource, bool>> predicate 4 )
上面代码:this IQueryable<T> source 参数表示:把方法Where<T>()扩展给了IQueryable<T> 类。this后的类型代表扩展的目标类型(就是要把该方法扩展给那个类,也就是该类的实例成员就可以像调用自己类内定义的方法一样调用该方法,并且在调用 where()的时候,参数列表就变成了where(Expression<Func<TSource,bool>> predicate),就是要不包含this所修饰的参数。因为this修饰后的参数是指扩展目标类型。this用于表明该扩展类型的对象是放到该函数名 前面使用的,并不是当作函数参数传到函数内部。)
Expression 类的定义是:public sealed class Expression<TDelegate> : LambdaExpression
Func<>是FCL(Framework Class Library)中定义的委托。在自己的程序中可以直接使用。在构造Expression类的时候,用该Func委托作形参。而实参就是这个Lambda表达式:
p => p.CategoryID == categoryID 。
其实,在使用的时候,直接以函数的形式去使用,按Lambda表达的方式去传值就行了。很简单,很好用的,不必考虑这些复杂的委托的实例与调用是如何封装的。函数名字也和我们之前使用T-SQL的关键字一样。
注意:
1. 然我们写代码的时候,是调用完一个函数后再调用另一个函数。但是在代码执行的时候,是把这些函数组合成一个sql语句后,一并执行的。
2. 延迟执行:下面这段代码中,var categories 的赋值语句执行后,var其实并没立即从数据库中查询数据,而只是保存一条查询命令。真正执行查询的时刻是把categories作为集合遍历时,或作为 数据源时。(如果你想验证的话,不要试图设置断点,去查看categories变量的值,因为你当你用断点去调试时,visual studio会告诉你“如果你要查看它的查询结果,将进行数据查询”所以,你是无法通过断点来查categories变量的值。但是你可设置断点,单步执 行,然后观察数据库侦测工具Data profile viewer的结果。由于我家用上电脑没有装这个工具,所以无法截图了。但是大家只要心里清楚执行是什么时候就ok了。)
1 using (NorthwindEntities context = new NorthwindEntities()) 2 { 3 var categories = from category in context.Categories 4 select new { category.CategoryID,category.CategoryName }; 5 6 foreach (var c in categories) 7 { 8 this.lboxCategory.Items.Add(c.CategoryName); 9 } 10 }
这就是所谓的延迟执行。如果你想要它立即执行查询可以用下面的方法:
1 using (NorthwindEntities context = new NorthwindEntities()) 2 { 3 var categories = (from category in context.Categories 4 select new { category.CategoryID,category.CategoryName }).ToList(); 5 6 foreach (var c in categories) 7 { 8 this.lboxCategory.Items.Add(c.CategoryName); 9 } 10 }
上面代码第4行使用了ToList()方法,使其立即执行。同样这样的方法还 有:ToDictionary(),First(),ToArray(),Count(),ToLookUp(),Average()等,只要你输入 “.”以后查看下智能提示就知道有哪些函数可用了。而这些函数的用法通过函数名字基本上也就非常清楚了。呵呵,如果不确定的话,直接可以到谷歌搜下,很简 单的方法。
在LINQ结合EF4的使用就是:LINQ to Entity了,除此以外还有LING to SQL,LINQ to XML, LINQ to Dataset等,以后专门在LINQ的文章中讨论,在这里讨论太多的话,就跑题了,呵呵。
休息咯!!晚安,各位。。