Map a Dictionary in Entity Framework Code First Approach

I had a similar problem with EF were I wanted to convert a query returned list, into a class property's dictionary equivalent. Very similar to how you want to have LeaveEntitlementDetails wrapped by LeveEntitlementStore For example:

class A
{   
    [NotMapped()]
    public Dictionary<int, DataType> Data {get; set}

    //refers to Data.Values
    public ICollection<DataType> DataAsList {get; set}        

}

Where I wanted DataAsList to essentially wrap Data.Values

After a lot of trial and error, I discovered that EF, for collections (maybe more) alters through the getter's returned value (rather than the setter). I.e. when initializing from my db:

var pollquery=From bb In DBM.Dbi.DataTable.Includes("DataAsList")
              Where bb.Id = id
              Select bb;

ClassA objInstance = pollquery.First();

ClassA.DataAsList's setter was never being called, but the getter was during EF's internal construction of my object.... Conclusion: EF is using a reference retrieved from the getter of property ClassA.DataAsList, and adding objects to it.

So I wrapped my getter's return value for DataAsList in an ObservableCollection and added a handler for CollectionChanged args and sure enough, my handler for CollectionChanged was picking up .Add calls.

So heres my hackaround-workaround:

class A : INotifyPropertyChanged
{
    //So we can let EF know a complex property has changed
    public event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged;

    //here's our actual data, rather than an auto property, we use an explicit member definition so we can call PropertyChanged when Data is changed
    private Dictionary<int, DataType> m_data = new Dictionary<int, DataType>();
    //not mapped property as it's not mapped to a column in EF DB
    [NotMapped()]
    public Dictionary<int, DataType> Data {
        get { return m_data; }
        set {
            m_data = value;
            //now call PropertyChanged for our Front (so EF will know it's been changed)
            if (PropertyChanged != null) {
                PropertyChanged(this, new PropertyChangedEventArgs("DataAsList"));
            }
        }
    }

    //this is our front for the data, that we use in EF to map data to
    [DebuggerHidden()]
    public ICollection<DataType> DataAsList {
        get {
            ObservableCollection<DataType> ob = new ObservableCollection<DataType>(Data.Values());
            ob.CollectionChanged += Handles_entryListChanged;
            return ob;
        }
        set {
            //clear any existing data, as EF is trying to set the collections value
            Data.Clear();
            //this is how, in my circumstance, i converted my object into the dictionary from an internal obj.Id property'
            foreach (DataType entry in value) {
                entryions.Add(entry.id, entry);
            }
        }
    }
    //This will now catch wind of any changes EF tries to make to our DataAsList property
    public void Handles_entryListChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        //Debugger.Break()
        switch (e.Action) {
            case NotifyCollectionChangedAction.Add:
                foreach (DataType entry in e.NewItems) {
                    m_data.Add(entry.Id, entry);
                }

                break;
            default:
                Debugger.Break();
                break;
        }
    }
}

Note the Magic is the:

public ICollection<DataType> DataAsList {
    get {
        ObservableCollection<DataType> ob = new ObservableCollection<DataType>(Data.Values());
        ob.CollectionChanged += Handles_entryListChanged;
        return ob;
    }

where we subscribe to any changes made to the returned list and Handles_entryListChanged where we handle and essentially replicate any changes made.


EF Core 2.1 introduced a new feature called value conversion:

Value converters allow property values to be converted when reading from or writing to the database.

This feature highly simplifies the serialization approach mentioned in previous answers, which means, the introduction of on an additional "helper" property and the marking of your dictionary property as [NotMapped] becomes unnecessary.

Here are some lines of code tailored to your case (note, I am using Json.NET, but feel free to use your serializer of choice):

using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

namespace My.Name.Space
{
    public class MyEntity
    {
        public int Id { get; set; }
        public Dictionary<string, EmployeeLeaveEntitlement> LeaveEntitlementDetails { get; set; } 
    }

    public class MyEntityConfiguration : IEntityTypeConfiguration<MyEntity>
    {
        public void Configure(EntityTypeBuilder<MyEntity> builder)
        {
            builder.ToTable("MyEntity");
            builder.HasKey(e => e.Id);

            builder
            .Property(e => e.LeaveEntitlementDetails)
            .IsRequired()
            .HasConversion(
                v => JsonConvert.SerializeObject(v),
                v => v == null
                    ? new Dictionary<string, EmployeeLeaveEntitlement>() // fallback
                    : JsonConvert.DeserializeObject<Dictionary<string, EmployeeLeaveEntitlement>>(v)
            );
        }
    }
}

Entity Framework does not presently support mapping a Dictionary natively.

See the following for more information and work-arounds:

Entity Framework 4 POCO with Dictionary

EF Code First - Map Dictionary or custom type as an nvarchar

http://social.msdn.microsoft.com/Forums/en-US/adonetefx/thread/a51ba903-2b8b-448e-8677-d140a0b43e89/


Using a XML Column in DB

So today I came across the same problem, and after thinking about it I found a cool solution which I would like to share with the community even if I am late. Basically I've made a wrapping system which saves the data in the Dictionary to the Database as XML Column, so later I can also query the XML from the DB if I want.

Pro of this approach

  • Easy to use
  • Fast implementation
  • You can use the dictionary
  • You can query the XML column

First of all here's the bone of all my models:

public abstract class BaseEntity 
{
    /// <summary>
    /// ID of the model
    /// </summary>
    public int ID { get; set; }
}

Suppose I have a model which contain a Dictionary<string,string> and a String property which contains the logic to Serialize and Deserialize the dictionary in XML, like the following snippet:

public class MyCoolModel : Base.BaseEntity
{
    /// <summary>
    /// Contains XML data of the attributes
    /// </summary>
    public string AttributesData
    {
        get
        {
            var xElem = new XElement(
                "items",
                Attributes.Select(x => new XElement("item", new XAttribute("key", x.Key), new XAttribute("value", x.Value)))
             );
            return xElem.ToString();
        }
        set
        {
            var xElem = XElement.Parse(value);
            var dict = xElem.Descendants("item")
                                .ToDictionary(
                                    x => (string)x.Attribute("key"), 
                                    x => (string)x.Attribute("value"));
            Attributes = dict;
        }
    }

    //Some other stuff

    /// <summary>
    /// Some cool description
    /// </summary>
    [NotMapped]
    public Dictionary<string, string> Attributes { get; set; }
}

Then I've implemented a BaseMapping class which ineherits from EntityTypeConfiguration<T>

class BaseMapping<TEntity> : EntityTypeConfiguration<TEntity>
    where TEntity : Model.Base.BaseEntity
{
    public BaseMapping()
    {
        //Some basic mapping logic which I want to implement to all my models 
    }
}

And after a Custom Mapping for MyCoolModel

class MyCoolModelMapping
    : BaseMapping<Model.MyCoolModel>
{        
    public MyCoolModelMapping() 
    {
        Property(r => r.AttributesData).HasColumnType("xml");
    }
}

Now notice that when AttributesData value is requested by EntityFramework it just serialize the dictionary and the same happens when I retrive data from the DB and EntityFramework sets the data to the field, which then deserializes the object and sets it to the dict.

And finally I have override the OnModelCreating of my DbContext

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

        modelBuilder.Configurations.Add(new Mappings.BaseMapping<SomeOtherModel>());
        modelBuilder.Configurations.Add(new Mappings.MyCoolModelMapping());
        //Other logic

    }

And that's it! Now I can use the dictionary from my business logic and this "wrapping" handles all the stuff need to save the data to the DB and retrive the data from it.