簡體   English   中英

實體框架實體的最佳做法將覆蓋Equals和GetHashCode

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

我想檢查兩個實體之間是否存在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嗎?

實體框架使用其自己的智能方法來檢測對象相等性。 例如,這在調用SaveChanges :獲取的對象的值與更新的對象的值匹配,以檢測是否需要SQL更新。

我不確定您的相等性定義是否會與該相等性檢查混淆,從而導致某些未更改的項目在數據庫中被更新,或更糟糕的是,某些已更改的數據不會在數據庫中被更新。

數據庫平等

請記住,實體類(放在DbSet<...> )代表數據庫中的表以及這些表之間的關系。

從數據庫中提取的兩個項目何時應被視為代表同一對象? 它們具有相同的值嗎? 我們不能在7月4日在一個數據庫中有兩個名為“ John Doe”的人嗎?

你可以用它來檢測兩個提取的唯一方法Persons從數據庫中代表相同的Person是通過檢查標識。 某些非主鍵值不同的事實只會告訴您更改的數據不會在數據庫中更新,而不是它是不同的Person

覆蓋等於vs創建EqualComparer

我的建議是,使表的表示形式盡可能簡單:僅表的列(非虛擬屬性)和表之間的關系(虛擬屬性)。 沒有成員,沒有方法,什么都沒有。

如果需要其他功能,請創建類的擴展功能。 如果需要非標准的相等比較器,請創建一個單獨的相等比較器。 班級的用戶可以決定是否要使用默認比較方法還是特殊的比較方法。

這可以與各種String比較器進行比較: 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和類型之外,我在進行相等檢查的方式中還遇到其他一些問題:

  • 可以將null分配給您的Gpus集合。 您必須解決它不會引發異常的問題。 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);
    }
}

待辦事項:如果您要使用大量的GPU,請考慮使用更智能的GetHashCode

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM