c# - EF Core - 如何使用值对象审计跟踪
问题描述
我正在尝试对 Entity Framework Core 中的一系列类实施审计跟踪(跟踪更改的内容、时间和人员)。
我当前的实现依赖于重写 OnSaveChangesAsync:
public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
{
var currentUserFullName = _userService.CurrentUserFullName!;
foreach (var entry in ChangeTracker.Entries<AuditableEntity>())
{
switch (entry.State)
{
case EntityState.Added:
entry.Entity.CreatedBy = currentUserFullName;
entry.Entity.Created = _dateTime.Now;
break;
case EntityState.Modified:
var originalValues = new Dictionary<string, object?>();
var currentValues = new Dictionary<string, object?>();
foreach (var prop in entry.Properties.Where(p => p.IsModified))
{
var name = prop.Metadata.Name;
originalValues.Add(name, prop.OriginalValue);
currentValues.Add(name, prop.CurrentValue);
}
entry.Entity.LastModifiedBy = currentUserFullName;
entry.Entity.LastModified = _dateTime.Now;
entry.Entity.LogEntries.Add(
new EntityEvent(
_dateTime.Now,
JsonConvert.SerializeObject(originalValues),
JsonConvert.SerializeObject(currentValues),
currentUserFullName));
break;
}
}
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
这简单、干净且非常易于使用;任何需要审计跟踪的实体只需要继承AuditableEntity
.
但是,这种方法有一个严重的限制:它无法捕获对导航属性所做的更改。
我们的实体充分利用了值对象,例如电子邮件地址:
public class EmailAddress : ValueObjectBase
{
public string Value { get; private set; } = null!;
public static EmailAddress Create(string value)
{
if (!IsValidEmail(value))
{
throw new ArgumentException("Incorrect email address format", nameof(value));
}
return new EmailAddress {
Value = value
};
}
}
... Entity.cs
public EmailAddress Email { get; set; }
... Entity EF configuration
entity.OwnsOne(e => e.Email);
... Updating
entity.Email = EmailAddress.Create("e@mail.com");
现在,如果用户更改了该实体的电子邮件地址,则该实体的状态永远不会更改为已修改。EF Core 似乎将 ValueObjects 作为导航属性处理,它们是单独处理的。
所以我认为有几个选择:
停止使用 ValueObjects 作为实体属性。我们仍然可以将它们用作实体构造函数参数,但这会导致其余代码的级联复杂性。它还会降低对数据有效性的信任。
停止使用 SaveChangesAsync 并构建我们自己的审核处理。同样,这会导致架构更加复杂,并且可能会降低性能。
ChangeTracker 的一些奇怪的黑客行为 - 这听起来很冒险,但理论上可能有效
还有什么,什么?
解决方案
如果您将值对象映射到数据库中的单个列(例如,电子邮件地址存储在文本列中),您可能可以使用转换器:
var emailAddressConverter = new ValueConverter<EmailAddress, string>(
emailAddress => emailAddress.Value,
@string => EmailAddress.Create(@string));
modelBuilder.Entity<User>()
.Property(user => user.Email)
.HasConversion(emailAddressConverter);
这应该适用于您的更改跟踪代码。
推荐阅读
- django - Django - 在数据库中获取两个条目,一个为空,另一个包含所有数据
- api - 必应新闻搜索 API 返回截断的新闻名称
- javascript - 按给定的数字序列对 Javascript 数组进行排序
- c# - LINQ:选择表中每分钟的第一个值
- android - bottomsheetbehaviour 的生命周期事件是什么?
- python - 在python中解压缩压缩数据的代码?
- c# - Dynamics 365 CRM SDK 或 Rest API
- python - 使用 Scrapy 和 Xpath 检索完整的 url
- python - Django admin inline - 仅在更改对象时显示
- python-3.x - 我对这段代码感到困惑,有人可以解释一下吗