Я работаю над созданием JsonConverter для JSON.NET, способного сериализовать и десериализовать выражения (System.Linq .Выражения). Я выполнил последние 5% или около того работы, и у меня возникли проблемы с запуском запроса LINQ-to-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-to-SQL. TestQuerySource
является реализацией System.Data.Linq.DataContext
. Bundle
, BundleItem
, Product
— все сущности LINQ-to-SQL, украшенные TableAttribute
и другими атрибутами сопоставления. Соответствующие им свойства контекста данных — это все свойства Table
, как обычно. Другими словами, здесь нет ничего эффектно примечательного.
Однако, когда я пытаюсь выполнить запрос после десериализации выражения, я получаю следующую ошибку:
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}
Проблема возникает, когда не примитивно типизированное свойство анонимного тип должен быть доступен. В этом случае доступ к свойству bi
осуществляется для доступа к свойству BundleItem
's ProductID
.
Чего я не могу понять, так это в чем разница — почему доступ к свойству в исходном выражении будет работать нормально, а в десериализованном выражении — нет.
Я предполагаю, что проблема как-то связана с какой-то информацией об анонимном типе, которая теряется во время сериализации, но я не уверен, где ее искать, или даже что искать.
Другие примеры:
Стоит отметить, что более простые выражения, подобные этому, работают нормально:
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()}