";没有支持的到SQL的翻译“;在反序列化IQueryable表达式之后
本文关键字:反序列化 IQueryable 之后 表达式 翻译 quot 支持 SQL | 更新日期: 2023-09-27 18:30:05
我正在为JSON.NET创建一个JsonConverter,它能够序列化和反序列化表达式(System.Linq.expressions)。我只完成了最后5%左右的工作,并且在运行由反序列化表达式生成的Linq到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中一个非常简单的分组查询。CCD_ 1是CCD_。Bundle
、BundleItem
、Product
都是用TableAttribute
和其他映射属性修饰的LINQ到SQL实体。它们对应的数据上下文属性通常都是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.
我理解这意味着LINQ到SQL查询提供程序无法将表达式正在执行的操作转换为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
的TestQuerySource
0属性。
我想不出会有什么区别——为什么访问原始表达式中的属性可以正常工作,但在反序列化表达式中却不行。
我猜这个问题与匿名类型在序列化过程中丢失的某些信息有关,但我不确定在哪里可以找到它,甚至不确定要找什么。
其他示例:
值得注意的是,像这样简单的表达式可以很好地工作:
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()}
我认为不同之处在于,在工作示例中,匿名类型是使用属性构建的,而在坏的情况下,它是使用构造函数实例化的。
L2S假设在查询转换过程中,如果您为某个属性指定了某个值,则该属性将仅返回该值。
L2S并不假设一个名为abc的ctor参数将初始化一个名称为abc的属性。这里的想法是,一个ctor可以做任何事情,而一个属性只存储一个值。
请记住,匿名类型与自定义DTO类没有什么不同(实际上!L2S无法区分它们)。
在您的示例中,您要么a)不使用匿名类型(works),要么b)仅在最终投影中使用ctor(works-所有内容都作为最终投影,甚至是任意方法调用。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();
第二个会起作用,第一个不会。
(Daniel更新)
重建反序列化表达式时,如果需要通过构造函数设置属性,请确保使用正确的Expression.New
重载。要使用的正确过载是Expression.New(ConstructorInfo, IEnumerable<Expression>, IEnumerable<MemberInfo>)
或Expression.New(ConstructorInfo, IEnumerable<Expression>, MemberInfo[])
。如果使用了其他重载之一,则参数将只传递到构造函数中,而不是分配给属性。