我正在尝试使用实体框架从数据库中选择对象为匿名类型。使用 Union 并选择子集合时,出现异常:
System.ArgumentException:“Distinct”操作无法应用于指定参数的集合 ResultType。
我的模型包含源自
BaseType
的多种类型。此基本类型引用 RefType
,其中包含 ItemType
的集合。从 BaseType
派生的类型存储在单独的表中,即 Union。
查询如下所示:
var q1 = ctx.Set<Type1>().Select(x => new { x.Id, x.Ref.Items });
var q2 = ctx.Set<Type2>().Select(x => new { x.Id, x.Ref.Items });
q1.Union(q2).ToList();
但是要重现该错误,您甚至可以合并相同类型的查询,只要您选择一个集合即可。
我会在联合之后进行选择,但是要联合
Type1
、Type2
等。我必须将它们强制转换为 BaseType
,这在 LINQ-to-SQL 中是不允许的。
有什么方法可以在同一个查询中执行此操作吗?
当
ExpressionConverter
尝试将表达式 q1.Union(q2)
转换为 SQL 时,实体框架的查询生成管道会出现异常。
在有效的查询中,您将看到 EF 向 SQL 查询添加了
DISTINCT
子句。具有集合属性 (x.Ref.Items
) 的类型不会作为 Distinct
操作的有效参数传递,并且 EF 会引发您看到的异常。
不幸的是,使用
Concat
代替 Union
并不是有效的解决方法。 EF也会抛出异常:
不支持嵌套查询。操作1='UnionAll' 操作2='MultiStreamNest'
这意味着根本不支持连接包含具有集合属性的类型的嵌套查询。
所以你必须在记忆中做
Union
:
var result = q1.AsEnumerable() // Continues the query in memory
.Union(q2).ToList();
C# 在等同包含集合的匿名类型方面没有任何问题:它只是认为任何集合成员与另一个集合成员不相等。这确实意味着查询可以生成包含非唯一结果的集合(相同的
Id
,相同的Items
),这在依赖于Union
的隐式Distinct
时可能是不可预期的。
我不确定为什么,由于某种原因,distinct 失败了,也许是因为它是匿名类型,而且它仍然是
IQueryable
,我建议触发这样的查询
var q1 = ctx.Set<Type1>()
.Select(x => new { x.Id, x.Ref.Items })
.ToList<object>();
var q2 = ctx.Set<Type2>()
.Select(x => new { x.Id, x.Ref.Items })
.ToList<object>();
q1.Union(q2).ToList();
请注意,在这种情况下,
Distinct
将检查所有属性是否相等,这意味着如果两个对象具有相同的 id 但不同的项目,则两者都会存在。
如果你不关心不同的值,你也可以使用 concat
如果你关心distinct并且第一个选项不起作用,你可以使用group by并实现你自己的distinct,像这样
var q1 = ctx.Set<Type1>()
.Select(x => new { Id = x.Id, Items = x.Ref.Items });
var q2 = ctx.Set<Type2>()
.Select(x => new { Id = x.Id, Items = x.Ref.Items });
// this will group by id, and select the first object items
var qFinal = q1.Concat(q2)
.GroupBy(e => e.id)
.Select(e => new { e.key, e.First().Items })
.ToList();
也许你不想要
First()
,你想用什么就用什么