2017-04-10 13 views
2

私はすべてのエンティティに4つのカスタムフィールドを持っています。 4つのフィールドは、CreatedBy,CreatedDate,UpdatedByおよびUpdatedDateです。Entity Frameworkコアの作成および更新フィールド

エンティティフレームワークのコアイベントにフックする方法があります。挿入すると、現在のユーザーの現在のDateTimeCreatedByが挿入されます。CreatedDateデータベースへの更新があると、に現在のユーザーの現在のDateTimeUpdatedByが入力されますか?

答えて

4

基本的には@ Steveのアプローチがありますが、現在の実装ではプロジェクトの単体テストが難しくなります。

リファクタリングを少し行うだけで、単体テストに対応し、SOLIDの原則とカプセル化に真正面を向けることができます。

ここではスティーブの例のリファクタリングバージョン

public abstract class AuditableEntity 
{ 
    public DateTime CreatedDate { get; set; } 
    public string CreatedBy { get; set; } 
    public DateTime UpdatedDate { get; set; } 
    public string UpdatedBy { get; set; } 
} 

public class AuditableDbContext : DbContext 
{ 
    protected readonly IUserService userService; 
    protected readonly DbContextOptions options; 
    protected readonly ITimeService timeService; 

    public BaseDbContext(DbContextOptions options, IUserService userService, ITimeService timeService) : base(options) 
    { 
     userService = userService ?? throw new ArgumentNullException(nameof(userService)); 
     timeService = timeService ?? throw new ArgumentNullException(nameof(timeService)); 
    } 

    public override int SaveChanges() 
    { 
     // get entries that are being Added or Updated 
     var modifiedEntries = ChangeTracker.Entries() 
       .Where(x => (x.State == EntityState.Added || x.State == EntityState.Modified)); 

     var identityName = userService.CurrentUser. 
     var now = timeService.CurrentTime; 

     foreach (var entry in modifiedEntries) 
     { 
      var entity = entry.Entity as AuditableEntity; 

      if (entry.State == EntityState.Added) 
      { 
       entity.CreatedBy = identityName ?? "unknown"; 
       entity.CreatedDate = now; 
      } 

      entity.UpdatedBy = identityName ?? "unknown"; 
      entity.UpdatedDate = now; 
     } 

     return base.SaveChanges(); 
    } 
} 

今ではモック時間に簡単で、ユニットテストとモデル/ドメイン/ビジネス層のためのユーザー/プリンシパルがより良いあなたのドメインロジックをカプセル化し、EFコアの依存関係がないですもう少し良い。

もちろん、これをさらに戦略的パターンを使用してモジュラーアプローチを使用するようにリファクタリングすることはできますが、それは範囲外です。また、監査可能な(およびソフト削除)EFコアDbContext(hereおよびhere)の実装を提供するASP.NET Core Boilerplateを使用することもできます。

+0

私はこれの考えが好きです。 1つの問題は、異なるタイプの複数のdbcontextを維持していない限り、監査可能でないエンティティ、またはセーブ設定の異なる種類のエンティティを持つ場合、この方法は機能しません。 – Steve

+1

@Steve: 'ChangeTracker.Entries().TypeOf ()'は既に監査対象のエンティティをフィルタリングします。しかし、それをさらにモジュール化したい場合は、戦略パターンを使用して、複数の 'IEntitySaveHandler'または' IEntitySaveHandler'sに分割してから、 'CanHandle'メソッドと' Handle'メソッドをメソッドに渡す必要があります。次に、各ハンドラはエンティティに適用できるかどうかを確認し、エンティティに適用できるかどうかを確認し、変更されている場合は変更を行います。このアプローチは、開かれた/閉じられた原則(拡張のために開いて、変更のために開かれている)を尊重し、ベースのDbContextを変更せずに新しいハンドラを追加することもできます – Tseng

+0

しかし、これは単一の質問の範囲外です。 – Tseng

1

「監査」フィールドと同じレイアウトを使用しています。

私がこれを解決したのは、AuditableEntityという名前の基本抽象クラスを作成してプロパティを保持し、PrepareSaveというメソッドを公開することでした。必要に応じてPrepareSave内部Iは、フィールドの値を設定します。

public abstract class AuditableEntity 
{ 
    public DateTime CreatedDate { get; set; } 
    public string CreatedBy { get; set; } 
    public DateTime UpdatedDate { get; set; } 
    public string UpdatedBy { get; set; } 

    public virtual void PrepareSave(EntityState state) 
    { 
     var identityName = Thread.CurrentPrincipal.Identity.Name; 
     var now = DateTime.UtcNow; 

     if (state == EntityState.Added) 
     { 
      CreatedBy = identityName ?? "unknown"; 
      CreatedDate = now; 
     } 

     UpdatedBy = identityName ?? "unknown"; 
     UpdatedDate = now; 
    } 
} 

は私がしたい場合ので、私は私のエンティティでそれをオーバーライドすることができ PrepareSave仮想をしました。実装に応じてIDを取得する方法を変更する必要があるかもしれません。

は、これを呼び出すには、私は私のDbContextSaveChangesを上書きして(私は変更トラッカから得た)追加または更新されていた各エンティティにPrepareSaveと呼ば:今すぐ

public override int SaveChanges() 
{ 
    // get entries that are being Added or Updated 
    var modifiedEntries = ChangeTracker.Entries() 
      .Where(x => x.State == EntityState.Added || x.State == EntityState.Modified); 

    foreach (var entry in modifiedEntries) 
    { 
     // try and convert to an Auditable Entity 
     var entity = entry.Entity as AuditableEntity; 
     // call PrepareSave on the entity, telling it the state it is in 
     entity?.PrepareSave(entry.State); 
    } 

    var result = base.SaveChanges(); 
    return result; 
} 

、私は上SaveChangesを呼び出すたび私のDbContext(直接またはリポジトリを介して)、AuditableEntityを継承するすべてのエンティティは、必要に応じて監査フィールドを設定します。

+0

また、この機能を** Interface ** 'IAuditableEntity' soエンティティクラスをすべて基底クラスから継承するよう強制する必要はありません –

+1

@marc_s私はインターフェイスを持っていますが(実際にはもっとレベルが2つあります)、AuditableEntityには関連するロジックが含まれているためフィールドを設定します。 – Steve

+0

これは最適な解決策ではありません。 'PrepareSave'メソッドは少なくとも2つのベストプラクティスに違反しています:1)SOLIDからのSRP:単一の責任原則、' AuditableEntity'には複数の責任があります:更新するフィールドの追跡とそれ自身の状態の保持と分離の違反懸念事項の2)カプセル化にも違反します: 'EntityState'をすべてのエンティティのパブリックメソッドのパラメータとして宣言することで**ドメイン/ビジネスレイヤーなどの**すべてのレイヤーを持続技術 – Tseng

関連する問題