IEquatable 中断 Entity Framework 个实体的加载

IEquatable breaks loading of Entity Framework entities

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

Person table:

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

记录 1 和 2 为 "equal"。记录 2 和 3 是 "not equal".

我在模型 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;
    }
}

问题是当来自导航 属性 的 Persons ICollection 被具体化时。它缺少彼此的记录 "equal"(即返回单个 John Smith 123 A Street 记录)。我猜这是因为默认情况下它会考虑具有唯一主键的不同实体。通过覆盖 equals,它认为两个记录是同一个实体。

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

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

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

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

IEquatableIEqualityComparer 是 LINQ to objects 几乎独有的概念。 EF 无法将以这种方式定义的 "equality" 的任何定义转换为 SQL,因此无法执行此类操作。

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

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

钥匙好像在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<Address>。这将使用默认的相等比较器并防止添加任何重复项。由于您的 Equals 实施将您的两个结果识别为相等,因此只会包括一个。

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