2.4 EF查询相关
2.4.1 IQueryable与IEnumberable接口的区别
IQueryable接口与IEnumberable接口的区别是:IEnumerable<T>泛型类在调用自己的SKip和Take等扩展方法之前数据就已经加载在本地内存里了,而IQueryable<T>是将Skip、take这些方法表达式翻译成T-SQL语句之后再向SQL服务器发送命令,也就是延迟在要真正显示数据的时候才执行。IQueryable其实继承了IEnumberable。
在Linq To EF中使用IEnumberable与IQueryable的区别要用到SQL Server Profiler工具,那么我们什么时候使用IQueryable、什么时候使用IEnumberable呢?通常,在数据访问层都是使用IQueryable,因为可以把对数据的加载延时到业务逻辑层来处理。很多时候,业务层调用数据访问层的方法时并不要求马上从数据库中加载数据再存到内存中。这时在业务逻辑层依旧可以使用延时加载,当真正需要加载数据的时候,再在业务逻辑层把IQueryable转换成IEnumberable把数据加载进来就可以了。
2.4.2 LINQ To EF
这里使用northwnd.mdf数据库。
1.简单查询
var result = from c in Entities.Customer select c;
2.条件查询
普通LINQ写法:
var result = from c in Entities.Customer where c.Gender ==‘w' select c;
Lambda表达式写法:
var result = Entities.Customer.Where<Customer>(c =>c.Gender==‘w');
3.排序分页
IQueryable<Customers> cust10 = (from c in customers orderby c.CustomerID select c).Skip(0).Take(10);
4.聚合
可使用的聚合运算符有Average、Count、Max、Min和Sum。
using (var edm = new NorthwindEntities()) { var maxuprice = edm.Products.Max(p => p.UnitPrice); Console.WriteLine(maxuprice.Value); }
5.连接
可以使用的连接有Join和GroupJoin方法。GroupJoin组连接等效于左外部连接,返回第一个(左侧)数据源的每个元素(即使其他数据源中没有关联元素)。
using (var edm = new NorthwindEntities()) { var query = from d in edm.Order_Details join order in edm.Orders on d.OrderID equals order.OrderID select new { OrderId = order.OrderID, ProductId = d.ProductID, UnitPrice = d.UnitPrice }; foreach (var q in query) Console.WriteLine("{0}, {1}, {2}", q.OrderId, q.ProductId, q.UnitPrice); }
EF不支持复杂类型(如实体)的直接检索,只能用简单类型,比如常用的标量类型string、int和guid。在执行这类查询时,会引发NotSupportedException异常,并显示消息“无法创建‘System.Object’类型的常量值”。
如下代码将会引发异常:
using (var edm = new NorthwindEntities()) { Customers customer = edm.Customers.FirstOrDefault(); IQueryable<string> cc = from c in edm.Customers where c == customer select c.ContactName; foreach (string name in cc) Console.WriteLine(name); }
在上面的代码中,由于customer是引用类型而不是Int32、String、Guid的标量类型,因此在执行到where c==customer这个地方时会报错。
6.分组查询
var query = from c in db.Categories join p in db.Products on c.CategoryID equals p.CategoryID group new {c, p} by new {c.CategoryName} into g select new { g.Key.CategoryName, SumPrice = (decimal? )g.Sum(pt=>pt.p.UnitPrice), Count = g.Select(x=>x.c.CategoryID).Distinct().Count() };
2.4.3 关于EF对象的创建问题
在开发过程中,项目往往被划分为多层,而一个请求过来往往是从表示层开始一层一层向下调用,那么如果我们在不同的层中都使用到了EF上下文对象,而有好几层都这么创建一个EF对象然后对其进行操作,那么最终哪一层的EF对象是我们需要的最新的数据就很难确定了,这时就很容易产生脏读。在这种情况下,我们首先会想到使用单例模式,这样在整个应用程序的生命周期内只允许被创建一次。但是这样又会出现一个问题,所有的用户都访问同一个EF对象,随着访问的用户越来越多,这个EF对象的资源无法及时释放,导致占用的内存也会越来越大,虽然垃圾回收机制会自动回收掉大部分内存,但是有一些内存对象是需要我们手动回收的,可是在这里我们又不能使用using直接回收这个单例对象,因为如果把单例对象释放了,大家就都访问不了了,所以无法使用单例模式来解决这个EF对象的创建问题。
这时,创建线程对象就可以了,我们只需要保证EF对象在一个线程内唯一,一个请求就是一个线程,这样就无须关心多层直接操作EF对象的问题了。HttpContext对象就是微软封装的一个线程对象,完全可以把创建的EF对象存到HttpContext对象中,实现EF对象在线程中唯一。使用示例如下:
public MvcFirstCodeContext DB2 { get { MvcFirstCodeContext db = null; if (HttpContext.Items["db1"] == null) { db = new MvcFirstCodeContext(); HttpContext.Items["db1"] = db; } else { db = HttpContext.Items["db1"] as MvcFirstCodeContext; } return db; } }
2.4.4 关于上下文的使用注意事项
● 不同的上下文实例直接控制对应的实体。
● 实体只能由一个上下文跟踪管理。
● EF上下文的ObjectStateMagner管理实体。
● 批量操作时提交数据库的选择。
● 延迟加载机制的选择。
● 查询Distinct的使用数据量大小,适时地选择是在内存中操作还是在数据库中操作。
2.4.5EF跨数据库支持
目前已有数个数据库厂商或元件开发商宣布要支持ADO.NET Entity Framework:
● Core Lab,支持Oracle、MySQL、PostgreSQL与SQLite数据库。
● IBM,实现DB2使用的LINQ Provider。
● MySQL,发展MySQL Server所用的Provider。
● Npqsql,发展PostgreSQL所用的Provider。
● OpenLink Software,发展支持多种数据库所用的Provider。
● Phoenix Software International,发展支持SQLite数据库的Provider。
● Sybase,支持Anywhere数据库。
●VistaDB Software,支持VistaDB数据库。
● DataDirect Technologies,发展支持多种数据库所用的Provider。
● Firebird,支持Firebird数据库。