Skip to content

Instantly share code, notes, and snippets.

@sliekens
Last active October 10, 2022 09:32
Show Gist options
  • Save sliekens/84138dec3db88268f09283eb7692bcb8 to your computer and use it in GitHub Desktop.
Save sliekens/84138dec3db88268f09283eb7692bcb8 to your computer and use it in GitHub Desktop.
Reference data as code and EF

This is a reply to Reference data as code: https://enterprisecraftsmanship.com/posts/reference-data-as-code

I recommend reading it first, because it's good and because otherwise this won't make sense to you.

TLDR; I strongly recommend replacing static readonly fields with creation methods or equivalent getters.

public class Industry : Entity
{
-    public static readonly IndustryCars = new Industry(1, "Cars");
-    public static readonly IndustryPharmacy = new Industry(2, "Pharmacy");
-    public static readonly IndustryMedia = new Industry(3, "Media");
+    public static IndustryCars => new Industry(1, "Cars");
+    public static IndustryPharmacy => new Industry(2, "Pharmacy");
+    public static IndustryMedia => new Industry(3, "Media");
 
    public string Name { get; private set; }
 
    private Industry(int id, string name)
    {
        Id = id;
        Name = name;
    }
}

There are one or two reasons why I'm writing this.

The first is that you shouldn't rely on reference equality in client code. I am hesitating to accept perf improvements a good argument to keep them as static readonly.

var left = Industry.IndustryMedia;
var right = _industryRepository.GetByName("Media");

// Won't work anyway
// ❌ Assert.True(ReferenceEquals(left, right));

// Always do full Equals
Assert.Equals(left, right);

The second, the reason why I'm writing this, it's that it is common to have bidirectional navigation properties when using Entity Framework.

public class Customer : Entity
{
    public Industry Industry { get; private set; }
}

public class Industry : Entity
{
    public static readonly IndustryCars = new Industry(1, "Cars");
    public static readonly IndustryPharmacy = new Industry(2, "Pharmacy");
    public static readonly IndustryMedia = new Industry(3, "Media");
 
    public string Name { get; private set; }
 
    private Industry(int id, string name)
    {
        Id = id;
        Name = name;
    }

+    public ICollection<Customer> Customers { get; private set; }
}

In this situation, Industry.IndustryMedia.Customers is a collection that is updated by Entity Framework whenever you add a new customer in the Media industry to the DbSet.

var customer = new Customer(Industry.IndustryMedia);
someDbContext.Customers.Attach(customer);
someDbContext.SaveChanges()

Assert.Contains(customer, Industry.IndustryMedia.Customers);

Because the Industry.Customers collection navigation property is mutable shared state in this configuration, other threads can observe your new Customer even before the transaction is committed (after Attach()). I found this leads to incorrect behavior that are a real PITA to debug.

Runnable demo https://dotnetfiddle.net/T0eHQJ

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment