yamamoWorks

.NET技術を中心に気まぐれに更新していきます

DbContextを動的に使用する

今回はEntity FrameworkのDbContextを動的に使おうというお話です。

Entity Frameworkを普通に使う時はDbContextを継承したクラスにDbSet<モデルクラス>なプロパティを実装しますよね。

public class AddressBookContext : DbContext
{
  public AddressBookContext()
    : base("AddressBookContext")
  {
  }

  public DbSet Persons { get; set; }
}

では、モデルクラスが実行時に決まる場合はどうすればよいか?
実行時にDbModelBuilder.Entity()メソッドを呼んでやりましょう。

public class DynamicDbContext : DbContext
{
  private static readonly MethodInfo DbModelBuilderEntityMethod;

  static DynamicDbContext()
  {
    // DbModelBuilder.Entity() を取得
    DbModelBuilderEntityMethod = typeof(DbModelBuilder).GetMethod("Entity");
  }

  public DynamicDbContext(string nameOrConnectionString, params Type[] entityTypes)
    : base(nameOrConnectionString)
  {
    this.EntityTypes = entityTypes;
  }

  public Type[] EntityTypes { get; private set; }

  protected override void OnModelCreating(DbModelBuilder modelBuilder)
  {
    foreach (var entityType in EntityTypes)
    {
      // DbModelBuilder.Entity() を実行
      _DbModelBuilderEntityMethod.MakeGenericMethod(entityType).Invoke(modelBuilder, new object[] { });
    }
  }
}

DbContextはインスタンスを生成する際の内部処理で、自身に実装されているDbSetなプロパティを探しDbModelBuilder.Entity()メソッドを実行しています。
なのでOnModelCreatingをオーバーライドして任意のモデルクラスでDbModelBuilder.Entity()メソッドを実行すればDbSet
なプロパティを定義しなくともモデルクラスがエンティティとして登録されます。
使い方

// 実行時に型が判明したとする
var newEntities = new[] 
{
  new Person { FirstName = "Naida", LastName="Huber" },
  new Person { FirstName = "Miriam", LastName="Dickson" },
  new Person { FirstName = "Jermaine", LastName="Martin" },
};

var type = newEntities.GetType().GetElementType();

using (var db = new DynamicDbContext("DynamicDbContext", type))
{
  // DbSetプロパティは無いのでSet()メソッドでDbSetインスタンスを取得
  var dbSet = db.Set(type);
 
  Console.WriteLine(dbSet.ElementType); // --> Person

  dbSet.AddRange(newEntities);
  db.SaveChanges();
}

で、これ何が嬉しいのか?

Entity Frameworkはデータベースにテーブルを作成してくれるので、一時的にデータを格納したい場合とか(謎)
定義された型(上例だとPersonクラス)ではなく動的に生成した型と組み合わせると活きてきます。