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卻不容易,再多看一下,既然實現體是一樣,其實也就是聲明不一樣了,那么直接更改參數聲明不就可以了么?上面已經演示過了,不需要任何硬編碼,直接生效。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM