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
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
List
au lieu d'un dictionnaire, le mappage fonctionneVous 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
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; }
}
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.