2012-04-05 24 views
6

Tôi đang làm việc về việc tạo một JsonConverter cho JSON.NET có khả năng tuần tự hóa và deserializing biểu thức (System.Linq.Expressions). Tôi đang xuống đến 5% cuối cùng của công việc, và tôi đang gặp vấn đề có thể chạy một truy vấn LINQ-to-SQL được tạo ra từ biểu thức deserialized."Không hỗ trợ dịch sang SQL" sau khi deserializing biểu thức IQueryable

Dưới đây là các biểu hiện:

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; 

Đây là câu hỏi nhóm khá đơn giản trong LINQ-to-SQL. TestQuerySource là triển khai System.Data.Linq.DataContext. Bundle, BundleItem, Product, là tất cả các thực thể LINQ-to-SQL được trang trí với TableAttribute và các thuộc tính ánh xạ khác. Các thuộc tính datacontext tương ứng của chúng là tất cả các thuộc tính Table<T> như bình thường. Nói cách khác, không có gì đáng chú ý đáng chú ý ở đây.

Tuy nhiên, khi tôi cố gắng để chạy các truy vấn sau khi các biểu hiện đã được deserialized, tôi nhận được lỗi sau:

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. 

Tôi hiểu rằng điều này có nghĩa rằng một cái gì đó biểu hiện đang làm không thể được dịch sang SQL bởi nhà cung cấp truy vấn LINQ-to-SQL. Dường như nó có liên quan đến việc tạo một kiểu ẩn danh như là một phần của truy vấn, giống như một phần của câu lệnh nối. Giả định này được hỗ trợ bằng cách so sánh chuỗi đại diện của các biểu thức ban đầu và deserialized:

gốc (làm việc):

{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} 

deserialized (bị hỏng):

{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} 

Sự cố có vẻ xảy ra khi một tệp không được nhập chính xác roperty của một loại vô danh cần được truy cập. Trong trường hợp này, thuộc tính bi đang được truy cập để truy cập thuộc tính ProductID của BundleItem.

Điều tôi không thể hiểu được là sự khác biệt sẽ là gì - tại sao việc truy cập thuộc tính trong biểu thức ban đầu sẽ hoạt động tốt, nhưng không có trong biểu thức deserialized.

Tôi đoán sự cố có liên quan đến một số loại thông tin về loại ẩn danh bị mất trong quá trình tuần tự hóa, nhưng tôi không chắc chắn nên tìm ở đâu hoặc thậm chí tìm kiếm gì.


Các ví dụ khác:

Điều đáng chú ý là biểu thức đơn giản như thế này tốt một công việc:

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

Ngay cả làm nhóm (không tham gia) hoạt động cũng như:

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

Tham gia đơn giản hoạt động:

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(); 

Chọn một loại vô danh hoạt động:

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(); 

Sau đây là các cơ quan đại diện chuỗi các ví dụ cuối cùng:

gốc:

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

deserialized:

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

Chỉ vì tò mò là điều này dựa trên InterLinq? –

+0

Không. Tôi đã không nhận ra cho đến sau khi tôi đã nhận được khá sâu rằng đã có dự án làm điều này cho WCF ... nhưng tôi cũng không muốn phải đối phó với bất cứ điều gì WCF liên quan cho việc này. –

+0

Vâng, công cụ tuần tự hóa mà họ đã làm là khá hay. Nó khá nhiều bật ra từ các công cụ WCF. WCF thực sự chỉ nhập vào hình ảnh khi bạn nhận được vào các triển khai IQueryable. Nó được chuyển đến Silverlight khá sạch. Tôi nghi ngờ hoán đổi XML cho JSON.NET cũng có thể tương đối thẳng về phía trước. FWIW;) –

Trả lời

2

Tôi nghĩ rằng sự khác biệt là trong ví dụ làm việc kiểu nặc danh được xây dựng sử dụng tài sản và trong trường hợp gãy nó được khởi tạo sử dụng một constructor.

L2S giả định trong khi dịch truy vấn nếu bạn chỉ định cho thuộc tính một giá trị nhất định, thuộc tính sẽ chỉ trả về giá trị đó.

L2S không cho rằng tên tham số ctor abc sẽ khởi tạo thuộc tính được gọi là Abc. Suy nghĩ ở đây là một ctor có thể làm bất cứ điều gì cả trong khi một tài sản sẽ chỉ lưu trữ một giá trị.

Hãy nhớ rằng các loại ẩn danh không khác với các lớp DTO tùy chỉnh (theo nghĩa đen! L2S không thể phân biệt chúng).

Trong ví dụ của bạn, bạn là a) không sử dụng các loại ẩn danh (tác phẩm) b) sử dụng ctor trong phép chiếu cuối cùng (công trình - mọi thứ hoạt động như một phép chiếu cuối cùng, ngay cả các cuộc gọi phương thức tùy ý. L2S là tuyệt vời.) hoặc c) sử dụng một ctor trong phần sql của truy vấn (bị hỏng). Điều này khẳng định lý thuyết của tôi.

Hãy thử điều này:

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(); 

thứ hai sẽ làm việc, người đầu tiên sẽ không.


(Update từ Daniel)

Khi xây dựng lại biểu deserialized, hãy chắc chắn để sử dụng quá tải đúng Expression.New nếu thuộc tính cần phải được thiết lập thông qua các nhà xây dựng. Quá tải chính xác để sử dụng là Expression.New(ConstructorInfo, IEnumerable<Expression>, IEnumerable<MemberInfo>) hoặc Expression.New(ConstructorInfo, IEnumerable<Expression>, MemberInfo[]). Nếu một trong những quá tải khác được sử dụng, các đối số sẽ chỉ được truyền vào hàm tạo, thay vì được gán cho các thuộc tính.

+0

Mã được liệt kê hiển thị cả hai ví dụ bằng cách sử dụng hàm tạo. Cơ quan này đang hỏi liệu việc đặt tên đó có thực sự tác động đến bản dịch hay không. Không có biểu thức được dịch nào đang sử dụng tính năng khởi tạo thuộc tính tự động. –

+0

Tôi không nghĩ đây chỉ là việc đặt tên. Đó là cú pháp cho các khởi tạo đối tượng (các cây biểu thức có sự hỗ trợ rõ ràng cho các khởi tạo!). Hãy nhớ rằng, cây biểu thức không định dạng trở lại mã C#. Chúng định dạng lại theo cú pháp của riêng chúng. – usr

+0

@usr: Đó cũng là suy nghĩ ban đầu của tôi, nhưng tôi có các ví dụ hoạt động khi biểu thức hiển thị cùng một cú pháp. Tôi chỉ nhận ra rằng một bản chỉnh sửa trước đó đã bị xóa nơi tôi đã trình bày các biểu diễn chuỗi, tôi sẽ cập nhật trong giây lát để đưa họ trở lại. –

Các vấn đề liên quan