EF Core: use a dictionary property

Seems someone has been struggling with that and found solution. See: Store a Dictionary as a JSON string using EF Core 2.1

The definition of the entity is as follows:

public class PublishSource
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    [Required]
    public string Name { get; set; }

    [Required]
    public Dictionary<string, string> Properties { get; set; } = new Dictionary<string, string>();
}

In the OnModelCreating method of the database context I just call HasConversion, which does the serialization and deserialization of the dictionary:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    modelBuilder.Entity<PublishSource>()
        .Property(b => b.Properties)
        .HasConversion(
            v => JsonConvert.SerializeObject(v),
            v => JsonConvert.DeserializeObject<Dictionary<string, string>>(v));
}

One important thing I have noticed, however, is that when updating the entity and changing items in the dictionary, the EF change tracking does not pick up on the fact that the dictionary was updated, so you will need to explicitly call the Update method on the DbSet<> to set the entity to modified in the change tracker.


I don't think saving a dictionary is a good idea (I can't even image how it would be done in the database). As I can see from you source code you are using the FirstName as key. In my opinion you should change the dictionary to a HashSet. This way you can keep the speed but also save it to the database. Here is an example:

class Course
{
    public Course() {
        this.People = new HashSet<Person>();
    }

    public ISet<Person> People { get; set; }

    public int Id { get; set; }
}

After this you can create a dictionary from it, or keep using the hashset. Sample for dictionary:

private Dictionary<string, Person> peopleDictionary = null;


public Dictionary<string, Person> PeopleDictionary {
    get {
        if (this.peopleDictionary == null) {
            this.peopleDictionary = this.People.ToDictionary(_ => _.FirstName, _ => _);
        }

        return this.peopleDictionary;
    }
}

Please note that this would mean that your People Set becomes unsynced after you add/remove to/from the dictionary. In order to have the changes in sync you should overwrite the SaveChanges method in your context, like this:

public override int SaveChanges() {
    this.SyncPeople();

    return base.SaveChanges();
}

public override int SaveChanges(bool acceptAllChangesOnSuccess) {
    this.SyncPeople();

    return base.SaveChanges(acceptAllChangesOnSuccess);
}

public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default) {
    this.SyncPeople();

    return base.SaveChangesAsync(cancellationToken);
}

public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default) {
    this.SyncPeople();

    return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}

private void SyncPeople() {
    foreach(var entry in this.ChangeTracker.Entries().Where(_ = >_.State == EntityState.Added || _.State == EntityState.Modified)) {
        if (entry.Entity is Course course) {
            course.People = course.PeopleDictionary.Values.ToHashSet();
        }
    }
}

EDIT: In order to have a running code, you will need to tell the EF not to map the dictionary, via the NotMapped Attribute.

[NotMapped]
public Dictionary<string, Person> PeopleDictionary { ... }