我目前正试图通过覆盖SaveChanges
方法并利用反射,以通用方式在我的应用中的所有表格上实现历史记录跟踪。作为一个简单的例子,假设我的域对象有2个类/ dbset,每个类的历史表如下:
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:
行:
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 :(得分:1)
您可以使用AutoMapper来映射您的历史实体。我刚刚创建了一个小测试,希望它可以复制你的情况:
iPhone10,1
答案 1 :(得分:1)
我将尝试解释我在申请中实施的方式。
我创建了名称以历史记录结尾的模型,用于在从原始表中删除记录之前需要插入的模型。
<强> 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 的删除方法
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;
}
TModel 是模型的类型
public class CrudRepository<TModel> : DataAccessBase, ICrudRepository<TModel> where TModel : class, new()
public class ProductStatusRepository : CrudRepository<ProductStatus>, IProductStatusRepository
如果要映射复杂权限(如子元素列表),可以覆盖相关存储库中的方法 MapDeletingObjectToHistoyObject 和 MapObjectToObject 。