2017-11-08 29 views
6

Tôi hiện đang cố gắng triển khai theo dõi lịch sử trên tất cả các bảng trong ứng dụng của mình theo cách tổng quát bằng cách ghi đè phương pháp SaveChanges và tận dụng phản ánh. Là một trường hợp đơn giản, giả sử tôi có 2 lớp/dbsets cho các đối tượng tên miền của tôi và một bảng lịch sử đối với từng giống như sau:Thực hiện theo dõi lịch sử trong SaveChanges ghi đè

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

Lớp CatHistory trông giống như sau (DogHistory sau chương trình tương tự):

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

    public virtual Cat Cat{ get; set; } 
} 

Mục tiêu của tôi là khi một đối tượng được lưu, tôi muốn chèn bản ghi vào bảng lịch sử thích hợp. Tôi gặp sự cố khi khắc phục sự khác biệt về loại khi sử dụng phản chiếu. nỗ lực hiện tại của tôi là dưới đây và tôi dường như bị mắc kẹt trên dòng //TODO::

 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

Nếu bạn đang tìm cách ánh xạ từ một thực thể đến thực thể 'lịch sử', hãy thử [Automapper] (http://automapper.org/) – CalC

+1

Tôi có một danh sách các thực thể lịch sử và tôi đang cố gắng ánh xạ tới một loại biến .Tôi khá quen thuộc với automapper và havent nhìn thấy một cách để làm điều này – GregH

Trả lời

1

Bạn có thể sử dụng AutoMapper để lập bản đồ thực thể lịch sử của mình. Tôi vừa tạo một thử nghiệm nhỏ, hy vọng nó sẽ tái tạo tình huống của bạn:

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

Vấn đề với điều này là bạn đang sử dụng 'typedHistories [1] như FooHistory' và tôi sẽ không nhất thiết phải biết để cast đối tượng như là một' FooHistory' . Không thể có bất kỳ tham chiếu cứng nào đối với các loại trong mã vì loại này biến là – GregH

+0

Đã chỉnh sửa ở trên để làm cho nó rõ ràng hơn (xem dòng nhận xét). Bạn có thể AddRange với một IList? Nó ít nhất là biên dịch, nhưng tôi đã không thử nó. – CalC

+0

OK, chúng ta có thể sử dụng một foreach trên IList như chỉ sử dụng 'Add()' trực tiếp trên bối cảnh. Xem phần chỉnh sửa ở trên. – CalC

0

Tôi sẽ cố gắng giải thích cách tôi đã triển khai trong đơn đăng ký của mình.

Tôi đã tạo Mô hình có tên kết thúc Lịch sử cho các mô hình mà ứng dụng cần chèn trước khi xóa bản ghi khỏi bảng gốc.

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; } 

    } 
} 

Trong Xóa phương pháp CrudRepository bạn

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; 
    } 

đâu TModel là loại mô hình

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

Bạn có thể ghi đè lên các phương pháp MapDeletingObjectToHistoyObjectMapObjectToObject trong kho lưu trữ có liên quan của bạn nếu bạn muốn ánh xạ các quyền lợi phức tạp như danh sách phần tử con.

Các vấn đề liên quan