C# 在实体框架代码优先方法中映射字典

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9613421/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-09 08:10:03  来源:igfitidea点击:

Map a Dictionary in Entity Framework Code First Approach

c#dictionaryef-code-first

提问by iJay

I have a dictionary like this:

我有一本这样的字典:

/// <summary>
/// Gets the leave entitlement details.
/// </summary>
/// <value>The leave entitlement details.</value>
public Dictionary<string, EmployeeLeaveEntitlement> LeaveEntitlementDetails { get; set; }  

And I want to map it to the database. Is it possible to use a protected or private List<> for that? such as:

我想将它映射到数据库。是否可以为此使用受保护或私有的 List<> ?如:

/// <summary>
/// Gets the leave entitlement details.
/// </summary>
/// <value>The leave entitlement details.</value>
public Dictionary<string, EmployeeLeaveEntitlement> LeaveEntitlementDetails { get; set; } 

public List<EmployeeLeaveEntitlement> LeveEntitlementStore
{
    get
    {
        List<EmployeeLeaveEntitlement> leaveEntitlements = new List<EmployeeLeaveEntitlement>();

        foreach (KeyValuePair<string, EmployeeLeaveEntitlement> leaveType in LeaveEntitlementDetails)
        {
            leaveEntitlements.Add(leaveType.Value);
        }

        return leaveEntitlements;
    }
    set
    {
        foreach (EmployeeLeaveEntitlement item in value)
        {
            this.LeaveEntitlementDetails.Add(item.LeaveType, item);
        }
    }
}

Can anyone help me?

谁能帮我?

回答by Bunni H

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 LeaveEntitlementDetailswrapped by LeveEntitlementStoreFor example:

我有一个与 EF 类似的问题,我想将查询返回列表转换为类属性的等效字典。非常类似于您想要的LeaveEntitlementDetails包装方式LeveEntitlementStore例如:

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

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

}

Where I wanted DataAsListto essentially wrap Data.Values

我想DataAsList基本上包装的地方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:

经过大量的反复试验,我发现 EF,用于集合(可能更多)通过 getter 的返回值(而不是 setter)进行更改。即从我的数据库初始化时:

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.

ClassA.DataAsList的 setter 从未被调用,但 getter 是在 EF 对我的对象的内部构造过程中进行的……结论:EF 正在使用从 property 的 getter 中检索到的引用ClassA.DataAsList,并向其添加对象。

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

因此,我将 getter 的返回值包装DataAsList在 ObservableCollection 中,并为 CollectionChanged args 添加了一个处理程序,果然,我的 CollectionChanged 处理程序正在.Add接听电话。

So heres my hackaround-workaround:

所以继承人我的hackaround解决方法:

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_entryListChangedwhere we handle and essentially replicate any changes made.

我们订阅对返回列表所做的任何更改,以及Handles_entryListChanged我们处理和基本上复制所做的任何更改的位置。

回答by Sid

Using a XML Column in DB

在 DB 中使用 XML 列

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 Dictionaryto the Databaseas XML Column, so later I can also query the XML from the DB if I want.

所以今天我遇到了同样的问题,经过思考,我找到了一个很酷的解决方案,即使我迟到了,我也想与社区分享。基本上我已经制作了一个包装系统,它将数据保存DictionaryDatabaseas 中XML Column,所以以后我也可以根据需要从数据库查询 XML。

Pro of this approach

这种方法的优点

  • Easy to use
  • Fast implementation
  • You can use the dictionary
  • You can query the XML column
  • 便于使用
  • 快速实施
  • 你可以用字典
  • 您可以查询 XML 列


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 Stringproperty which contains the logic to Serialize and Deserialize the dictionary in XML, like the following snippet:

假设我有一个包含 a 的模型Dictionary<string,string>和一个String包含序列化和反序列化字典的逻辑的属性XML,如下面的片段:

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 BaseMappingclass which ineherits from EntityTypeConfiguration<T>

然后我实现了一个BaseMapping继承自的类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 Mappingfor MyCoolModel

和一个自定义后MappingMyCoolModel

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

Now notice that when AttributesDatavalue is requested by EntityFrameworkit 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.

现在请注意,当AttributesData它请求值时,EntityFramework它只是序列化字典,当我从数据库检索数据并且 EntityFramework 将数据设置为字段时,也会发生同样的情况,然后反序列化对象并将其设置为字典。

And finally I have overridethe OnModelCreatingof my DbContext

最后我overrideOnModelCreating我的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 DBand retrive the data from it.

就是这样!现在我可以使用我的业务逻辑中的字典,这个“包装”处理将数据保存到DB并从中检索数据所需的所有东西。

回答by B12Toaster

EF Core 2.1 introduced a new feature called value conversion:

EF Core 2.1 引入了一个称为值转换的新功能:

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.

此功能极大地简化了先前答案中提到的序列化方法,这意味着引入额外的“帮助程序”属性和将字典属性标记为[NotMapped]变得不必要。

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):

以下是针对您的情况量身定制的一些代码行(注意,我使用的是Json.NET,但您可以随意使用您选择的序列化程序):

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)
            );
        }
    }
}

回答by afshar

As mentioned in here, One important thing after object serialization, 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 modify in the change tracker.

正如提到这里,对象序列化后一个重要的事情是,更新实体和字典中的改变项目时,该EF更改跟踪不挑上的事实,词典被更新,所以你需要显式调用DbSet<> 上的更新方法以设置要在更改跟踪器中修改的实体。

there is also another good sample here

也有另一种不错的样本在这里