見出し画像

[Entity Framework] メタデータ列(データ作成/更新日時など)の値設定

データ作成/更新日時などメタデータ列の値設定は共通処理にすると便利です。
Entity Framework ではどのタイミングでどのように設定すればよいでしょうか。

※データベースのトリガーにしないこと、ローカルのシステム日付を使用することの是非についてはここでは触れません。

方法はいくつかあります(一番きれいなのは最後の方式です)。

データバインドコントロールのイベント

たとえば ASP.NET Web フォームの場合、以下のハンドラで e.Values(Insert の場合)または e.NewValues(Update の場合)の各カラム要素に値を設定すると反映されます。

・GridView.RowUpdating イベント
・ListView.ItemInserting/ItemUpdating イベント
・DetailsView.ItemInserting/ItemUpdating イベント
・FormView.ItemInserting/ItemUpdating イベント

データソースコントロールのイベント

たとえば ASP.NET Web フォームの場合、以下のハンドラでエンティティを取得してプロパティに設定することができます。

・ObjectDataSource.Inserting/Updating イベント
 e.InputParameters[0] にエンティティが格納されています。
・EntityDataSource.Inserting/Updating イベント
 e.Entity にエンティティが格納されています。

SaveChanges メソッド(dynamic 方式)

個別のデータや操作に依存した値を設定するのには向きませんが、データ作成/更新日時の設定であれば、今回ご紹介した中で最も確実で実装効率のよい方法と言えます。
ここでは DbContext(EF 4.1 ~)の SaveChanges メソッドをオーバーライドして作成日時を設定する例をご紹介します。

DbContextの部分クラス

public partial class SampleEntities
{
   public override int SaveChanges()
   {
       SetCreatedDateTime();
       return base.SaveChanges();
   }
   
   private void SetCreatedDateTime()
   {
       DateTime now = DateTime.Now;
       // 追加エンティティのうち、CreatedDateTime プロパティを持つものを抽出
       var entities = this.ChangeTracker.Entries()
           .Where(e => e.State == EntityState.Added && e.CurrentValues.PropertyNames.Contains("CreatedDateTime"))
           .Select(e => e.Entity);
       
       foreach (dynamic entity in entities)
       {
           entity.CreatedDateTime = now;
       }
   }
}

SaveChanges メソッド(インターフェイス方式)

エンティティの部分クラス定義(インターフェイス実装)を手動で行う必要がありますが、dynamic 方式よりきれいです。
エンティティを追加したときなど、部分クラス定義を忘れないように注意が必要です(そのためのユニットテストが一番下にあります)。

Code First なら BaseEntity 等の抽象クラスに定義するのがいいですね。

IEntity.cs(エンティティのインターフェイス)

public interface IEntity
{
   int? CreatedUserId { get; set; }
   DateTime? CreatedDateTime { get; set; }
   int? UpdatedUserId { get; set; }
   DateTime? UpdatedDateTime { get; set; }
}

EntityPartials.cs(エンティティの部分クラス定義)

public partial class Foo : IEntity {}
public partial class Bar : IEntity {}
: 

DbContext の部分クラス

public partial class SampleEntities
{
   public override int SaveChanges()
   {
       SetCreatedDateTime();
       return base.SaveChanges();
   }
   
   private void SetCreatedDateTime()
   {
       DateTime now = DateTime.Now;
       // 追加エンティティのうち、IEntity を実装したものを抽出
       var entities = this.ChangeTracker.Entries<IEntity>()
           .Where(e => e.State == EntityState.Added)
           .Select(e => e.Entity);
        
       foreach (var entity in entities)
       {
           entity.CreatedDateTime = now;
       }
   }
}

IEntity の実装漏れを検出するユニットテスト

[TestMethod]
public void EntitiesShouldImplementIEntity()
{
   var entityTypes = typeof(SampleEntities)
       .GetProperties(BindingFlags.Public | BindingFlags.Instance)
       .Where(p => p.PropertyType.IsGenericType && p.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>))
       .Select(p => p.PropertyType.GetGenericArguments().Single());
   
   foreach (var type in entityTypes)
   {
       if (type == typeof(Baz))
       {
           // 除外エンティティ
           continue;
       }
       Assert.IsTrue(typeof(IEntity).IsAssignableFrom(type), String.Format("{0} は IEntity を実装していません。", type.FullName));
   }
}

この記事が気に入ったらサポートをしてみませんか?