Func转Expression的方法(C#)
这篇文章写完后,发现网上有大量关于Expresstion和Func的讨论,可以不看我的,看这几篇,是一样的,还更深入一些:
http://stackoverflow.com/questions/793571/why-would-you-use-expressionfunct-rather-than-funct
为什么有这个需求,先看如下两个扩展方法:
public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate); public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
这就是我们用Linq, EntityFramework的时候常用的Where方法而已,平时也没去注意过它们的差别:
传入Func,得到的是IEnumerable对象,传入Expression,得到的是IQueryable对象,那么它们之间到底有什么差别?
如下示例:
我要查NorthWind数据库里的Products表里ProductId大于15的所有产品,很简单的一句话,为了分别传入Func和Expression,我们做如下封装:
//接受Func参数,返回IEnumerable private IEnumerable<T> FetchData2<T>(Func<T, bool> f) where T : class { var context = new NorthwindEntities(); return context.Set<T>().Where(f); } //接受Expression<Func>参数,返回IQueryable private IQueryable<T> FetchData<T>(Expression<Func<T, bool>> f) where T : class { var context = new NorthwindEntities(); return context.Set<T>().Where(f); }
分别进行调用:
FetchData2<Products>(m => m.ProductID > 15).ToList(); FetchData<Products>(m => m.ProductID > 15).ToList();
结果当然没什么区别,我们要的是SQL:
--以Func为参数进行的查询,可见没有生成where语句 SELECT [Extent1].[ProductID] AS [ProductID], [Extent1].[ProductName] AS [ProductName], [Extent1].[SupplierID] AS [SupplierID], [Extent1].[CategoryID] AS [CategoryID], [Extent1].[QuantityPerUnit] AS [QuantityPerUnit], [Extent1].[UnitPrice] AS [UnitPrice], [Extent1].[UnitsInStock] AS [UnitsInStock], [Extent1].[UnitsOnOrder] AS [UnitsOnOrder], [Extent1].[ReorderLevel] AS [ReorderLevel], [Extent1].[Discontinued] AS [Discontinued] FROM [dbo].[Products] AS [Extent1] --以Expression为参数进行的查询,如愿生成了where语句 SELECT [Extent1].[ProductID] AS [ProductID], [Extent1].[ProductName] AS [ProductName], [Extent1].[SupplierID] AS [SupplierID], [Extent1].[CategoryID] AS [CategoryID], [Extent1].[QuantityPerUnit] AS [QuantityPerUnit], [Extent1].[UnitPrice] AS [UnitPrice], [Extent1].[UnitsInStock] AS [UnitsInStock], [Extent1].[UnitsOnOrder] AS [UnitsOnOrder], [Extent1].[ReorderLevel] AS [ReorderLevel], [Extent1].[Discontinued] AS [Discontinued] FROM [dbo].[Products] AS [Extent1] WHERE [Extent1].[ProductID] > 15
这个结果是惊人的,居然传入func的情况下是把数据全部取到内存里再进行枚举过滤(linq to entity)~~~,这一下吃惊不小,可见平时的开发中这一点是必须要时刻注意的。
其实这就是Linq to Sql和Linq to Entity的区别,由于重载的原因,都做到同样的方法上来,一旦参数传错了,就是别的方法了。
比较还没完,因为一般的企业框架或接口,是不会暴露底层数据库操作出来的,对外的都是业务方法,比如我们上面的示例,目的是获取ID大于某值的所有产品,那么我们假定暴露成如下方法(接口):
//获取ID大于某值的产品列表 private IEnumerable<Products> GetProducts(int id) { var datas = FetchData<Products>(m => m.ProductID > id); return datas; }
调用:
GetProducts(15);
生成SQL:
exec sp_executesql N'SELECT [Extent1].[ProductID] AS [ProductID], [Extent1].[ProductName] AS [ProductName], [Extent1].[SupplierID] AS [SupplierID], [Extent1].[CategoryID] AS [CategoryID], [Extent1].[QuantityPerUnit] AS [QuantityPerUnit], [Extent1].[UnitPrice] AS [UnitPrice], [Extent1].[UnitsInStock] AS [UnitsInStock], [Extent1].[UnitsOnOrder] AS [UnitsOnOrder], [Extent1].[ReorderLevel] AS [ReorderLevel], [Extent1].[Discontinued] AS [Discontinued] FROM [dbo].[Products] AS [Extent1] WHERE [Extent1].[ProductID] > @p__linq__0',N'@p__linq__0 int',@p__linq__0=15
可以看一下跟上面生成的sql的差别,已经变成存储过程了。
好吧,其实说到这里还没点题,上面只是说到了几种不同语法的差别,但是为什么有我标题这一说呢?因为来自于我的项目底层的某个封装:
IEnumerable<T> GetEntities(Func<T, bool> exp);
显然我用了Func传参,结果你们也知道了,于是我开始寻找Func转Expression的方法,一遍海搜之下,用了各种转化方式,才发现如下两句话都是成立的:
Expression<Func<Products, bool>> g = m => m.ProductID > 15; Func<Products, bool> t = m => m.ProductID > 15;
但是把t转成g却不容易,再多看一下,既然实现体是一样,其实也就是声明不一样了,那么直接更改参数声明不就可以了么?上面已经演示过了,不需要任何硬编码,直接生效。