Сопоставление словаря в Entity Framework Code Первый подход - PullRequest
17 голосов
/ 08 марта 2012

У меня есть словарь, подобный этому:

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

И я хочу сопоставить его с базой данных.Можно ли использовать для этого защищенный или закрытый список <>?такие как:

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

Кто-нибудь может мне помочь?

Ответы [ 5 ]

20 голосов
/ 19 марта 2012

Entity Framework в настоящее время не поддерживает собственное отображение словаря.

Для получения дополнительной информации и способов обхода см. Следующее:

Entity Framework 4 POCO со словарем

EF Code First - Словарь словаря или пользовательский тип как nvarchar

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

7 голосов
/ 25 ноября 2016

Использование столбца XML в БД

Итак, сегодня я столкнулся с той же проблемой, и подумав об этом, я нашел классное решение, которым я хотел бы поделиться с сообществом, даже если я опаздываю.По сути, я создал систему упаковки, которая сохраняет данные из Dictionary в Database как XML Column, поэтому позже я также могу запросить XML из БД, если захочу.

Pro этого подхода

  • Простота использования
  • Быстрое внедрение
  • Вы можете использовать словарь
  • Вы можете запроситьстолбец XML

Прежде всего, вот кость всех моих моделей:

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

Предположим, у меня есть модель, которая содержит 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; }
}

Затем я реализовал класс 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 
    }
}

И после пользовательского Mapping для MyCoolModel

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

Теперь обратите внимание, что когда AttributesData запрашивается значение *1040*, он просто сериализует словарь, и то же самое происходит, когдаЯ извлекаю данные из БД, и EntityFramework устанавливает данные в поле, которое затем десериализует объект и устанавливает его в dict.

И наконец у меня есть override OnModelCreating моего 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

    }

И это все!Теперь я могу использовать словарь из своей бизнес-логики, и эта «упаковка» обрабатывает все, что нужно для сохранения данных в DB и извлечения данных из него.

4 голосов
/ 28 декабря 2018

EF Core 2.1 представил новую функцию под названием преобразование значений :

Преобразователи значений позволяют преобразовывать значения свойств при чтении или записи в базу данных.

Эта функция значительно упрощает подход сериализации, упомянутый в предыдущих ответах, что означает, что введение дополнительного свойства "helper" и маркировка вашего словарного свойства как [NotMapped] становятся ненужными.

Вот несколько строк кода, адаптированных к вашему случаю (обратите внимание, я использую 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)
            );
        }
    }
}
2 голосов
/ 24 ноября 2016

У меня была похожая проблема с EF, где я хотел преобразовать возвращенный список запроса в словарный эквивалент свойства класса. Очень похоже на то, как вы хотите, чтобы LeaveEntitlementDetails был обернут LeveEntitlementStore Например:

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

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

}

Где я хотел DataAsList, чтобы обернуть Data.Values

После долгих проб и ошибок я обнаружил, что EF для коллекций (может быть, больше) изменяется через возвращаемое значение получателя (а не установщика). То есть при инициализации из моей базы данных:

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

ClassA objInstance = pollquery.First();
Установщик

ClassA.DataAsList никогда не вызывался, но получатель был во время внутреннего построения EF моего объекта .... Вывод: EF использует ссылку, полученную из получателя свойства ClassA.DataAsList, и добавляет объекты в это.

Поэтому я обернул возвращаемое значение моего получателя для DataAsList в ObservableCollection и добавил обработчик для аргументов CollectionChanged и, конечно же, мой обработчик для CollectionChanged принимал .Add вызовов.

Так вот мой обходной путь взлома:

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

Обратите внимание, что магия:

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

, где мы подписываемся на любые изменения, внесенные в возвращенный список, и Handles_entryListChanged, где мы обрабатываем и по существу копируем любые сделанные изменения.

0 голосов
/ 19 января 2019

Как упомянуто в здесь , Одна важная вещь после сериализации объекта заключается в том, что при обновлении сущности и изменении элементов в словаре отслеживание изменений EF не учитывает тот факт, что словарь был обновлен поэтому вам нужно явно вызвать метод Update в DbSet <>, чтобы настроить сущность для изменения в трекере изменений.

есть еще один хороший образец здесь

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...