I'm working on creating a JsonConverter for JSON.NET that is capable of serializing and deserializing expressions (System.Linq.Expressions). I'm down to the last 5% or so of the work, and I'm having problems being able to run a LINQ-to-SQL query generated from the 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 pretty straightforward grouping query in LINQ-to-SQL. TestQuerySource
is an implementation of System.Data.Linq.DataContext
. Bundle
, BundleItem
, Product
, are all LINQ-to-SQL entities decorated with TableAttribute
and other other mapping attributes. Their corresponding datacontext properties are all Table<T>
properties as normal. In other words, nothing spectacularly notable here.
However, when I attempt to run the query after the expression has been deserialized, 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 the expression is doing cannot be translated to SQL by the LINQ-to-SQL query provider. It appears that it has something to do with creating an anonymous type as part of the query, like as part of the join statement. This assumption is supported 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}
The problem seems to occur when a non-primitively typed property of an anonymous type needs be accessed. In this case the bi
property is being accessed in order to get to BundleItem
's ProductID
property.
What I can't figure out is what the difference would be - why accessing the property in the original expression would work fine, but not in the deserialized expression.
I'm guessing the issue has something to do with some sort of information about the anonymous type getting lost during serialization, but I'm not sure where to look to find it, or even what to be looking for.
Other Examples:
It is worth noting that simpler expressions like this one work fine:
Expression<Func<TestQuerySource, Category>> expression = db => db.Categories.First();
Even doing grouping (without joining) works as well:
Expression<Func<TestQuerySource, Int32>> expression = db => db.Categories.GroupBy(c => c.ID).First().Key;
Simple joins work:
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();
Selecting 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()}