简体   繁体   English

实体框架实体的最佳做法将覆盖Equals和GetHashCode

[英]Best practices for Entity Framework entities override Equals and GetHashCode

I want to check equality between two entities with one-to-many relationships inside them. 我想检查两个实体之间是否存在one-to-many关系的相等性。

So obviously I overrode the Object.Equals method, but then I get the CS0659 compiler warning: 'class' overrides Object.Equals(object o) but does not override Object.GetHashCode() . 所以很明显我覆盖了Object.Equals方法,但是随后我得到了CS0659编译器警告: 'class' overrides Object.Equals(object o) but does not override Object.GetHashCode()

I overrode the Object.GetHashCode , but then, Resharper told me that the GetHashCode method should return the same result for all object life cycle, and will used in mutable objects. 我覆盖了Object.GetHashCode ,但是随后Resharper告诉我, GetHashCode方法应该在所有对象生命周期中返回相同的结果,并将在可变对象中使用。 ( docs ) docs

public class Computer
{
    public long Id { get; set; }
    public ICollection<GPU> GPUs { get; set; } = new List<GPU>();

    public override bool Equals(object obj)
    {
        return obj is Computer computer &&
               GPUs.All(computer.GPUs.Contains);
    }

    public override int GetHashCode()
    {
        return HashCode.Combine(GPUs);
    }
}

public class GPU
{
    public long Id { get; set; }
    public int? Cores { get; set; } = null;

    public override bool Equals(object obj)
    {
        return obj is GPU gpu &&
               Cores == gpu.Cores;
    }

    public override int GetHashCode()
    {
        return HashCode.Combine(Cores);
    }
}

I don't know what should I prefer: 我不知道我该喜欢什么:

  • Overriding the Equals method without overriding GetHashCode , or 覆盖Equals方法而不覆盖GetHashCode ,或者
  • Overriding the GetHashCode with immutable data? 用不可变数据覆盖GetHashCode吗?

Entity Framework uses its own smart methods to detect object equality. 实体框架使用其自己的智能方法来检测对象相等性。 This is for instance used if you call SaveChanges : the values of fetched objects are matched with the values of updated objects to detect whether a SQL update is needed or not. 例如,这在调用SaveChanges :获取的对象的值与更新的对象的值匹配,以检测是否需要SQL更新。

I'm not sure whether your definitions of equality would mess with this equality checking, causing some unchanged items to be updated in the database, or even worse, some changed data not to be updated in the database. 我不确定您的相等性定义是否会与该相等性检查混淆,从而导致某些未更改的项目在数据库中被更新,或更糟糕的是,某些已更改的数据不会在数据库中被更新。

Database equality 数据库平等

Keep in mind that your entity classes (the classes that you put in the DbSet<...> ) represent the tables in your database and the relations between the tables. 请记住,实体类(放在DbSet<...> )代表数据库中的表以及这些表之间的关系。

When should two items extracted from your database considered to represent the same object? 从数据库中提取的两个项目何时应被视为代表同一对象? Is it when they have same values? 它们具有相同的值吗? Can't we have two Persons named "John Doe", born on the 4th of July in one database? 我们不能在7月4日在一个数据库中有两个名为“ John Doe”的人吗?

The only way you can use to detect that two extracted Persons from the database represent the same Person is by checking the Id. 你可以用它来检测两个提取的唯一方法Persons从数据库中代表相同的Person是通过检查标识。 The fact that some non-primary key values differ only tells you that the changed data is not updated in the database, not that it is a different Person . 某些非主键值不同的事实只会告诉您更改的数据不会在数据库中更新,而不是它是不同的Person

Override Equals vs Create EqualityComparer 覆盖等于vs创建EqualComparer

My advice would be, to keep your table representations as simple as possible: only the columns of the table (non-virtual properties) and the relations between the tables (virtual properties). 我的建议是,使表的表示形式尽可能简单:仅表的列(非虚拟属性)和表之间的关系(虚拟属性)。 No members, no Methods, nothing. 没有成员,没有方法,什么都没有。

If you need extra functionality, create extension functions of the classes. 如果需要其他功能,请创建类的扩展功能。 If you need non-standard equality comparison methods, create a separate equality comparer. 如果需要非标准的相等比较器,请创建一个单独的相等比较器。 Users of your class can decide whether they want to use the default comparison method or your special comparison method. 班级的用户可以决定是否要使用默认比较方法还是特殊的比较方法。

This is all comparable as the various kinds of String Comparers: StringComparer.OrdinalIgnorCase , StringComparer.InvariantCulture , etc. 这可以与各种String比较器进行比较: StringComparer.OrdinalIgnorCaseStringComparer.InvariantCulture等。

Back to your question 回到您的问题

It seems to me that you want a Gpu comparer that does not check the value of Id: two items that have different Id, but same values for other properties are considered equal. 在我看来,您想要一个不检查Id值的Gpu比较器:两个具有不同Id,但其他属性相同的值被认为是相等的。

class GpuComparer : EqualityComparer<Gpu>
{
    public static IEqualityComparer<Gpu> IgnoreIdComparer {get;} = new GpuComparer()

    public override bool Equals(Gpu x, Gpu y)
    {
        if (x == null) return y == null; // true if both null, false if x null but y not
        if (y == null) return false;     // because x not null
        if (Object.ReferenceEquals(x, y)) return true;
        if (x.GetType() != y.GetType()) return false;

        // if here, we know x and y both not null, and of same type.
        // compare all properties for equality
        return x.Cores == y.Cores;
    }
    public override int GetHasCode(Gpu x)
    {
        if (x == null) throw new ArgumentNullException(nameof(x));

         // note: I want a different Hash for x.Cores == null than x.Cores == 0!

         return (x.Cores.HasValue) ? return x.Cores.Value.GetHashCode() : -78546;
         // -78546 is just a value I expect that is not used often as Cores;
    }
}

Note that I added the test for same type, because if y is a derived class of Gpu, and you would ignore that they are not the same type, then maybe Equals(x, y), but not Equals(y, x), which is one of the prerequisites of equality functions 请注意,我添加了针对同一类型的测试,因为如果y是Gpu的派生类,并且您会忽略它们不是同一类型,那么可能是Equals(x,y),而不是Equals(y,x),这是相等函数的前提条件之一

Usage: 用法:

IEqualityComparer<Gpu> gpuIgnoreIdComparer = GpuComparer.IgnoreIdComparer;
Gpu x = new Gpu {Id = 0, Cores = null}
Gpu y = new Gpu {Id = 1, Cores = null}

bool sameExceptForId = gpuIgnoreIdComparer.Equals(x, y);

x and y will be considered equal x和y将被视为相等

HashSet<Gpu> hashSetIgnoringIds = new HashSet<Gpu>(GpuComparer.IgnoreIdComparer);
hashSetIgnoringIds.Add(x);
bool containsY = hashSetIgnoringIds.Contains(y); // expect true

A comparer for Computer will be similar. 计算机的比较器将类似。 Apart that you forgot to check for null and types, I see some other problems in the way you want to do the equality checking: 除了您忘了检查null和类型之外,我在进行相等检查的方式中还遇到其他一些问题:

  • it is possible to assign null to your collection of Gpus. 可以将null分配给您的Gpus集合。 You have to solve this that it does not throw an exception. 您必须解决它不会引发异常的问题。 Is a Computer with null Gpus equal to a Computer with zero Gpus? Gpus为空的计算机等于Gpus为零的计算机吗?
  • Apparently the order of the Gpus is not important to you: [1, 3] is equal to [3, 1] 显然,GPU的顺序对您并不重要:[1,3]等于[3,1]
  • Apparently the number of times that a certain GPU appears is not important: [1, 1, 3] is equal to [1, 3, 3]? 显然,某个GPU出现的次数并不重要:[1、3、3]等于[1、3、3]?

.

class IgnoreIdComputerComparer : EqualityComparer<Computer>
{
    public static IEqualityComparer NoIdComparer {get} = new IgnoreIdComputerCompare();


    public override bool (Computer x, Computer y)
    {
        if (x == null) return y == null;not null
        if (y == null) return false;
        if (Object.ReferenceEquals(x, y)) return true;
        if (x.GetType() != y.GetType())  return false;

        // equal if both GPU collections null or empty,
        // or any element in X.Gpu is also in Y.Gpu ignoring duplicates
        // using the Gpu IgnoreIdComparer
        if (x.Gpus == null || x.Gpus.Count == 0)
            return y.Gpus == null || y.Gpus.Count == 0;

        // equal if same elements, ignoring duplicates:
        HashSet<Gpu> xGpus = new HashSet<Gpu>(x, GpuComparer.IgnoreIdComparer);
        return xGpush.EqualSet(y);
    }

    public override int GetHashCode(Computer x)
    {
        if (x == null) throw new ArgumentNullException(nameof(x));

        if (x.Gpus == null || x.Gpus.Count == 0) return -784120;

         HashSet<Gpu> xGpus = new HashSet<Gpu>(x, GpuComparer.IgnoreIdComparer);
         return xGpus.Sum(gpu => gpu);
    }
}

TODO: if you will be using large collections of Gpus, consider a smarter GetHashCode 待办事项:如果您要使用大量的GPU,请考虑使用更智能的GetHashCode

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM