Entity Framework Coreで辞書プロパティを埋める方法はありますか?
パフォーマンス上の理由から、データベースではなくアプリケーションで検索することを好みます。リストは適切にスケーリングされないため、辞書を使用します。
例(簡略化した例)
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; }
}
私が試したもの
System.InvalidOperationException:プロパティ 'Persons'は、サポートされているプリミティブタイプまたは有効なエンティティタイプではないタイプ 'Dictionary'であるため、マップできませんでした。このプロパティを明示的にマップするか、 '[NotMapped]'属性を使用するか、 'OnModelCreating'の 'EntityTypeBuilder.Ignore'を使用して無視してください。
値の変換 (HasConversion
を使用)を追加してみてください。ただし、変換は1つのアイテムでのみ機能し、コレクションでは機能しません。 HasMany
はすでにコンパイルエラーを出します:
builder
.HasMany<Person>(c => c.Persons) //won't compile, Persons isn't a IEnumerable<Person>
.WithOne().HasForeignKey("PersonId");
カスタムコレクションクラスの作成( Collection<T>
およびInsertItem
、SetItem
などを実装します)–残念ながら、EF Coreがコレクションにアイテムを追加し、その後にプロパティ(少なくとも私たちのOwnsOneプロパティでは、デモのケースにはありません)-SetItem
は後で呼び出されません。
辞書を作成する「計算済み」プロパティを追加すると、セッターは呼び出されません(リストは部分的な値で毎回更新され、上記と少し同じです)。試してみる:
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; }
}
もちろん、リポジトリにディクショナリを構築することもできます( Repository pattern を使用)。ただし、一部のパーツを忘れてしまう可能性があるため、注意が必要です。実行時エラーよりもコンパイル時エラーや宣言型スタイルを優先します。命令型コード。
明確にするために更新
List
を使用すると、マッピングが機能しますあなたは次のようなものになるでしょう:
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
JSONデータを格納するための新しいプロパティPersonsJson
を追加できます。データがDBから取得されるか、DBに格納されるときに、JSONデータが自動的にPersons
プロパティにシリアル化または逆シリアル化されます。 Persons
プロパティはマップされず、PersonsJson
のみがマップされます。
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; }
}
誰かがそれに苦労していて解決策を見つけたようです。参照: EF Core 2.1を使用してJSON文字列として辞書を保存
エンティティの定義は次のとおりです。
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>();
}
データベースコンテキストのOnModelCreatingメソッドでは、HasConversionを呼び出すだけで、辞書のシリアル化と逆シリアル化が行われます。
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));
}
ただし、私が気付いた重要な点の1つは、エンティティを更新してディクショナリのアイテムを変更するときに、EF変更追跡がディクショナリが更新されたという事実を認識しないため、Updateメソッドを明示的に呼び出す必要があることです。変更トラッカーでエンティティを変更済みに設定するDbSet <>。