web-dev-qa-db-fra.com

EF Core: utilisez une propriété de dictionnaire

Existe-t-il un moyen de remplir une propriété de dictionnaire avec Entity Framework Core?

Pour des raisons de performances, nous aimons rechercher dans l'application plutôt que dans la base de données. Comme une liste n’est pas bien mise à l’échelle, nous aimons utiliser un dictionnaire.

Par exemple (exemple simplifié)

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

Les choses que j'ai essayées

  • Ajoutez simplement naïvement une propriété de dictionnaire. Cela entraînera l'erreur suivante:

    System.InvalidOperationException: La propriété 'Persons' n'a pas pu être mappée, car elle est de type 'Dictionary' qui n'est pas un type primitif pris en charge ou un type d'entité valide. Mappez explicitement cette propriété ou ignorez-la à l'aide de l'attribut "[NotMapped]" ou en utilisant "EntityTypeBuilder.Ignore" dans "OnModelCreating".

  • Essayez d'ajouter un conversion de valeur (avec HasConversion), mais la conversion ne fonctionne que sur un seul élément et non sur les collections. Le HasMany donne déjà une erreur de compilation:

    builder
      .HasMany<Person>(c => c.Persons) //won't compile, Persons isn't a IEnumerable<Person>
      .WithOne().HasForeignKey("PersonId");
    
  • Création d'une classe de collection personnalisée (héritée de Collection<T> et implémenter InsertItem, SetItem etc.) - malheureusement, cela ne fonctionnera pas non plus car EF Core ajoutera l'élément à la collection et remplira d'abord les propriétés ( au moins avec nos propriétés OwnsOne, ce n'est pas dans le cas de la démo) - SetItem ne sera pas appelé par la suite.

  • En ajoutant une propriété "calculée" qui construira le dictionnaire, le setter ne sera pas appelé (la liste est mise à jour à chaque fois avec des valeurs partielles, un peu comme ci-dessus). Voir essayer:

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

Bien sûr, je pourrais créer un dictionnaire dans le référentiel (en utilisant le modèle de référentiel ), mais c'est délicat car je pourrais oublier certaines parties - et je préfère vraiment les erreurs de compilation aux erreurs d'exécution et au style déclaratif. code de style impératif.

Mettre à jour, pour être clair

  • ce n'est pas une approche basée sur le code
  • l'idée de changer le mappage dans EF Core, donc aucun changement de base de données. - Je n'ai pas étiqueté la base de données exprès;)
  • Si j'utilise un List au lieu d'un dictionnaire, le mappage fonctionne
  • C'est une relation 1: n ou n: m dans la base de données (voir HasMany - WithOne)
4
Julian
  1. Créez une classe partielle du type généré par EF.
  2. Créez une classe wrapper qui contient un dictionnaire ou implémentez IDictionary.
  3. Implémentez la fonction Add afin qu'elle ajoute également la valeur à la liste utilisée par EF.
  4. La première fois qu'une méthode opérant sur la liste ou le dictionnaire de personnes est appelée, assurez-vous qu'elles sont correctement initialisées

Vous vous retrouveriez avec quelque chose comme:

private class PersonsDictionary
{
  private delegate Person PersonAddedDelegate;
  private event PersonAddedDelegate PersonAddedEvent; // there can be other events needed too, eg PersonDictionarySetEvent

  private Dictionary<string, Person> dict = ...
  ...
  public void Add(string key, Person value)
  {
    if(dict.ContainsKey(key))
    {
      // .. do custom logic, if updating/replacing make sure to either update the fields or remove/re-add the item so the one in the list has the current values
    } else {
      dict.Add(key, value);
      PersonAddedEvent?.(value); // your partial class that holds this object can hook into this event to add it its list
    }
  }
 // ... add other methods and logic
}

public partial class Person
{

 [NotMapped]
 private Dictionary<string, Person> _personsDict

 [NotMapped]
 public PersonsDictionary<string, Person> PersonsDict
 {
    get 
    {
      if(_personsDict == null) _personsDict = Persons.ToDictionary(x => x.FirstName, x => x); // or call method that takes list of Persons
      return _personsDict;
    }
    set
    {
      // delete all from Persons
      // add all to Persons from dictionary
    }
 }
}
public List<Person> Persons; // possibly auto-generated by entity framework and located in another .cs file
  • si vous allez accéder directement à la liste des personnes, vous devez également modifier votre classe partielle afin que l'ajout à la liste s'ajoute au dictionnaire (peut-être en utilisant un wrapper pour la liste des personnes ou une classe wrapper tous ensemble)
  • il y a quelques améliorations à apporter si vous traitez avec de grands ensembles de données ou si vous avez besoin d'optimisation, par exemple ne pas supprimer/rajouter tous les éléments lors de la configuration d'un nouveau dictionnaire
  • vous devrez peut-être implémenter d'autres événements et une logique personnalisée en fonction de vos besoins
0
BoldAsLove

Vous pouvez ajouter une nouvelle propriété PersonsJson pour stocker les données JSON. Il sérialise ou désérialise automatiquement les données JSON dans la propriété Persons lorsque les données sont extraites de la base de données ou stockées dans la base de données. La propriété Persons n'est pas mappée, seul PersonsJson est mappé.

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

    public string PersonsJson
    {
        get => JsonConvert.SerializeObject(Persons);
        set => Persons = JsonConvert.DeserializeObject<Dictionary<string, Person>>(value);
    }

    public int Id { get; set; }
}
0
Martin Staufcik

Il semble que quelqu'un a eu du mal avec cela et a trouvé une solution. Voir: Stocker un dictionnaire sous forme de chaîne JSON à l'aide d'EF Core 2.1

La définition de l'entité est la suivante:

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

Dans la méthode OnModelCreating du contexte de base de données, j'appelle simplement HasConversion, qui effectue la sérialisation et la désérialisation du dictionnaire:

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

Cependant, une chose importante que j'ai remarquée est que lors de la mise à jour de l'entité et de la modification des éléments dans le dictionnaire, le suivi des modifications EF ne prend pas en compte le fait que le dictionnaire a été mis à jour, vous devrez donc appeler explicitement la méthode Update sur le DbSet <> pour définir l'entité à modifier dans le suivi des modifications.

0
Maciej Los