Skip to content

Commit 947ea10

Browse files
committed
Documentation for value comparers
Fixes #1986
1 parent ce52c3f commit 947ea10

File tree

4 files changed

+488
-0
lines changed

4 files changed

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

0 commit comments

Comments
 (0)