I am working on creating a JsonConverter for JSON.NET that is able to serialize and deserialize expressions (System.Linq.Expressions). I have reached the last 5% of my work, and I am having problems with the ability to run a LINQ-to-SQL query created from a deserialized expression.
Here is the expression:
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;
This is a fairly simple grouping query in LINQ-to-SQL. TestQuerySource
is an implementation of System.Data.Linq.DataContext
. Bundle
, BundleItem
, Product
- all LINQ-to-SQL objects decorated with TableAttribute
and other other matching attributes. Their respective datacontext properties are Table<T>
properties as normal. In other words, nothing remarkable is visible here.
However, when I try to run the query after deserializing the expression, I get the following error:
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.
I understand that this means that something that the expression does cannot be translated into SQL by the LINQ-to-SQL query provider. It seems like this has something to do with creating an anonymous type as part of the request, for example, as part of a join statement. This assumption is confirmed by comparing the string representation of the original and deserialized expressions:
Original (working):
{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 (broken):
{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}
A problem occurs when access to a non-primitively typed property of an anonymous type is required. In this case, the bi
property is available to access the BundleItem
ProductID
property.
I canβt understand what the difference is: why access to the property in the original expression will work fine, but not in the deserialized expression.
I suppose the problem is with some information about the anonymous type that gets lost during serialization, but I'm not sure where to look for it or even what to look for.
Other examples:
It's worth noting that simpler expressions like this one work just fine:
Expression<Func<TestQuerySource, Category>> expression = db => db.Categories.First();
Also, work is done on grouping (without a connection):
Expression<Func<TestQuerySource, Int32>> expression = db => db.Categories.GroupBy(c => c.ID).First().Key;
Simple work with connections:
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();
Choosing an anonymous type works:
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();
Here are the string representations of the last example:
Original:
{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()}