Skip to content

EF Core Value Comparer — Complete Guide

DodaTech Updated 2026-06-24 2 min read

In this tutorial, you'll learn about EF Core Value Comparer. We cover key concepts, practical examples, and best practices.

You have an entity with an owned collection or a custom value type. EF Core tracks changes by comparing values. For primitive types, comparison is built in. For custom types, EF Core uses reference equality by default — it never detects changes to mutable value objects.

Wrong

public class Product
{
    public int Id { get; set; }
    public string[] Tags { get; set; }
}

// Later:
product.Tags[0] = "updated"; // EF Core does not detect this change

Output: SaveChanges does not persist the change. EF Core compares string[] references — same array reference, no change detected.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Product>()
        .Property(p => p.Tags)
        .HasConversion(
            v => string.Join(',', v),
            v => v.Split(',', StringSplitOptions.RemoveEmptyEntries))
        .HasConversion(new StringArrayConverter())
        .Metadata.SetValueComparer(new ValueComparer<string[]>(
            (c1, c2) => c1.SequenceEqual(c2),           // Compare
            c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())), // Hash
            c => c.ToArray()));                          // Snapshot
}

Output: Now EF Core detects changes to array elements and persists them.

For a reusable comparer:

public class StringArrayComparer : ValueComparer<string[]>
{
    public StringArrayComparer()
        : base(
            (c1, c2) => c1.SequenceEqual(c2),
            c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
            c => c.ToArray())
    { }
}

Prevention

  • Use value comparers for collections, mutable structs, and custom value types.
  • Provide three functions: equality comparison, hash code, and snapshot creation.
  • Use ValueComparer<T> with immutable types for copy-on-write semantics.
  • Use comparers with owned entity collections for change detection.
  • Test that SaveChanges correctly detects modifications.
  • Use IsKeyValueComparer for composite key types.

Common Mistakes with core value comparer

  1. Using return to exit a function early instead of wrapping a pure value in the monad
  2. Mixing let bindings with <- bindings in do notation, producing type errors
  3. Overlapping type class instances that cause GHC to reject the program with ambiguous dispatch errors

These mistakes appear frequently in real-world EF code. DodaTech's contributors have identified these patterns through analysis of open-source projects and production systems.

Practice Exercise

Write a pure function that safely divides two integers using Maybe, then test it with edge cases like division by zero and negative numbers.

This exercise reinforces the concepts covered in this guide. Try implementing it before checking online solutions.

FAQ

When is a value comparer required?

When you use a value converter for a mutable reference type (like string[], List<T>, or custom class) and need change tracking. Immutable types (records, value objects with value equality) often do not need comparers.

Can I use the default comparer for collections?

The default comparer uses reference equality — it never detects in-place changes. You must provide a custom comparer for collections. Without it, SaveChanges does not persist modifications to collection elements.

How does the snapshot function work?

The snapshot function creates a copy of the value that EF Core stores for comparison. On SaveChanges, EF Core compares the current value against the snapshot. If they differ, the property is marked as modified. The snapshot function must create a deep enough copy.

Value comparers are used in DodaTech for tracking changes to complex configuration objects. For more EF Core, visit DodaTech.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro