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

时间:2018-12-30 13:38:10

标签: c# entity-framework object-equality

我想检查两个内部具有one-to-many关系的实体是否相等。

因此很明显,我改写了Object.Equals方法,但随后得到了CS0659编译器警告:'class' overrides Object.Equals(object o) but does not override Object.GetHashCode()

我推翻了Object.GetHashCode,但是随后Resharper告诉我,GetHashCode方法应该在所有对象生命周期中返回相同的结果,并将在可变对象中使用。 (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);
    }
}

我不知道我该怎么办:

  • 覆盖Equals方法而不覆盖GetHashCode,或者
  • 用不变的数据覆盖GetHashCode

1 个答案:

答案 0 :(得分:2)

实体框架使用其自己的智能方法来检测对象相等性。例如,这在调用SaveChanges时使用:获取的对象的值与更新的对象的值匹配,以检测是否需要SQL更新。

我不确定您的相等性定义是否会与这种相等性检查相混淆,从而导致某些未更改的项目在数据库中被更新,甚至更糟的是,某些已更改的数据不在数据库中被更新。

数据库相等性

请记住,您的实体类(放在DbSet<...>中的类)代表数据库中的表以及表之间的关系。

何时应将从数据库中提取的两个项目视为代表同一对象?它们具有相同的值吗?我们不能在7月4日在一个数据库中有两个名为“ John Doe”的人吗?

唯一可用于检测从数据库中提取的两个Persons代表相同的Person的方法是检查ID。某些非主键值不同的事实只会告诉您更改后的数据不会在数据库中更新,而不是不同的Person

覆盖等于vs创建EqualityComparer

我的建议是,使表表示尽可能简单:仅表的列(非虚拟属性)和表之间的关系(虚拟属性)。没有成员,没有方法,什么都没有。

如果需要其他功能,请创建类的扩展功能。如果需要非标准的相等比较器,请创建一个单独的相等比较器。班级的用户可以决定要使用默认比较方法还是特殊的比较方法。

这可以与各种字符串比较器(StringComparer.OrdinalIgnorCaseStringComparer.InvariantCulture等)进行比较。

返回您的问题

在我看来,您想要一个不检查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;
    }
}

请注意,我添加了针对同一类型的测试,因为如果y是Gpu的派生类,而您会忽略它们不是同一类型,则可能是Equals(x,y),而不是Equals(y, x),这是相等函数的先决条件之一

用法:

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和y将被视为

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

计算机比较器将类似。除了您忘了检查null和类型之外,我在进行相等检查的方式中还遇到其他一些问题:

  • 可以为您的Gpu集合分配null。您必须解决它不会引发异常的问题。 Gpus为空的计算机等于Gpus为零的计算机吗?
  • 显然,GPU的顺序对您并不重要:[1,3]等于[3,1]
  • 显然某个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:如果您要使用大量的GPU,请考虑使用更智能的GetHashCode