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