web-dev-qa-db-fra.com

Mapper un dictionnaire dans la première approche du code de l'entité

J'ai un dictionnaire comme celui-ci:

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

Et je veux mapper à la base de données. Est-il possible d'utiliser une liste protégée ou privée <> pour cela? tel que:

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

Quelqu'un peut-il m'aider?

17
iJay

Entity Framework ne prend actuellement pas en charge le mappage natif d'un dictionnaire.

Reportez-vous à ce qui suit pour plus d'informations et des solutions de rechange:

Entity Framework 4 POCO avec dictionnaire

EF Code First - Dictionnaire de carte ou type personnalisé en tant que nvarchar

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

19
Eric J.

Utilisation d'une colonne XML dans la base de données

Donc, aujourd’hui, j’ai rencontré le même problème et après y avoir réfléchi, j’ai trouvé une solution intéressante que je voudrais partager avec la communauté, même si je suis en retard. Fondamentalement, j'ai créé un système d'encapsulation qui enregistre les données de la variable Dictionary dans la variable Database sous le nom XML Column. Ainsi, je pourrai ultérieurement interroger le code XML de la base de données si je le souhaite. 

Pro de cette approche

  • Facile à utiliser
  • Mise en œuvre rapide
  • Vous pouvez utiliser le dictionnaire
  • Vous pouvez interroger la colonne XML

Tout d'abord voici l'os de tous mes modèles: 

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

Supposons que j'ai un modèle qui contient un Dictionary<string,string> et une propriété String qui contient la logique pour sérialiser et désérialiser le dictionnaire dans XML, comme le fragment suivant:

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

Ensuite, j'ai implémenté une classe BaseMapping qui hérite de 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 
    }
}

Et après une coutume Mapping pour MyCoolModel

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

Notez maintenant que lorsque AttributesData valeur est demandée par EntityFramework, il ne fait que sérialiser le dictionnaire. Il en va de même lorsque je récupère des données à partir de la base de données et que EntityFramework définit les données dans le champ. dict. 

Et enfin j'ai override la OnModelCreating de mon 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

    }

Et c'est tout! Maintenant, je peux utiliser le dictionnaire de ma logique métier et ce "wrapping" gère tout le matériel nécessaire pour enregistrer les données dans la variable DB et en récupérer les données.

7
Sid

J'ai eu un problème similaire avec EF où je voulais convertir une liste de requêtes retournée en équivalent du dictionnaire d'une propriété de classe. Très semblable à la façon dont vous voulez que LeaveEntitlementDetails soit enveloppé par LeveEntitlementStore Par exemple:

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

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

}

Où je voulais DataAsList essentiellement envelopper Data.Values

Après beaucoup d'essais et d'erreurs, j'ai découvert que EF, pour les collections (peut-être plus), modifie la valeur renvoyée par le getter (plutôt que le setter). I.e. lors de l'initialisation à partir de ma base de données:

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

ClassA objInstance = pollquery.First();

Le setter de ClassA.DataAsList n'a jamais été appelé, mais le getter était pendant la construction interne de mon objet par EF ... Conclusion: EF utilise une référence extraite du getter de la propriété ClassA.DataAsList et y ajoute des objets.

J'ai donc incorporé la valeur de retour de mon getter pour DataAsList dans une ObservableCollection et ajouté un gestionnaire pour les arguments CollectionChanged et, bien entendu, mon gestionnaire pour CollectionChanged recevait les appels .Add.

Alors voici ma solution de contournement:

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

Notez que la magie est la:

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

où nous nous abonnons à toutes les modifications apportées à la liste renvoyée et à Handles_entryListChanged où nous traitons et répliquons essentiellement toutes les modifications apportées.

2
Bunni H

EF Core 2.1 introduit une nouvelle fonctionnalité appelée value conversion :

Les convertisseurs de valeur permettent de convertir les valeurs de propriété lors de la lecture ou de l'écriture dans la base de données. 

Cette fonctionnalité simplifie grandement l’approche de sérialisation mentionnée dans les réponses précédentes, ce qui signifie que l’introduction de la propriété "helper" supplémentaire et le marquage de la propriété de votre dictionnaire en tant que [NotMapped] deviennent inutiles.

Voici quelques lignes de code adaptées à votre cas (note, j'utilise Json.NET , mais n'hésitez pas à utiliser le sérialiseur de votre choix):

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

comme mentionné dans ici , une chose importante après la sérialisation d'objet est que lors de la mise à jour de l'entité et de la modification d'éléments dans le dictionnaire, le suivi des modifications EF ne détecte pas le fait que le dictionnaire a été mis à jour, vous aurez donc besoin d'appeler explicitement la méthode Update sur le DbSet <> pour définir l'entité à modifier dans le suivi des modifications.

il y a aussi un autre bon échantillon ici

0
afshar