【问题标题】:IEquatable breaks loading of Entity Framework entitiesIEquatable 中断加载实体框架实体
【发布时间】:2015-01-15 06:24:08
【问题描述】:

我想比较记录,看看它们之间是否存在差异。

Person表:

ID    Name          Address
--------------------------------
1     John Smith    123 A Street
2     John Smith    123 A Street
3     John Smith    234 B Street

记录 1 和 2 是“相等的”。记录 2 和 3 “不相等”。

我已经在模型Person 上实现了IEquatable,如下所示。

public static bool operator ==(Person p1, Person p2)
{
    if (System.Object.ReferenceEquals(p1, p2)) return true;

    return p1.Equals(p2);
}

public static bool operator !=(Person p1, Person p2)
{
    return !(p1== p2);
}

public bool Equals(Person other)
{
    if (System.Object.ReferenceEquals(this, other)) return true;

    if (Name != other.Name) return false;
    if (Address != other.Address) return false;

    return true;
}

public override bool Equals(object obj)
{
    Person person = obj as Person;
    if (person == null) return false;

    return Equals(person);
}

public override int GetHashCode()
{
    unchecked
    {
        int hash = (int)2166136261;
        hash = hash * 25165843 ^ (Name != null ? Name .GetHashCode() : 0);
        hash = hash * 25165843 ^ (Address != null ? Address.GetHashCode() : 0);

        return hash;
    }
}

问题是当导航属性中的PersonsICollection 实现时。它缺少彼此“相等”的记录(即返回单个 John Smith 123 A Street 记录)。我猜这是因为默认情况下它会考虑具有唯一主键的不同实体。通过覆盖 equals 它认为两条记录是同一个实体。

显示Addresses 而不是Persons 的屏幕截图:(顶部是IEquatable,底部是没有)

//Addresses Definition (generated code)
public virtual ICollection<Address> Addresses { get; set; }

我如何协调 EF 需要在对象级别查看平等与我希望看到逻辑平等?

【问题讨论】:

  • 你能在EmployeeElection上显示Addresses属性的声明吗?
  • @CharlesMager 当然,我已经在屏幕截图下添加了它。它是生成的 EF 代码的一部分。
  • 你在构造函数中设置为new Hashset&lt;Address&gt;()吗?在 EF 样本中经常出现这种情况。另外,virtual 关键字允许延迟加载,创建的 EF 代理可能使用ISet&lt;Address&gt;(我不知道具体情况!)。设置语义意味着无法添加重复项,并且默认情况下它们将使用默认的相等比较器(因此您的 Equals 覆盖)。因此,尽管查询可能返回重复项,但模型/EF 不会显示它们。
  • @CharlesMager 不,我没有在构造函数中设置任何东西。 ISet 很有趣,这可以解释我的问题。
  • @CharlesMager I found something on that 它说如果虚拟属性由ICollection 类型支持,那么他们会将List 放入其中。当然这是来自 EF4,我不确定它是否从 EF6 改变。

标签: c# entity-framework entity-framework-6 equality iequatable


【解决方案1】:

钥匙好像在EF source code

EntityUtil.DetermineCollectionType(Type requestedType) 的备注中,有这些“规则”:

    // The rules are:
    // If the collection is defined as a concrete type with a publicly accessible parameterless constructor, then create an instance of that type
    // Else, if HashSet{T} can be assigned to the type, then use HashSet{T}
    // Else, if List{T} can be assigned to the type, then use List{T}
    // Else, throw a nice exception.

因此,从这里看来,EF 将为您的导航属性新建一个HashSet&lt;Address&gt;。这将使用默认的相等比较器并防止添加任何重复项。由于您的 Equals 实现将您的两个结果标识为相等,因此只会包含一个。

实体通常是唯一标识的 - 覆盖 Equals 忽略唯一标识符可能是不正确的。最好的解决方案是删除覆盖并实现单独的IEqualityComparer。大多数使用相等语义的方法都会将此作为参数。

【讨论】:

  • 这绝对是它。谢谢。
【解决方案2】:

IEquatableIEqualityComparer 是 LINQ to 对象所独有的概念。 EF 无法将任何以这种方式定义的“相等”定义转换为 SQL,因此无法执行此类操作。

要根据某些列获取不同的项目,以一种可以翻译成 SQL 的方式,只需根据这些值对项目进行分组,然后从每个组中获取一个项目:

var query = context.Table.GroupBy(row => new
    {
        row.Name,
        row.Address,
    })
    .Select(group => group.FirstOrDefault());

【讨论】:

  • 我没有遇到不同项目的问题,我有兴趣回答“我有两个 Person 对象,它们在逻辑上是否相同?”的问题。
  • @Shoe 不管怎样,如果您的查询没有返回您想要的结果,则需要查看该查询并调整它使用的运算符或使用它们的方式。答案的第一段仍然有效; IEquatable 根本无法被 EF 解释。
  • 我明白这一点,但我没有任何查询做任何事情。如果我只使用代码context.Persons.ToList(),则物化集将不会包含多个“相等”的记录。我需要在上面的评论中回答这个问题,但是实现 IEquatable 似乎完全破坏了实体的加载。
  • @Shoe 如果您只是将整个表拉到一个列表中,它不会删除根据IEquatable 相等定义相等的项目。那只是不会发生。要么你没有你认为你拥有的数据,你在没有意识到的情况下过滤了它,你没有正确检查没有“相等”的项目,或者类似的东西。
  • 我已将屏幕截图添加到 OP。它是一个导航属性,没有过滤或任何东西。
【解决方案3】:

不要使用IEquatable 等,创建自己的AreEquivalentIsEquivalentTo 方法。

【讨论】:

  • 这些方法如何帮助他从数据库中获取不同的项目?
  • 使用.GroupBy(x =&gt; new { x.Name, x.Address }).Select(g =&gt; g.First()) ...实际上是DistinctBy
猜你喜欢
  • 1970-01-01
  • 2014-10-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-07
  • 1970-01-01
  • 2011-04-09
相关资源
最近更新 更多