値の比較演算子Value Comparers

注意

この機能は EF Core 3.0 で新たに追加されています。This feature is new in EF Core 3.0.

ヒント

このドキュメントのコードは、実行可能な サンプルとして GitHub にあります。The code in this document can be found on GitHub as a runnable sample.

背景Background

EF Core では、次の場合にプロパティ値を比較する必要があります。EF Core needs to compare property values when:

  • 更新プログラムの変更の検出の一部としてプロパティが変更されているかどうかを確認するDetermining whether a property has been changed as part of detecting changes for updates
  • リレーションシップの解決時に2つのキー値が等しいかどうかを判断するDetermining whether two key values are the same when resolving relationships

これは、int、bool、DateTime などの一般的なプリミティブ型に対して自動的に処理されます。This is handled automatically for common primitive types such as int, bool, DateTime, etc.

より複雑な型については、比較の実行方法を選択する必要があります。For more complex types, choices need to be made as to how to do the comparison. たとえば、バイト配列は次のように比較できます。For example, a byte array could be compared:

  • 新しいバイト配列が使用されている場合にのみ、違いが検出されるように、参照渡しBy reference, such that a difference is only detected if a new byte array is used
  • 詳細な比較により、配列内のバイトの変化が検出されます。By deep comparison, such that mutation of the bytes in the array is detected

既定では、EF Core は、キー以外のバイト配列に対してこれらの方法の1つ目を使用します。By default, EF Core uses the first of these approaches for non-key byte arrays. つまり、参照のみが比較され、既存のバイト配列が新しいバイト配列に置き換えられた場合にのみ、変更が検出されます。That is, only references are compared and a change is detected only when an existing byte array is replaced with a new one. これは、SaveChanges を実行するときに多くの大きなバイト配列の深い比較を回避する、実際的な決定です。This is a pragmatic decision that avoids deep comparison of many large byte arrays when executing SaveChanges. ただし、別のイメージを使用したイメージがパフォーマンスの高い方法で処理されるという一般的なシナリオがあります。But the common scenario of replacing, say, an image with a different image is handled in a performant way.

一方、バイナリキーを表すためにバイト配列が使用されている場合、参照の等価性は機能しません。On the other hand, reference equality would not work when byte arrays are used to represent binary keys. FK プロパティが、比較する必要のある PK プロパティと 同じインスタンス に設定されていることはほとんどありません。It's very unlikely that an FK property is set to the same instance as a PK property to which it needs to be compared. そのため、EF Core は、キーとして機能するバイト配列の詳細な比較を使用します。Therefore, EF Core uses deep comparisons for byte arrays acting as keys. これは、通常、バイナリキーが短いため、パフォーマンスが大幅に低下する可能性があります。This is unlikely to have a big performance hit since binary keys are usually short.

スナップショットSnapshots

変更可能な型の詳細な比較では、プロパティ値の詳細な "スナップショット" を作成する機能が EF Core に必要です。Deep comparisons on mutable types means that EF Core needs the ability to create a deep "snapshot" of the property value. 代わりに参照をコピーするだけで、現在の値とスナップショットの両方が変更されます。これは、 _同じオブジェクト_であるためです。Just copying the reference instead would result in mutating both the current value and the snapshot, since they are the same object. したがって、変更可能な型に対して詳細比較を使用する場合は、ディープスナップショットも必要です。Therefore, when deep comparisons are used on mutable types, deep snapshotting is also required.

値コンバーターを持つプロパティProperties with value converters

上の例では、EF Core によってバイト配列のネイティブマッピングがサポートされているため、適切な既定値を自動的に選択できます。In the case above, EF Core has native mapping support for byte arrays and so can automatically choose appropriate defaults. ただし、プロパティが 値コンバーターによってマップされている場合、EF Core は、必ず適切な比較を決定できません。However, if the property is mapped through a value converter, then EF Core can't always determine the appropriate comparison to use. 代わりに、EF Core は常に、プロパティの型によって定義された既定の等値比較を使用します。Instead, EF Core always uses the default equality comparison defined by the type of the property. 多くの場合、これは適切ですが、より複雑な型をマップするときにオーバーライドする必要があります。This is often correct, but may need to be overridden when mapping more complex types.

単純な変更できないクラスSimple immutable classes

値コンバーターを使用して単純な変更できないクラスをマップするプロパティについて考えてみます。Consider a property that uses a value converter to map a simple, immutable class.

public sealed class ImmutableClass
{
    public ImmutableClass(int value)
    {
        Value = value;
    }

    public int Value { get; }

    private bool Equals(ImmutableClass other)
        => Value == other.Value;

    public override bool Equals(object obj)
        => ReferenceEquals(this, obj) || obj is ImmutableClass other && Equals(other);

    public override int GetHashCode()
        => Value;
}
modelBuilder
    .Entity<MyEntityType>()
    .Property(e => e.MyProperty)
    .HasConversion(
        v => v.Value,
        v => new ImmutableClass(v));

この型のプロパティでは、次の理由により、特別な比較やスナップショットは必要ありません。Properties of this type do not need special comparisons or snapshots because:

  • 異なるインスタンスが正しく比較されるように、等値がオーバーライドされます。Equality is overridden so that different instances will compare correctly
  • 型は不変であるため、スナップショットの値を変更する可能性はありません。The type is immutable, so there is no chance of mutating a snapshot value

この場合、EF Core の既定の動作はそのままです。So in this case the default behavior of EF Core is fine as it is.

単純な変更できない構造体Simple immutable Structs

単純な構造体のマッピングも単純であり、特別な比較子やスナップショットは必要ありません。The mapping for simple structs is also simple and requires no special comparers or snapshotting.

public readonly struct ImmutableStruct
{
    public ImmutableStruct(int value)
    {
        Value = value;
    }

    public int Value { get; }
}
modelBuilder
    .Entity<EntityType>()
    .Property(e => e.MyProperty)
    .HasConversion(
        v => v.Value,
        v => new ImmutableStruct(v));

EF Core には、構造体のプロパティのコンパイル済みのメンバーごとの比較を生成するためのサポートが組み込まれています。EF Core has built-in support for generating compiled, memberwise comparisons of struct properties. これは、構造体が EF に対して等値オーバーライドを必要としないことを意味しますが、 他の理由でこれを行うこともできます。This means structs don't need to have equality overridden for EF, but you may still choose to do this for other reasons. また、特別なスナップショットは必要ありません。構造体は変更できず、常にメンバーごとにコピーされるためです。Also, special snapshotting is not needed since structs immutable and are always memberwise copied anyway. (これは変更可能な構造体にも当てはまりますが、変更 可能な構造体は一般に避ける必要があります)。(This is also true for mutable structs, but mutable structs should in general be avoided.)

変更可能なクラスMutable classes

可能な限り、変更できない型 (クラスまたは構造体) を値コンバーターと共に使用することをお勧めします。It is recommended that you use immutable types (classes or structs) with value converters when possible. これは通常、より効率的であり、変更可能な型を使用する場合よりも明確なセマンティクスを持ちます。This is usually more efficient and has cleaner semantics than using a mutable type.

ただし、このような場合は、アプリケーションが変更できない型のプロパティを使用するのが一般的です。However, that being said, it is common to use properties of types that the application cannot change. たとえば、次のように、数値のリストを含むプロパティをマッピングします。For example, mapping a property containing a list of numbers:

public List<int> MyProperty { get; set; }

List<T> クラス:The List<T> class:

  • 参照の等価性があります。同じ値を含む2つのリストは、異なるものとして扱われます。Has reference equality; two lists containing the same values are treated as different.
  • 変更可能です。リスト内の値は、追加および削除できます。Is mutable; values in the list can be added and removed.

リストプロパティの一般的な値の変換では、リストを JSON との間で変換することができます。A typical value conversion on a list property might convert the list to and from JSON:

modelBuilder
    .Entity<EntityType>()
    .Property(e => e.MyProperty)
    .HasConversion(
        v => JsonSerializer.Serialize(v, null),
        v => JsonSerializer.Deserialize<List<int>>(v, null));

そのためには、プロパティにを設定して、 ValueComparer<T> この変換で正しい比較を使用 EF Core ようにする必要があります。This then requires setting a ValueComparer<T> on the property to force EF Core use correct comparisons with this conversion:

    var valueComparer = new ValueComparer<List<int>>(
        (c1, c2) => c1.SequenceEqual(c2),
        c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
        c => c.ToList());

    modelBuilder
        .Entity<EntityType>()
        .Property(e => e.MyProperty)
        .Metadata
        .SetValueComparer(valueComparer);

注意

値の比較子を設定するためのモデルビルダー ("fluent") API は、まだ実装されていません。The model builder ("fluent") API to set a value comparer has not yet been implemented. 代わりに、上記のコードは、ビルダーによって公開されている下位レベルの IMutableProperty に対して ' Metadata ' として SetValueComparer を呼び出します。Instead, the code above calls SetValueComparer on the lower-level IMutableProperty exposed by the builder as 'Metadata'.

コンストラクターは、 ValueComparer<T> 次の3つの式を受け取ります。The ValueComparer<T> constructor accepts three expressions:

  • 等しいかどうかをチェックする式An expression for checking equality
  • ハッシュコードを生成するための式An expression for generating a hash code
  • 値をスナップショットにする式An expression to snapshot a value

この場合、比較は、数値のシーケンスが同じであるかどうかをチェックすることによって行われます。In this case the comparison is done by checking if the sequences of numbers are the same.

同様に、ハッシュコードは同じ順序で作成されます。Likewise, the hash code is built from this same sequence. (これは変更可能な値を超えるハッシュコードであるため、 問題が発生する可能性があります。(Note that this is a hash code over mutable values and hence can cause problems. 可能であれば、変更不可にしてください。)Be immutable instead if you can.)

スナップショットは、リストを ToList で複製することによって作成されます。The snapshot is created by cloning the list with ToList. ここでも、リストを変換する場合にのみ必要です。Again, this is only needed if the lists are going to be mutated. 可能であれば、変更できないようにします。Be immutable instead if you can.

注意

値コンバーターと比較子は、単純なデリゲートではなく、式を使用して構築されます。Value converters and comparers are constructed using expressions rather than simple delegates. これは、EF によって、これらの式がより複雑な式ツリーに挿入され、その後、entity shaper デリゲートにコンパイルされるためです。This is because EF inserts these expressions into a much more complex expression tree that is then compiled into an entity shaper delegate. 概念的には、これはコンパイラのインライン展開に似ています。Conceptually, this is similar to compiler inlining. たとえば、単純な変換は、別のメソッドを呼び出して変換を行うのではなく、キャストでコンパイルされるだけである場合があります。For example, a simple conversion may just be a compiled in cast, rather than a call to another method to do the conversion.

キー比較子Key comparers

[背景] セクションでは、キーの比較によって特殊なセマンティクスが必要になる理由を説明します。The background section covers why key comparisons may require special semantics. プライマリ、プリンシパル、または外部キーのプロパティでキーを設定するときに、キーに適した比較子を作成してください。Make sure to create a comparer that is appropriate for keys when setting it on a primary, principal, or foreign key property.

同じプロパティに異なるセマンティクスが必要なまれなケースでは、 Setkeyvaluecomparer を使用します。Use SetKeyValueComparer in the rare cases where different semantics is required on the same property.

注意

SetStructuralComparer は EF Core 5.0 で廃止されました。SetStructuralComparer has been obsoleted in EF Core 5.0. 代わりに、SetKeyValueComparer を使用してください。Use SetKeyValueComparer instead.

既定値のオーバーライドOverriding defaults

EF Core によって使用される既定の比較が適切でない場合があります。Sometimes the default comparison used by EF Core may not be appropriate. たとえば、バイト配列の変異は、既定では EF Core で検出されません。For example, mutation of byte arrays is not, by default, detected in EF Core. これは、プロパティで別の比較子を設定することによってオーバーライドできます。This can be overridden by setting a different comparer on the property:

modelBuilder
    .Entity<EntityType>()
    .Property(e => e.MyBytes)
    .Metadata
    .SetValueComparer(new ValueComparer<byte[]>(
        (c1, c2) => c1.SequenceEqual(c2),
        c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
        c => c.ToArray()));

EF Core では、バイトシーケンスを比較するため、バイト配列変更が検出されます。EF Core will now compare byte sequences and will therefore detect byte array mutations.