2012-04-05 64 views
6

我正在爲能夠序列化和反序列化表達式(System.Linq.Expressions)的JSON.NET創建JsonConverter。我下降到最後5%左右的工作,並且遇到問題能夠運行從反序列化表達式生成的LINQ-to-SQL查詢。反編譯IQueryable表達式後「沒有支持的轉換爲SQL」

這裏是表達:

Expression<Func<TestQuerySource, Bundle>> expression = db => (
    from b in db.Bundles 
    join bi in db.BundleItems on b.ID equals bi.BundleID 
    join p in db.Products on bi.ProductID equals p.ID 
    group p by b).First().Key; 

這是LINQ到SQL一個非常簡單的分組查詢。 TestQuerySourceSystem.Data.Linq.DataContext的實現。 Bundle,BundleItem,Product,都是用TableAttribute和其他其他映射屬性裝飾的LINQ-to-SQL實體。它們的相應datacontext屬性都是正常的Table<T>屬性。換句話說,這裏沒有什麼特別值得注意的。

然而,當我試圖表達的反序列化後,運行查詢,我得到以下錯誤:

System.Reflection.TargetInvocationException: 
Exception has been thrown by the target of an invocation. ---> 
    System.NotSupportedException: The member '<>f__AnonymousType0`2[Bundle,BundleItem].bi' has no supported translation to SQL. 

我明白這意味着什麼表達是做不能轉換通過到SQL LINQ-to-SQL查詢提供程序。看起來它與創建一個匿名類型作爲查詢的一部分有關,例如作爲連接語句的一部分。這個假設是通過比較原始和反序列化表現形式的字符串表示支持:

原件(工作):

{db => db.Bundles 
.Join(db.BundleItems, 
    b => b.ID, 
    bi => bi.BundleID, 
    (b, bi) => new <>f__AnonymousType0`2(b = b, bi = bi)) 
.Join(db.Products, 
    <>h__TransparentIdentifier0 => <>h__TransparentIdentifier0.bi.ProductID, 
    p => p.ID, 
    (<>h__TransparentIdentifier0, p) => 
     new <>f__AnonymousType1`2(<>h__TransparentIdentifier0 = <>h__TransparentIdentifier0, p = p)) 
.GroupBy(<>h__TransparentIdentifier1 => 
    <>h__TransparentIdentifier1.<>h__TransparentIdentifier0.b, 
    <>h__TransparentIdentifier1 => <>h__TransparentIdentifier1.p) 
.First().Key} 

反序列化(碎):

{db => db.Bundles 
.Join(db.BundleItems, 
    b => b.ID, 
    bi => bi.BundleID, 
    (b, bi) => new <>f__AnonymousType0`2(b, bi)) 
.Join(db.Products, 
    <>h__TransparentIdentifier0 => <>h__TransparentIdentifier0.bi.ProductID, 
    p => p.ID, 
    (<>h__TransparentIdentifier0, p) => new <>f__AnonymousType1`2(<>h__TransparentIdentifier0, p)) 
.GroupBy(<>h__TransparentIdentifier1 => 
    <>h__TransparentIdentifier1.<>h__TransparentIdentifier0.b, 
    <>h__TransparentIdentifier1 => <>h__TransparentIdentifier1.p) 
.First().Key} 

這個問題似乎發生在一個非原始類型的p需要訪問匿名類型的roperty。在這種情況下,bi屬性正在訪問,以獲得BundleItemProductID屬性。

我無法弄清楚什麼區別會是 - 爲什麼訪問原始表達式中的屬性可以正常工作,但不是反序列化的表達式。

我猜這個問題與某些有關匿名類型在序列化期間丟失的信息有關,但我不知道在哪裏尋找它,甚至找什麼。


其他例子:

值得注意的是簡單的表情像這樣的做工精細:

Expression<Func<TestQuerySource, Category>> expression = db => db.Categories.First(); 

即使做分組(不含加盟)的作品,以及:

Expression<Func<TestQuerySource, Int32>> expression = db => db.Categories.GroupBy(c => c.ID).First().Key; 

簡單加入工作:

Expression<Func<TestQuerySource, Product>> expression = db => (
    from bi in db.BundleItems 
    join p in db.Products on bi.ProductID equals p.ID 
    select p).First(); 

選擇一個匿名類型的作品:

Expression<Func<TestQuerySource, dynamic>> expression = db => (
    from bi in db.BundleItems 
    join p in db.Products on bi.ProductID equals p.ID 
    select new { a = bi, b = p }).First(); 

下面是最後一個例子中的字符串表示:

原文:

{db => db.BundleItems 
.Join(db.Products, 
    bi => bi.ProductID, 
    p => p.ID, 
    (bi, p) => new <>f__AnonymousType0`2(a = bi, b = p)) 
.First()} 

反序列化:

{db => db.BundleItems 
.Join(db.Products, 
    bi => bi.ProductID, 
    p => p.ID, 
    (bi, p) => new <>f__AnonymousType0`2(bi, p)) 
.First()} 
+0

出於好奇,這是基於InterLinq? – 2012-04-05 21:04:21

+0

不,我直到我已經深入瞭解有些項目正在爲WCF做這些事情之後才意識到......但我也不想處理與此相關的任何WCF。 – 2012-04-05 21:06:16

+0

是的,他們做的系列化的東西非常好。它幾乎從WCF的東西中彈出。當你進入IQueryable實現時,WCF只會進入圖片。它相當乾淨地移植到Silverlight。我懷疑爲JSON.NET換出XML可能也是相對直截了當的。 FWIW;) – 2012-04-05 21:13:08

回答

2

我認爲不同的是,在實施例的匿名類型是使用特性和在破碎情況下,它是使用構造函數實例構成。

L2S在查詢翻譯過程中假定如果您爲某個屬性分配了某個值,該屬性將僅返回該值。

L2S不認爲ctor參數名稱abc會初始化一個名爲Abc的屬性。這裏的想法是,一家公司可以做任何事情,而一個財產只會存儲一個價值。

請記住,匿名類型與自定義DTO類沒有什麼不同(從字面上看,L2S無法區分它們)。

在你的例子中,你要麼a)不使用匿名類型(作品)b)僅在最終投影中使用ctor(作品 - 所有作品都作爲最終投影,甚至任意方法調用,L2S真棒。或者c)在查詢的sql部分使用ctor(破壞)。這證實了我的理論。

試試這個:

var query1 = someTable.Select(x => new CustomDTO(x.SomeString)).Where(x => x.SomeString != null).ToList(); 
var query2 = someTable.Select(x => new CustomDTO() { SomeString = x.SomeString }).Where(x => x.SomeString != null).ToList(); 

第二個將工作,第一個不會。


(更新丹尼爾)

當重建反序列化的表達,確保使用的Expression.New正確的過載,如果屬性需要可以通過構造方法設置。使用的正確過載是Expression.New(ConstructorInfo, IEnumerable<Expression>, IEnumerable<MemberInfo>)Expression.New(ConstructorInfo, IEnumerable<Expression>, MemberInfo[])。如果使用其他重載之一,參數將只傳遞給構造函數,而不是分配給屬性。

+0

列出的代碼顯示了使用排序構造函數的兩個示例,其中一個僅使用命名參數。操作是問這個命名是否實際影響翻譯或不。翻譯的表達式都不使用自動屬性初始化。 – 2012-04-05 22:46:44

+0

我不認爲這只是命名。這是對象初始化器的語法(表達式樹對初始化器有明確的支持!)。請記住,表達式樹不會格式化爲C#代碼。他們格式化爲自己的語法。 – usr 2012-04-05 22:47:39

+0

@usr:那也是我最初的想法,但是我有一些例子可以在表達顯示相同語法的地方工作。我剛剛意識到,之前的編輯已經刪除了我所顯示的字符串表示的位置,我會稍後更新以將它們還原。 – 2012-04-05 22:52:42