I will try to explain the way I have implemented in my application.
I have created Models with name ending History for models for which application needs to insert before deleting the record from the original table.
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; }
}
}
In Delete method of your 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;
}
Where TModel is the type of the model
public class CrudRepository<TModel> : DataAccessBase, ICrudRepository<TModel> where TModel : class, new()
public class ProductStatusRepository : CrudRepository<ProductStatus>, IProductStatusRepository
You can override methods MapDeletingObjectToHistoyObject and MapObjectToObject in your related repository if you want to map complex entitites like child element list.