web-dev-qa-db-ja.com

EF Core:辞書プロパティを使用する

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> およびInsertItemSetItemなどを実装します)–残念ながら、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 を使用)。ただし、一部のパーツを忘れてしまう可能性があるため、注意が必要です。実行時エラーよりもコンパイル時エラーや宣言型スタイルを優先します。命令型コード。

明確にするために更新

  • これはコードファーストのアプローチではありません
  • eF Coreでマッピングを変更するため、データベースは変更されません。 -データベースにわざとタグを付けていません;)
  • 辞書の代わりにListを使用すると、マッピングが機能します
  • これは、データベースの1:nまたはn:mの関係です(HasMany-WithOneを参照)
4
Julian
  1. EFによって生成されたタイプの部分クラスを作成します。
  2. 辞書を保持するラッパークラスを作成するか、IDictionaryを実装します。
  3. Add関数を実装して、EFが使用するリストにも値を追加します。
  4. Personsリストまたはディクショナリを操作するメソッドが初めて呼び出されたときに、それらが適切に初期化されていることを確認してください

あなたは次のようなものになるでしょう:

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
  • personsのリストに直接アクセスする場合、リストに追加するとディクショナリに追加されるように部分クラスも変更する必要があります(おそらくPersonsリストのラッパーまたはラッパークラスをすべて一緒に使用します)
  • 大きなデータセットを扱う場合、または最適化が必要な場合、たとえば、新しいディクショナリを設定するときにすべての要素を削除/再追加しないなど、いくつかの改善が必要です
  • 要件によっては、他のイベントとカスタムロジックを実装する必要がある場合があります
0
BoldAsLove

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; }
}
0
Martin Staufcik

誰かがそれに苦労していて解決策を見つけたようです。参照: 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 <>。

0
Maciej Los