2017-11-08 1 views
6

私は現在、SaveChangesメソッドをオーバーライドしてリフレクションを利用することで、アプリ内のすべてのテーブルで履歴トラッキングを一般的な方法で実装しようとしています。簡単な場合として、のは、私は私のドメインオブジェクトと、次のようなそれぞれの履歴テーブルのための2クラス/ dbsetsを持っているとしましょう:SaveChangesでヒストリトラッキングを実装する

DbSet<Cat> Cats { get; set; } 
DbSet<CatHistory> CatHistories { get; set; } 
DbSet<Dog> Dogs { get; set; } 
DbSet<DogHistory> DogHistories { get; set; } 

CatHistoryクラスには、次の(DogHistoryは同じスキームを次の)ように見えます

public class CatHistory : HistoricalEntity 
{ 
    public int CatId { get; set; } 

    public virtual Cat Cat{ get; set; } 
} 

マイゴールは、オブジェクトが保存されているときに、適切な履歴テーブルにレコードを挿入したいとします。反射を使用しているときに型の違いを克服するのに問題があります。私の現在の試みは、以下であると私は//TODO:ライン上で立ち往生しているように見える:あなたの歴史的実体をマッピングするためにAutoMapperを使用することができ

 var properties = entry.CurrentValues.PropertyNames.Where(x => entry.Property(x).IsModified).ToList(); 

     //get the history entry type from our calculated typeName 
     var historyType = Assembly.GetExecutingAssembly().GetTypes().FirstOrDefault(x => x.Name == historyTypeName); 

     if(historyType != null) 
     { 

      //modified entries 
      if (dbSet != null && historyDbSet != null && entry.State == EntityState.Modified) 
      { 
      var existingEntry = dbSet.Find(entry.Property("Id").CurrentValue); 

      //create history record and add entry to table 
      var newHistories = GetHistoricalEntities(existingEntry, type, entry); 

      var listType = typeof(List<>).MakeGenericType(new[] { historyType }); 
      var typedHistories = (IList)Activator.CreateInstance(listType); 

      //TODO: turn newHistories (type = List<HistoricalEntity>) into specific list type (List<MyObjectHistory>) so I can addrange on appropriate DbSet (MDbSet<MyObjectHistory>) 

      historyDbSet.AddRange(newHistories); 
      } 
     } 
+0

エンティティから歴史的なエンティティにマッピングする場合は、[Automapper](http://automapper.org/)を試してください。 – CalC

+1

私は歴史的なエンティティのリストを持っていて、可変型にマップしようとしています。私はautomapperにかなり精通していて、これを行う方法を見たhavent – GregH

答えて

1

。私はほんの少しのテストを作成し、うまくいけば、それはあなたの状況を複製:

IList dogs = new List<Dog>() { new Dog { Id = 1, Name = "Alsatian" }, new Dog { Id = 2, Name = "Westie" } }; 
var dogHistoryType = typeof(DogHistory); 
var listType = typeof(List<>).MakeGenericType(new[] { dogHistoryType }); 
var typedHistories = (IList)Activator.CreateInstance(listType); 

mapper.Map(dogs, typedHistories); 

foreach (var historyItem in typedHistories) 
{ 
    this.Add(historyItem); 
} 
+0

これは、あなたが 'typedHistories [1]をFooHistory'として使っているということで、このオブジェクトを' FooHistory'としてキャストする必要はないでしょう。タイプが変数であるため、コード内の型へのハード参照はありません – GregH

+0

上記を編集してより明確にしました(コメント行参照)。 AddRangeをIListで使用できますか?それは少なくともコンパイルしますが、私はそれを試していません。 – CalC

+0

OK、コンテキスト上で直接 'Add()'を使うだけで、IListでforeachを使うことができます。上記の編集を参照してください。 – CalC

0

私は自分のアプリケーションに実装されている方法を説明しようとします。

元のテーブルからレコードを削除する前に、アプリケーションが挿入する必要のあるモデルの場合は、履歴のモデルを作成しました。

BaseModel.cs

namespace ProductVersionModel.Model 
{ 
    using System; 
    using System.ComponentModel.DataAnnotations; 
    using System.ComponentModel.DataAnnotations.Schema; 

    /// <summary> 
    /// all common properties of the tables are defined here 
    /// </summary> 
    public class BaseModel 
    { 
     /// <summary> 
     /// id of the table 
     /// </summary> 
     [Key] 
     public int Id { get; set; } 

     /// <summary> 
     /// user id of the user who modified last 
     /// </summary> 
     public string LastModifiedBy { get; set; } 

     /// <summary> 
     /// last modified time 
     /// </summary> 
     public DateTime LastModifiedTime { get; set; } 


     /// <summary> 
     /// record created user id 
     /// </summary> 
     [Required] 
     public string CreatedBy { get; set; } 

     /// <summary> 
     /// record creation time 
     /// </summary> 
     public DateTime CreationTime { get; set; } 

     /// <summary> 
     /// Not mapped to database, only for querying used 
     /// </summary> 
     [NotMapped] 
     public int RowNumber { get; set; } 
    } 
} 

Product.cs

namespace ProductVersionModel.Model 
{ 
    using System; 
    using System.Collections.Generic; 
    using System.ComponentModel.DataAnnotations; 

    /// <summary> 
    /// store detals of the product 
    /// </summary> 
    public class ProductStatus : BaseModel 
    { 
     /// <summary> 
     /// Name of the product 
     /// </summary> 
     [Required, MaxLength(100)] 
     public string Name { get; set; } 


     /// <summary> 
     /// product version validity start date 
     /// </summary> 

     public DateTime ValidFrom { get; set; } 

     /// <summary> 
     /// product version valid till 
     /// </summary> 
     public DateTime? ValidTill { get; set; } 

     /// <summary> 
     /// This field used to keep track of history of a product 
     /// </summary> 
     public int ProductNumber { get; set; } 

    } 
} 

HistoryBaseModel.cs

using System; 
using System.ComponentModel.DataAnnotations; 
using System.ComponentModel.DataAnnotations.Schema; 

namespace ProductVersionModel.Model.History 
{ 
    public class HistroyBaseModel 
    { 

     /// <summary> 
     /// id of the table 
     /// </summary> 
     [Key, DatabaseGenerated(DatabaseGeneratedOption.None)] 
     public int Id { get; set; } 

     public string DeletedBy { get; set; } 

     public DateTime? DeletedTime { get; set; } 
     /// <summary> 
     /// record created user id 
     /// </summary> 
     [Required] 
     public string CreatedBy { get; set; } 

     /// <summary> 
     /// record creation time 
     /// </summary> 
     public DateTime CreationTime { get; set; } 
    } 
} 

ProductStatusHistory.cs

using System; 
using System.Collections.Generic; 
using System.ComponentModel.DataAnnotations; 
using ProductVersionModel.Model.History; 

// ReSharper disable once CheckNamespace 
namespace ProductVersionModel.Model.History 
{ 
    public class ProductStatusHistory : HistroyBaseModel 
    { 

     /// <summary> 
     /// Name of the product 
     /// </summary> 
     [MaxLength(100)] 
     public string Name { get; set; } 

     /// <summary> 
     /// product version validity start date 
     /// </summary> 
     public DateTime ValidFrom { get; set; } 

     /// <summary> 
     /// product version valid till 
     /// </summary> 
     public DateTime? ValidTill { get; set; } 

     /// <summary> 
     /// This field used to keep track of history of a product 
     /// </summary> 
     public int ProductNumber { get; set; } 

    } 
} 

はあなたCrudRepository TModelのあなたはメソッドをオーバーライドすることができモデル

public class CrudRepository<TModel> : DataAccessBase, ICrudRepository<TModel> where TModel : class, new() 
public class ProductStatusRepository : CrudRepository<ProductStatus>, IProductStatusRepository 

のタイプである

public virtual int Delete(List<object> ids, string userName) 
    { 
     try 
     { 
      foreach (var id in ids) 
      { 
       var dbObject = _table.Find(id); 
       HistroyBaseModel historyRecord = null; 
       var modelAssembly = Assembly.Load(nameof(ProductVersionModel)); 
       var historyType = 
        modelAssembly.GetType(
         // ReSharper disable once RedundantNameQualifier - dont remove namespace it is required 
         $"{typeof(ProductVersionModel.Model.History.HistroyBaseModel).Namespace}.{typeof(TModel).Name}History"); 

       if (historyType != null) 
       { 
        var historyObject = Activator.CreateInstance(historyType); 

        historyRecord = MapDeletingObjectToHistoyObject(dbObject, historyObject, userName); 

        DatabaseContext.Entry(historyRecord).State = EntityState.Added; 
       } 
       DatabaseContext.Entry(dbObject).State = EntityState.Deleted; 
      } 
      return DatabaseContext.SaveChanges(); 
     } 
     catch (DbUpdateException ex) 
     { 
      throw HandleDbException(ex); 
     } 
    } 

    protected virtual HistroyBaseModel MapDeletingObjectToHistoyObject(object inputObject, object outputObject, string userName) 
    { 
     var historyRecord = MapObjectToObject(inputObject, outputObject) as HistroyBaseModel; 
     if (historyRecord != null) 
     { 
      historyRecord.DeletedBy = userName; 
      historyRecord.DeletedTime = DateTime.UtcNow; 
     } 
     return historyRecord; 
    } 

    protected virtual object MapObjectToObject(object inputObject, object outputObject) 
    { 
     var inputProperties = inputObject.GetType().GetProperties(); 
     var outputProperties = outputObject.GetType().GetProperties();//.Where(x => !x.HasAttribute<IgnoreMappingAttribute>()); 
     outputProperties.ForEach(x => 
     { 
      var prop = 
       inputProperties.FirstOrDefault(y => y.Name.Equals(x.Name) && y.PropertyType == x.PropertyType); 
      if (prop != null) 
       x.SetValue(outputObject, prop.GetValue(inputObject)); 
     }); 

     return outputObject; 
    } 

の方法を削除します。 MapDeletingObjectToHistoyObjectおよび子要素リストのような複雑なエンティティをマップする場合は、関連するリポジトリのMapObjectToObjectを使用します。

関連する問題