是否可以在对象上执行分组?
from item in context.Items
group item by item.MyObject
select ...
其中Item.MyObject是一个简单的对象,例如:
public class MyObject {
public int SomeValue { get; set; }
public string SomeName { get; set; }
public string SomeOtherProperty { get; set; }
}
显然我可以做到以下几点:
from item in context.Items
group item by new { item.SomeValue, item.SomeName, item.SomeOtherProperty }
select ...
然而,当按具有大量属性的对象进行分组时,这种方法很繁琐且容易出错。
上面的代码导致NotSupportedException,并带有以下消息:“调用'GroupBy'方法的密钥选择器类型在底层商店提供程序中无法比较”。覆盖Equals和GetHashcode没有任何影响。我猜真正的问题是实体框架不知道如何表达SQL ......?
答案 0 :(得分:7)
据我所知,“显然我可以做到以下几点:”是唯一可行的方法。您只需要在每个子列的基础上定义分组,或者通过投影到某种类型,数据库将知道如何比较。
核心问题是EF团队认为ComplexType 不是一组列,允许您使用多个字段整理您的实体,但对于他们来说,它是另一个实体类型,瞬态类型。 ComplexType用于表示从存储过程(或表返回函数)返回的对象,这些对象返回5列的结果集,这些列无法映射到任何正常的现有表/类。例如,假设您有一个包含50个列/字段的表/类Customer,其中大多数不是null并且表示一些敏感数据。您创建一个存储过程,该过程获取客户的ID并返回其{名称,地址,联系电话,鞋号}。显然,如果设置了这样的修剪结果,则无法将其解码/映射到具有无法为空的大量字段的Customer类。那么如何在EF中表示结果集?
第一个选项,相当蹩脚的一个,就是简单地忽略EF并直接与数据库对话并手动读取/翻译/解包行/列。好吧,我们知道SqlClient,我们可以做到。
第二个选项,丑陋的选项,是在EF中定义一个存根表/视图,例如CustomerView,它只包含{name,address,contact phone,shoe size}列,并将存储过程结果集映射到它。它可以很好地工作,但如果你偶然'生成数据库'形成EF模型,你也会获得额外的未使用的表..
救援的第三个选择是ComplexType。您只需告诉EF“有一些额外的数据类型”,而永远不会映射到自己的表,并且永远不会定义一个PrimaryKey ,而不是尝试制作影子表或视图。 ,为方便起见,EF会将其映射到类,就像任何其他数据对象一样。现在,有这样的类型,你可以从程序返回它并将其作为一个很好的类型对象检索,你可以将它作为过程的参数等传递。但是:你不能自己坚持它。它没有自己的表映射,也没有自己的主键。它有没有身份。
很好,但是,为什么你想要从程序中返回这样的数据类型?可能是,该过程处理或生成了一些数据,您希望将它们存储在某个表中。您尚未定义整个普通实体,因此该事物只是一个“数据包”,因此您可能会将该结果值与其他数据一起写入该表。这就是为什么EF允许你将ComplexType映射到表的某些列:如果你从一个过程获得了那个临时结果对象,现在你想在某个地方写它,你不必逐列手动完成它,您只需将其映射到客户的“姓名,地址,联系电话,鞋码”栏目。
最重要的一点是,数据库服务器永远不会知道存在这样的ComplexType。正如我所说,它没有身份。当您尝试执行类似
的LINQ查询时aset.Where(item => item.complexProperty == complexValue)
然后,complexValue是一个CLR对象,它没有映射到任何表,没有NO身份,因此没有PK,因此EF完全没有IDEA如何检查“左手对象”是否与“右手”相同宾语”。如果对象定义了一些标识,它可以检查服务器端的PK,或者在客户端进行对象引用比较,但是这里 - 它只是失败了。
我完全同意这是EF中另一个该死的有用功能。我相信EF团队很容易在逐列的基础上比较ComplexTypes,但他们没有。他们根本不认为ComplexTypes可以用于整理表格。它们意味着它是一个瞬态数据包,可以传递到存储过程和函数.Pity。