将 属性 值存储到另一个 table

Storing a property value into another table

我正在寻找一种方法将 class 的某些属性的值复制到数据库中的另一个 table。有没有什么特别的方法可以将 class 的属性标记为它们的值需要存储在两个地方(一个作为其自己的域对象的一部分,另一个在另一个域对象中,比如摘要)?

我有一个经过审计的实体如下:

public class Audited 
{
    public virtual int Id{ get; set; }

    public virtual string FieldName{ get; set; }

    public virtual string FieldValue{ get; set; }
}

以及其他实体,例如:

public class Plan : FullAuditedEntity
{
    [ToBeAudited]
    public virtual int PlanName{ get; set; }

    public DateTime Date { get; set; }
}

我正在寻找一种能够使用属性(例如 [ToBeAudited])标记 classes 的属性的方法,以便将 属性 值复制到 Audited [=30] =] 在插入或更新时

我已将 [已审核] (Abp.Auditing) 添加到 PlanName 并在日志中收到以下错误:

ERROR 2018-04-20 10:02:09,286 [5 ] Mvc.ExceptionHandling.AbpExceptionFilter - Object reference not set to an instance of an object. System.NullReferenceException: Object reference not set to an instance of an object. at Abp.EntityHistory.EntityHistoryHelper.ShouldSavePropertyHistory(PropertyEntry propertyEntry, Boolean defaultValue) at Abp.EntityHistory.EntityHistoryHelper.GetPropertyChanges(EntityEntry entityEntry) at Abp.EntityHistory.EntityHistoryHelper.CreateEntityChangeInfo(EntityEntry entityEntry) at Abp.EntityHistory.EntityHistoryHelper.CreateEntityChangeSet(ICollection1 entityEntries) at Abp.Zero.EntityFrameworkCore.AbpZeroCommonDbContext3.d__98.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Abp.EntityFrameworkCore.Uow.EfCoreUnitOfWork.d__20.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Abp.EntityFrameworkCore.Uow.EfCoreUnitOfWork.d__12.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Abp.EntityFrameworkCore.Uow.EfCoreUnitOfWork.d__14.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Abp.Domain.Uow.UnitOfWorkBase.d__57.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Abp.AspNetCore.Mvc.Uow.AbpUowActionFilter.d__4.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.d__10.MoveNext() --- End of stack trace from previous location where exception was thrown --- at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context) at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.d__14.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.d__23.MoveNext()

您可以利用 ABP 的 Entity History 功能(无需此自定义即可使用)。

这个答案假设 Audited 是一个 Entity 以便于使用 IRepository,但它不一定是:

public class Audited : Entity
{
    public virtual int EntityId { get; set; }

    public virtual string FieldName { get; set; }

    public virtual string FieldValue { get; set; }
}

首先,执行IEntityHistoryStore:

public class MyEntityHistoryStore : IEntityHistoryStore
{
    private readonly IRepository<Audited> _auditedRepository;

    public MyEntityHistoryStore(IRepository<Audited> auditedRepository)
    {
        _auditedRepository = auditedRepository;
    }

    public async Task SaveAsync(EntityChangeSet entityChangeSet)
    {
        foreach (var entityChange in entityChangeSet.EntityChanges)
        {
            var entityType = entityChange.EntityEntry.As<EntityEntry>().Entity.GetType();

            foreach (var propertyChange in entityChange.PropertyChanges)
            {
                var property = entityType.GetProperty(propertyChange.PropertyName);
                if (property.IsDefined(typeof(ToBeAuditedAttribute)))
                {
                    await _auditedRepository.InsertAsync(new Audited
                    {
                        EntityId = JsonConvert.DeserializeObject<int>(entityChange.EntityId),
                        FieldName = propertyChange.PropertyName,
                        FieldValue = propertyChange.NewValue
                    });
                }
            }
        }
    }
}

接下来,替换服务并在你的模块的PreInitialize方法中添加到Selectors

// using Abp.Configuration.Startup;

Configuration.ReplaceService<IEntityHistoryStore, MyEntityHistoryStore>();
Configuration.EntityHistory.Selectors.Add(
    new NamedTypeSelector(
        "ToBeAuditedEntities",
        type => type.GetProperties().Any(p => p.IsDefined(typeof(ToBeAuditedAttribute)))
    )
);

然后,照常Insert

_planRepository.Insert(new Plan
{
    PlanName = 42
});

Audited table: