Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

EF Core: use a dictionary property

Is there a way to fill a dictionary property with Entity Framework Core?

For performance reasons, we like to search in the application instead of the database. As a list won’t scale well, we like to use a dictionary.

For example (simplified example)

class Course
{
    public Dictionary<string, Person> Persons { get; set; }

    public int Id { get; set; }
}

class Person
{
    public string Firstname { get; set; }
    public string Lastname { get; set; }
}

Things I tried

  • Naively just add a dictionary property. This will result the in following error:

    System.InvalidOperationException: The property 'Persons' could not be mapped, because it is of type 'Dictionary' which is not a supported primitive type or a valid entity type. Either explicitly map this property, or ignore it using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.

  • Try adding a value conversion (with HasConversion), but conversion one only works on a single item and not on collections. The HasMany already gives a compile error:

    builder
      .HasMany<Person>(c => c.Persons) //won't compile, Persons isn't a IEnumerable<Person>
      .WithOne().HasForeignKey("PersonId");
    
  • Creating a custom collection class (inherited from Collection<T> and implement InsertItem, SetItem etc.) – unfortunately this also won’t work because EF Core will add the item to the collection and first after that will fill the properties (at least with our OwnsOne properties, that is not in the demo case) - SetItem won't be called afterwards.

  • Adding a "computed" property that will build the dictionary, the setter won't be called (the list is updated every time with partly values, a bit the same as above). See try:

    class Course
    {
        private Dictionary<string, Person> _personsDict;
    
        public List<Person> Persons
        {
            get => _personsDict.Values.ToList();
            set => _personsDict = value.ToDictionary(p => p.Firstname, p => p); //never called
        }
    
        public int Id { get; set; }
    }
    

Of course I could build a dictionary in the Repository (using the Repository pattern), but that’s tricky as I could forget some parts – and I really prefer compile time errors over run-time errors and declarative style over imperative style code.

Update, to be clear

  • this isn't a code first approach
  • the idea to change the mapping in EF Core, so no database changes. - I haven't tagged the database on purpose ;)
  • If I use a List instead of Dictionary, the mapping works
  • It's a 1:n or n:m relationship in the database (see HasMany - WithOne)
like image 435
Julian Avatar asked Mar 17 '20 17:03

Julian


People also ask

Can a dictionary be a property C#?

Item[] Property. This property is used to get or set the value associated with the specified key in the Dictionary. Here, key is the Key of the value to get or set.

Does Entity Framework support dictionary?

Entity Framework does not presently support mapping a Dictionary natively.

Should I use EF6 or EF core?

Keep using EF6 if the data access code is stable and not likely to evolve or need new features. Port to EF Core if the data access code is evolving or if the app needs new features only available in EF Core. Porting to EF Core is also often done for performance.

What are shadow properties in EF core?

Shadow properties are properties that aren't defined in your . NET entity class but are defined for that entity type in the EF Core model. The value and state of these properties are maintained purely in the Change Tracker.


1 Answers

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.

like image 145
Maciej Los Avatar answered Oct 10 '22 08:10

Maciej Los