Dan*_*fer 6 c# serialization expression iqueryable linq-to-sql
我正在为JSON.NET创建一个JsonConverter,它能够序列化和反序列化表达式(System.Linq.Expressions).我已经完成了工作的最后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;
Run Code Online (Sandbox Code Playgroud)
这是LINQ-to-SQL中非常简单的分组查询.TestQuerySource是一个实现System.Data.Linq.DataContext.Bundle,BundleItem,Product,都装饰有LINQ到SQL实体TableAttribute等其他映射属性.它们对应的datacontext属性都是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.
Run Code Online (Sandbox Code Playgroud)
我理解这意味着表达式所做的事情不能由LINQ-to-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}
Run Code Online (Sandbox Code Playgroud)
反序列化(损坏):
{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}
Run Code Online (Sandbox Code Playgroud)
当需要访问匿名类型的非原始类型属性时,似乎会出现此问题.在这种情况下,bi物业是为了得到被访问 BundleItem的ProductID属性.
我无法弄清楚的是差异是什么 - 为什么在原始表达式中访问属性可以正常工作,但不能在反序列化表达式中工作.
我猜这个问题与某些关于匿名类型在序列化过程中丢失的信息有关,但我不知道在哪里找到它,甚至找不到什么.
其他例子:
值得注意的是,像这样的简单表达式可以正常工作:
Expression<Func<TestQuerySource, Category>> expression = db => db.Categories.First();
Run Code Online (Sandbox Code Playgroud)
即使进行分组(没有加入)也可以:
Expression<Func<TestQuerySource, Int32>> expression = db => db.Categories.GroupBy(c => c.ID).First().Key;
Run Code Online (Sandbox Code Playgroud)
简单连接工作:
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();
Run Code Online (Sandbox Code Playgroud)
选择匿名类型有效:
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();
Run Code Online (Sandbox Code Playgroud)
以下是最后一个示例的字符串表示:
原版的:
{db => db.BundleItems
.Join(db.Products,
bi => bi.ProductID,
p => p.ID,
(bi, p) => new <>f__AnonymousType0`2(a = bi, b = p))
.First()}
Run Code Online (Sandbox Code Playgroud)
反序列化:
{db => db.BundleItems
.Join(db.Products,
bi => bi.ProductID,
p => p.ID,
(bi, p) => new <>f__AnonymousType0`2(bi, p))
.First()}
Run Code Online (Sandbox Code Playgroud)
我认为区别在于,在工作示例中,匿名类型是使用属性构造的,而在损坏的情况下,它是使用构造函数实例化的。
L2S 假设在查询转换期间,如果您为属性分配特定值,则该属性将仅返回该值。
L2S 并不假设名为 abc 的 ctor 参数将初始化名为 Abc 的属性。这里的想法是,一个角色可以做任何事情,而一个属性只是存储一个值。
请记住,匿名类型与自定义 DTO 类没有什么不同(字面意思!L2S 无法区分它们)。
在你的例子中,你要么a)不使用匿名类型(有效)b)仅在最终投影中使用ctor(有效 - 一切都可以作为最终投影,甚至任意方法调用。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();
Run Code Online (Sandbox Code Playgroud)
第二个会起作用,第一个不会。
(丹尼尔更新)
重建反序列化表达式时,请确保使用正确的重载(Expression.New如果需要通过构造函数设置属性)。使用的正确重载是Expression.New(ConstructorInfo, IEnumerable<Expression>, IEnumerable<MemberInfo>)or Expression.New(ConstructorInfo, IEnumerable<Expression>, MemberInfo[])。如果使用其他重载之一,则参数将仅传递到构造函数中,而不是分配给属性。
| 归档时间: |
|
| 查看次数: |
803 次 |
| 最近记录: |