Func转Expression的方法(C#)

这篇文章写完后,发现网上有大量关于Expresstion和Func的讨论,可以不看我的,看这几篇,是一样的,还更深入一些:

http://fascinatedwithsoftware.com/blog/post/2012/01/10/More-on-Expression-vs-Func-with-Entity-Framework.aspx

http://fascinatedwithsoftware.com/blog/post/2011/12/02/Falling-in-Love-with-LINQ-Part-7-Expressions-and-Funcs.aspx

http://stackoverflow.com/questions/793571/why-would-you-use-expressionfunct-rather-than-funct

http://q.cnblogs.com/q/37952/

为什么有这个需求,先看如下两个扩展方法:

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却不容易,再多看一下,既然实现体是一样,其实也就是声明不一样了,那么直接更改参数声明不就可以了么?上面已经演示过了,不需要任何硬编码,直接生效。

posted @ 2013-03-26 19:12  $walker  阅读(4646)  评论(3编辑  收藏  举报