Skip to content

Commit

Permalink
Updates to value converter docs
Browse files Browse the repository at this point in the history
Lots of examples!

Fixes #614
Fixes #784
Part of #685
Fixes #2252
Fixes #2415
Fixes #2456
Fixes #2824
Fixes #2979
  • Loading branch information
ajcvickers committed Jan 17, 2021
1 parent ef7247f commit ecf418b
Show file tree
Hide file tree
Showing 16 changed files with 2,039 additions and 127 deletions.
39 changes: 12 additions & 27 deletions entity-framework/core/modeling/value-comparers.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: Value Comparers - EF Core
description: Using value comparers to control how EF Core compares property values
author: ajcvickers
ms.date: 03/20/2020
ms.date: 01/16/2021
uid: core/modeling/value-comparers
---

Expand All @@ -16,9 +16,9 @@ uid: core/modeling/value-comparers
## Background

Change tracking means that EF Core automatically determines what changes were performed by the application on a loaded entity instance, so that those changes can be saved back to the database when <xref:Microsoft.EntityFrameworkCore.DbContext.SaveChanges%2A> is called. EF Core usually performs this by taking a *snapshot* of the instance when it's loaded from the database, and *comparing* that snapshot to the instance handed out to the application.
[Change tracking](xref:core/change-tracking/index) means that EF Core automatically determines what changes were performed by the application on a loaded entity instance, so that those changes can be saved back to the database when <xref:Microsoft.EntityFrameworkCore.DbContext.SaveChanges%2A> is called. EF Core usually performs this by taking a *snapshot* of the instance when it's loaded from the database, and *comparing* that snapshot to the instance handed out to the application.

EF Core comes with built-in logic for snapshotting and comparing most standard types used in databases, so users don't usually need to worry about this topic. However, when a property is mapped through a [value converter](xref:core/modeling/value-conversions), EF Core needs to perform comparison on arbitrary user types, which may be complex. By default, EF Core uses the default equality comparison defined by types (e.g. the `Equals` method); for snapshotting, value types are copied to produce the snapshot, while for reference types no copying occurs, and the same instance is used as the snapshot.
EF Core comes with built-in logic for snapshotting and comparing most standard types used in databases, so users don't usually need to worry about this topic. However, when a property is mapped through a [value converter](xref:core/modeling/value-conversions), EF Core needs to perform comparison on arbitrary user types, which may be complex. By default, EF Core uses the default equality comparison defined by types (e.g. the `Equals` method); for snapshotting, [value types](/dotnet/csharp/language-reference/builtin-types/value-types) are copied to produce the snapshot, while for [reference types](/dotnet/csharp/language-reference/keywords/reference-types) no copying occurs, and the same instance is used as the snapshot.

In cases where the built-in comparison behavior isn't appropriate, users may provide a *value comparer*, which contains logic for snapshotting, comparing and calculating a hash code. For example, the following sets up value conversion for `List<int>` property to be value converted to a JSON string in the database, and defines an appropriate value comparer as well:

Expand All @@ -37,7 +37,7 @@ Consider byte arrays, which can be arbitrarily large. These 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

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. This is a pragmatic decision that avoids copying entire arrays and comparing them byte-to-byte when executing <xref:Microsoft.EntityFrameworkCore.DbContext.SaveChanges%2A>, and the common scenario of replacing, say, one image with another is handled in a performant way.
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. This is a pragmatic decision that avoids copying entire arrays and comparing them byte-to-byte when executing <xref:Microsoft.EntityFrameworkCore.DbContext.SaveChanges%2A>. It means the common scenario of replacing, say, one image with another is handled in a performant way.

On the other hand, reference equality would not work when byte arrays are used to represent binary keys, since 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. 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.

Expand Down Expand Up @@ -66,18 +66,13 @@ The mapping for simple structs is also simple and requires no special comparers

[!code-csharp[ConfigureImmutableStructProperty](../../../samples/core/Modeling/ValueConversions/MappingImmutableStructProperty.cs?name=ConfigureImmutableStructProperty)]

EF Core has built-in support for generating compiled, memberwise comparisons of struct properties.
This means structs don't need to have equality overridden for EF Core, 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).
Also, special snapshotting is not needed since structs are immutable and are always copied memberwise anyway.
(This is also true for mutable structs, but [mutable structs should in general be avoided](/dotnet/csharp/write-safe-efficient-code).)
EF Core has built-in support for generating compiled, memberwise comparisons of struct properties. This means structs don't need to have equality overridden for EF Core, 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). Also, special snapshotting is not needed since structs are immutable and are always copied memberwise anyway. (This is also true for mutable structs, but [mutable structs should in general be avoided](/dotnet/csharp/write-safe-efficient-code).)

## 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.
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:
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:

[!code-csharp[ListProperty](../../../samples/core/Modeling/ValueConversions/MappingListProperty.cs?name=ListProperty)]

Expand Down Expand Up @@ -106,24 +101,16 @@ The <xref:Microsoft.EntityFrameworkCore.ChangeTracking.ValueComparer%601> constr

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](https://ericlippert.com/2011/02/28/guidelines-and-rules-for-gethashcode/).
Be immutable instead if you can.)
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](https://ericlippert.com/2011/02/28/guidelines-and-rules-for-gethashcode/). Be immutable instead if you can.)

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.
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.

> [!NOTE]
> Value converters and comparers are constructed using expressions rather than simple delegates.
> This is because EF Core 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.
> Value converters and comparers are constructed using expressions rather than simple delegates. This is because EF Core 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.
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.

Use <xref:Microsoft.EntityFrameworkCore.MutablePropertyExtensions.SetKeyValueComparer%2A> in the rare cases where different semantics is required on the same property.

Expand All @@ -132,9 +119,7 @@ Use <xref:Microsoft.EntityFrameworkCore.MutablePropertyExtensions.SetKeyValueCom
## Overriding the default comparer

Sometimes the default comparison used by EF Core may not be appropriate.
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:
Sometimes the default comparison used by EF Core may not be appropriate. 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:

[!code-csharp[OverrideComparer](../../../samples/core/Modeling/ValueConversions/OverridingByteArrayComparisons.cs?name=OverrideComparer)]

Expand Down
Loading

0 comments on commit ecf418b

Please sign in to comment.