2

one-to-many内部に関係がある2 つのエンティティ間の同等性を確認したいと考えています。

したがって、明らかにObject.Equalsメソッドをオーバーライドしましたが、CS0659コンパイラ警告が表示されます'class' overrides Object.Equals(object o) but does not override Object.GetHashCode()

私は をObject.GetHashCodeオーバーライドしましたが、Resharper は、このGetHashCodeメソッドはすべてのオブジェクトのライフサイクルで同じ結果を返す必要があり、変更可能なオブジェクトで使用されると教えてくれました。(ドキュメント)

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不変データでオーバーライドしますか?
4

1 に答える 1

5

Entity Framework は、独自のスマート メソッドを使用してオブジェクトの等価性を検出します。これは、たとえば を呼び出す場合に使用されますSaveChanges。フェッチされたオブジェクトの値が更新されたオブジェクトの値と照合され、SQL 更新が必要かどうかが検出されます。

等価性の定義がこの等価性チェックを台無しにして、変更されていないアイテムがデータベースで更新されたり、さらに悪いことに、変更されたデータがデータベースで更新されなかったりするかどうかはわかりません。

データベースの等価性

エンティティ クラス ( に配置するクラスDbSet<...>) は、データベース内のテーブルとテーブル間の関係を表すことに注意してください。

データベースから抽出された 2 つのアイテムが同じオブジェクトを表していると見なされるのはいつですか? 彼らが同じ価値観を持っているときですか?1 つのデータベースに 7 月 4 日生まれの「John Doe」という名前の 2 人の人物を含めることはできませんか?

Personsデータベースから抽出された 2 つが同じであることを検出するために使用できる唯一の方法Personは、ID を確認することです。一部の非主キー値が異なるという事実は、変更されたデータがデータベースで更新されていないことを示しているだけであり、異なるPerson.

Equals のオーバーライドと EqualityComparer の作成

私のアドバイスは、テーブルの表現をできるだけシンプルに保つことです。テーブルの列 (非仮想プロパティ) とテーブル間の関係 (仮想プロパティ) のみです。メンバーもメソッドも何もありません。

追加の機能が必要な場合は、クラスの拡張関数を作成します。非標準の等値比較メソッドが必要な場合は、別の等値比較子を作成してください。クラスのユーザーは、デフォルトの比較方法を使用するか、特別な比較方法を使用するかを決定できます。

これはすべて、さまざまな種類の文字列比較器 ( StringComparer.OrdinalIgnorCaseStringComparer.InvariantCulture、など) と比較できます。

質問に戻る

Id の値をチェックしない Gpu 比較子が必要なようです。ID が異なりますが、他のプロパティの値が同じである 2 つのアイテムは等しいと見なされます。

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) ではないため、同じ型のテストを追加したことに注意してください。これは等価関数の前提条件の 1 つです

使用法:

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

Computer の比較子も同様です。null と型をチェックするのを忘れたことを除けば、等値チェックを行う方法に他の問題がいくつか見られます。

  • Gpu のコレクションに null を割り当てることができます。例外をスローしないようにこれを解決する必要があります。Gpu がゼロのコンピューターは、Gpu がゼロのコンピューターと同じですか?
  • どうやら Gpus の順序は重要ではありません: [1, 3] は [3, 1] に等しい
  • 特定の GPU が出現する回数は明らかに重要ではありません: [1, 1, 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 を検討してください。

于 2019-01-02T12:23:33.203 に答える