警告:此篇為翻譯文章,所有內容皆為原作者所有
此外歡迎指正破英文,如果哪邊有嚴重謬誤哪肯定是我把靈魂出賣給了 google 小姐 ... 這篇好難翻呀 我日 -.-
警告:此篇文章 內容艱澀 技術過硬
這篇 100% 是技術人員寫的 英文用字的 可讀性 可維護性 就跟"大部分的專案" 一樣好
這篇文章的
server 指得是 SQL SERVER 這類的東東
client 指得是 自定義的 C# 代碼
translation 指得是 將 C# 轉成 查詢端語法
客戶端 自訂屬性 和 遠端 LINQ provider
如果你寫了些自訂屬性 你將無法在 遠端 LINQ 操作中 使用他們 (EX:LINQ to Entities)
問題發生的原因在於 這些客製屬性無法 轉譯到 server (EX:SQL SERVER)
因為他們已經被編譯成 中介語言 (intermediate language - IL)
而不是 個 需要被 IQueryable 實作 translation 的 LINQ 表達式樹 (LINQ expression trees)
目前在 .NET 是沒法度 用些反向工程 將 IL 變回方法 (methods) 和 語法 (syntax)
讓我們可以如預期的轉換成查詢語法 (remote query)
這意味者你不得不將查詢寫成兩個部分
首先是 server (EX:SQL SERVER) 可以幹的 (LINQ 查詢)
再用 ToList 或者 AsEnumerable 查詢出中間結果 到 client
然後就只能在這邊進行些運算操作
如果你想要 激烈的 做些轉換 這會非常耗損效能
我們 (David, Colin Meek and myself) 想出了一個 非關 provider 一次性宣告 屬性的方法
這樣他們就可以在兩種情況下使用
他能在 LINQ to SQL, LINQ to Entities 和任何其他 LINQ 輕易的運算屬性
在 .NET 3.5 SP1 運作良好 :)
使用前
這邊我們 擴充 Employee 類別 增加 Age 和 FullName
我們只需要 名字帶有 "da" 的人
但是為了 做出選取 我們不得不 抓下所有東東 到 client
partial class Employee { public string FullName { get { return Forename + " " + Surname; } } public int Age { get { return DateTime.Now.Year - BirthDate.Year - (((DateTime.Now.Month < BirthDate.Month) || DateTime.Now.Month == BirthDate.Month && DateTime.Now.Day < BirthDate.Day) ? 1 : 0)); } } } ... var employees = db.Employees.ToList().Where(e => e.FullName.Contains("da")).GroupBy(e => e.Age);
使用後
這邊 使用我們的方式
他發生在 server 端...
而且 LINQ to SQL, LINQ to Entities 都行的通
partial class Employee { private static readonly CompiledExpressionfullNameExpression = DefaultTranslationOf .Property(e => e.FullName).Is(e => e.Forename + " " + e.Surname); private static readonly CompiledExpression ageExpression = DefaultTranslationOf .Property(e => e.Age).Is(e => DateTime.Now.Year - e.BirthDate.Value.Year - (((DateTime.Now.Month < e.BirthDate.Value.Month) || (DateTime.Now.Month == e.BirthDate.Value.Month && DateTime.Now.Day < e.BirthDate.Value.Day)) ? 1 : 0))); public string FullName { get { return fullNameExpression.Evaluate(this); } } public int Age { get { return ageExpression.Evaluate(this); } } } ... var employees = db.Employees.Where(e => e.FullName.Contains("da")).GroupBy(e => e.Age).WithTranslations();
使用注意事項
上述技巧的注意事項是 你需要確保 你的類別 在查詢之前 有初始化 (看看下面的替代方案)
然而 很顯然的 你註冊的表達式(expression) 所指的 屬性(property) 必須能夠被 translated
所以你將需要約束你自己的方法(methods) 和 實作 IQueryable
這裡是一些上述例子的幾個替代方案。
註冊 表達式
你可以像這個例子一樣 在類別本身的註冊屬性
該屬性可以不靠任何反射 (reflection) 就能自行 計算表達式 (evaluate the expressions)
此外 如果 效能考量不是那麼重要的話
你可以在其他地方註冊它們
使用 方法 (methods) 透過反射 動態查看他們的值
例如
... DefaultTranslationOf.Property(e => e.FullName).Is(e => e.Forename + " " + e.Surname); var employees = db.Employees.Where(e => e.FullName.Contains("da")).GroupBy(e => e.Age).WithTranslations(); ... partial class Employee { public string FullName { get { return DefaultTranslationOf .Evaluate (this, MethodInfo.GetCurrentMethod());} } }
如果 效能考量很重要
你可以獲得 重複運算的 完整代碼
一次從中介語言中的屬性 然後一次從 translation 的表達示
針對不同情境的不同對應
有時候 你的程式的某些部份 可能想在不同的情境下
使用不同的 translations 或 performance 等等
沒問題!
WithTranslations 方法 通常操作著預設的 translation 對應 (map)
(靠 DefaultTranslationOf 訪問)
但也有另一個使用 TranslationMap 的多載
讓你對付特定的情況
例如
var myTranslationMap = new TranslationMap(); myTranslationMap.Add(e => e.Name, e => e.FirstName + " " + e.LastName); var results = (from e in db.Employees where e.Name.Contains("martin") select e).WithTranslations(myTranslationMap).ToList();
他是怎麼運作的
CompiledExpression<T, TResult>
我們需要做的第一件事是讓 自訂編寫的「計算」屬性遠離 IL 然後 變回 表達式樹 (expression trees)
所以我們才能 translate 它們
因為我們也想 在 client 計算他們
所以我們需要在 執行時期 編譯他們
CompiledExpression 只是為了取得 表達式 Func<T, TResult> 而存在
允許編譯和計算 objects 針對不同的編譯版本
ExpressiveExtensions
這個小類別 提供 WithTranslations 擴充方法 和 internal TranslatingVisitor
以利走訪 透過 TranslationMap 實際註冊 Func<T, TResult> expressions 的 property
所以 底層的 LINQ provider 可以處理他
TranslationMap
我們需要有 編譯 表達式的 屬性對應
TranslationMap 為此存在
你可以手動建立一個 TranslationMap
將其傳入 WithTranslations
如果你想要依靠程式 在執行時期建立它們 或者 對不同情境使用不同方法
但是通常你會想用它...
DefaultTranslationOf
這個 helper 類別 讓你針對 default TranslationMap 註冊屬性
我們在 WithTranslations 沒有傳入值的時候使用
它也可以讓你查看 已註冊的屬性
所以你也可以計算他們
即使它耗費了一點反射的效能
public int Age { get { return DefaultTranslationOf.Evaluate (this, MethodInfo.GetCurrentMethod()); } }
有樂!
[)amien
最後...新年快樂 2014