|
| 1 | +--- |
| 2 | +title: Value Comparers - EF Core |
| 3 | +description: Using value comparers to control how EF Core compares property values |
| 4 | +author: ajcvickers |
| 5 | +ms.date: 03/20/2020 |
| 6 | +uid: core/modeling/value-comparers |
| 7 | +--- |
| 8 | + |
| 9 | +# Value Comparers |
| 10 | + |
| 11 | +> [!NOTE] |
| 12 | +> This feature is new in EF Core 3.0. |
| 13 | +
|
| 14 | +> [!TIP] |
| 15 | +> The code in this document can be found on GitHub as a [runnable sample](https://github.com/dotnet/EntityFramework.Docs/tree/master/samples/core/Modeling/ValueConversions/). |
| 16 | +
|
| 17 | +## Background |
| 18 | + |
| 19 | +EF Core needs to compare property values when: |
| 20 | + |
| 21 | +* Determining whether a property has been changed as part of [detecting changes for updates](xref:core/saving/basic) |
| 22 | +* Determining whether two key values are the same when resolving relationships |
| 23 | + |
| 24 | +This is handled automatically for common primitive types such as int, bool, DateTime, etc. |
| 25 | + |
| 26 | +For more complex types, choices need to be made as to how to do the comparison. |
| 27 | +For example, a byte array could be compared: |
| 28 | + |
| 29 | +* By reference, such that a difference is only detected if a new byte array is used |
| 30 | +* By deep comparison, such that mutation of the bytes in the array is detected |
| 31 | + |
| 32 | +By default, EF Core uses the first of these approaches for non-key byte arrays. |
| 33 | +That is, only references are compared and a change is detected only when an existing byte array is replaced with a new one. |
| 34 | +This is a pragmatic decision that avoids deep comparison of many large byte arrays when executing SaveChanges. |
| 35 | +But the common scenario of replacing, say, an image with a different image is handled in a performant way. |
| 36 | + |
| 37 | +On the other hand, reference equality would not work when byte arrays are used to represent binary keys. |
| 38 | +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. |
| 39 | +Therefore, EF Core uses deep comparisons for byte arrays acting as keys. |
| 40 | +This is unlikely to have a big performance hit since binary keys are usually short. |
| 41 | + |
| 42 | +### Snapshots |
| 43 | + |
| 44 | +Deep comparisons on mutable types means that EF Core needs the ability to create a deep "snapshot" of the property value. |
| 45 | +Just copying the reference instead would result in mutating both the current value and the snapshot, since they are _the same object_. |
| 46 | +Therefore, when deep comparisons are used on mutable types, deep snapshotting is also required. |
| 47 | + |
| 48 | +## Properties with value converters |
| 49 | + |
| 50 | +In the case above, EF Core has native mapping support for byte arrays and so can automatically choose appropriate defaults. |
| 51 | +However, if the property is mapped through a [value converter](xref:core/modeling/value-conversions), then EF Core can't always determine the appropriate comparison to use. |
| 52 | +Instead, EF Core always uses the default equality comparison defined by the type of the property. |
| 53 | +This is often correct, but may need to be overridden when mapping more complex types. |
| 54 | + |
| 55 | +### Simple immutable classes |
| 56 | + |
| 57 | +Consider a property the uses a value converter to map a simple, immutable class. |
| 58 | + |
| 59 | +[!code-csharp[SimpleImmutableClass](../../../samples/core/Modeling/ValueConversions/MappingImmutableClassProperty.cs?name=SimpleImmutableClass)] |
| 60 | + |
| 61 | +[!code-csharp[ConfigureImmutableClassProperty](../../../samples/core/Modeling/ValueConversions/MappingImmutableClassProperty.cs?name=ConfigureImmutableClassProperty)] |
| 62 | + |
| 63 | +Properties of this type do not need special comparisons or snapshots because: |
| 64 | +* Equality is overridden so that different instances will compare correctly |
| 65 | +* The type is immutable, so there is no chance of mutating a snapshot value |
| 66 | + |
| 67 | +So in this case the default behavior of EF Core is fine as it is. |
| 68 | + |
| 69 | +### Simple immutable Structs |
| 70 | + |
| 71 | +The mapping for simple structs is also simple and requires no special comparers or snapshotting. |
| 72 | + |
| 73 | +[!code-csharp[SimpleImmutableStruct](../../../samples/core/Modeling/ValueConversions/MappingImmutableStructProperty.cs?name=SimpleImmutableStruct)] |
| 74 | + |
| 75 | +[!code-csharp[ConfigureImmutableStructProperty](../../../samples/core/Modeling/ValueConversions/MappingImmutableStructProperty.cs?name=ConfigureImmutableStructProperty)] |
| 76 | + |
| 77 | +EF Core has built-in support for generating compiled, memberwise comparisons of struct properties. |
| 78 | +This means structs don't need to have equality overridden for EF, but you may still choose to do this for [other reasons](/dotnet/csharp/programming-guide/statements-expressions-operators/how-to-define-value-equality-for-a-type). |
| 79 | +Also, special snapshotting is not needed since structs immutable and are always memberwise copied anyway. |
| 80 | +(This is also true for mutable structs, but [mutable structs should in general be avoided](/dotnet/csharp/write-safe-efficient-code).) |
| 81 | + |
| 82 | +### Mutable classes |
| 83 | + |
| 84 | +It is recommended that you use immutable types (classes or structs) with value converters when possible. |
| 85 | +This is usually more efficient and has cleaner semantics than using a mutable type. |
| 86 | + |
| 87 | +However, that being said, it is common to use properties of types that the application cannot change. |
| 88 | +For example, mapping a property containing a list of numbers: |
| 89 | + |
| 90 | +[!code-csharp[ListProperty](../../../samples/core/Modeling/ValueConversions/MappingListProperty.cs?name=ListProperty)] |
| 91 | + |
| 92 | +The [`List<T>` class](/dotnet/api/system.collections.generic.list-1?view=netstandard-2.1): |
| 93 | +* Has reference equality; two lists containing the same values are treated as different. |
| 94 | +* Is mutable; values in the list can be added and removed. |
| 95 | + |
| 96 | +A typical value conversion on a list property might convert the list to and from JSON: |
| 97 | + |
| 98 | +[!code-csharp[ConfigureListProperty](../../../samples/core/Modeling/ValueConversions/MappingListProperty.cs?name=ConfigureListProperty)] |
| 99 | + |
| 100 | +This then requires setting a `ValueComparer<T>` on the property to force EF Core use correct comparisons with this conversion: |
| 101 | + |
| 102 | +[!code-csharp[ConfigureListPropertyComparer](../../../samples/core/Modeling/ValueConversions/MappingListProperty.cs?name=ConfigureListPropertyComparer)] |
| 103 | + |
| 104 | +> [!NOTE] |
| 105 | +> The model builder ("fluent") API to set a value comparer has not yet been implemented. |
| 106 | +> Instead, the code above calls SetValueComparer on the lower-level IMutableProperty exposed by the builder as 'Metadata'. |
| 107 | +
|
| 108 | +The `ValueComparer<T>` constructor accepts three expressions: |
| 109 | +* An expression for checking quality |
| 110 | +* An expression for generating a hash code |
| 111 | +* An expression to snapshot a value |
| 112 | + |
| 113 | +In this case the comparison is done by checking if the sequences of numbers are the same. |
| 114 | + |
| 115 | +Likewise, the hash code is built from this same sequence. |
| 116 | +(Note that this is a hash code over mutable values and hence can [cause problems](https://ericlippert.com/2011/02/28/guidelines-and-rules-for-gethashcode/). |
| 117 | +Be immutable instead if you can.) |
| 118 | + |
| 119 | +The snapshot is created by cloning the list with ToList. |
| 120 | +Again, this is only needed if the lists are going to be mutated. |
| 121 | +Be immutable instead if you can. |
| 122 | + |
| 123 | +> [!NOTE] |
| 124 | +> Value converters and comparers are constructed using expressions rather than simple delegates. |
| 125 | +> This is because EF inserts these expressions into a much more complex expression tree that is then compiled into an entity shaper delegate. |
| 126 | +> Conceptually, this is similar to compiler inlining. |
| 127 | +> For example, a simple conversion may just be a compiled in cast, rather than a call to another method to do the conversion. |
| 128 | +
|
| 129 | +### Key comparers |
| 130 | + |
| 131 | +The background section covers why key comparisons may require special semantics. |
| 132 | +Make sure to create a comparer that is appropriate for keys when setting it on a primary, principal, or foreign key property. |
| 133 | + |
| 134 | +Use [SetKeyValueComparer](/dotnet/api/microsoft.entityframeworkcore.mutablepropertyextensions.setkeyvaluecomparer?view=efcore-3.1) in the rare cases where different semantics is required on the same property. |
| 135 | + |
| 136 | +> [!NOTE] |
| 137 | +> SetStructuralComparer has been obsoleted in EF Core 5.0. |
| 138 | +> Use SetKeyValueComparer instead. |
| 139 | +
|
| 140 | +### Overriding defaults |
| 141 | + |
| 142 | +Sometimes the default comparison used by EF Core may not be appropriate. |
| 143 | +For example, mutation of byte arrays is not, by default, detected in EF Core. |
| 144 | +This can be overridden by setting a different comparer on the property: |
| 145 | + |
| 146 | +[!code-csharp[OverrideComparer](../../../samples/core/Modeling/ValueConversions/OverridingByteArrayComparisons.cs?name=OverrideComparer)] |
| 147 | + |
| 148 | +EF Core will now compare byte sequences and will therefore detect byte array mutations. |
0 commit comments