【问题标题】:Override SaveChangesAsync Entity Framework 6 to log modified property values覆盖 SaveChangesAsync Entity Framework 6 以记录修改的属性值
【发布时间】:2021-05-23 14:15:37
【问题描述】:

我正在开发一个 ASP.NET Core 项目,在 SaveChanges 中,我需要记录更新后的值

根据this,我尝试覆盖SaveChangesAsync,但出现此错误:

无法将“MyProject.ApplicationDbContext”类型的对象转换为“System.Data.Entity.Infrastructure.IObjectContextAdapter”类型。

代码:

public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
    ChangeTracker.DetectChanges();

    ObjectContext ctx = ((IObjectContextAdapter)this).ObjectContext;

    List<ObjectStateEntry> objectStateEntryList = ctx.ObjectStateManager
                                                     .GetObjectStateEntries((System.Data.Entity.EntityState)(EntityState.Added | EntityState.Modified | EntityState.Deleted))
                                                     .ToList();

    foreach (ObjectStateEntry entry in objectStateEntryList)
    {
        if (!entry.IsRelationship)
        {
            switch (entry.State)
            {
                case (System.Data.Entity.EntityState)EntityState.Added:
                    // write log...
                    break;

                case (System.Data.Entity.EntityState)EntityState.Deleted:
                    // write log...
                    break;

                case (System.Data.Entity.EntityState)EntityState.Modified:
                    foreach (string propertyName in entry.GetModifiedProperties())
                    {
                        DbDataRecord original = entry.OriginalValues;
                        string oldValue = original.GetValue(original.GetOrdinal(propertyName)).ToString();

                        CurrentValueRecord current = entry.CurrentValues;
                        string newValue = current.GetValue(current.GetOrdinal(propertyName)).ToString();

                        if (oldValue != newValue) // probably not necessary
                        {
                            var a = string.Format("Entry: {0} Original :{1} New: {2}",
                                                  entry.Entity.GetType().Name,
                                                  oldValue, newValue);
                        }
                    }
                    break;
           }
       }
   }

   return base.SaveChangesAsync();
}

我也尝试在 Action 方法中编写日志字符串,但我得到了同样的错误。

public async Task<IActionResult> Edit(MyModel model)
{
    ...
    // _context is ApplicationDbContext()
    var myObjectState = _context.ObjectStateManager.GetObjectStateEntry(model);
    var modifiedProperties = myObjectState.GetModifiedProperties();

    foreach (var propName in modifiedProperties)
    {
        Console.WriteLine("Property {0} changed from {1} to {2}",
                          propName,
                          myObjectState.OriginalValues[propName],
                          myObjectState.CurrentValues[propName]);
    }
}

我正在使用 EF 6。

我需要记录属性名称、旧值和新值。我不明白为什么会出现这个错误,我做错了什么?

【问题讨论】:

    标签: c# asp.net-mvc entity-framework entity-framework-6


    【解决方案1】:

    我做错了什么?

    使用 EF 4.1 中的示例? :)

    您可以利用 EF 6 中的 ChangeTracker 来完成您正在寻找的内容。我建议根据需要为您的日志记录设置一个单独的 DbContext,其中仅包含日志记录表和 FK:

    在您的 SaveChanges/SaveChangesAsync 覆盖范围内:

    var updatedEntities = ChangeTracker.Entries()
        .Where(x => x.State == EntityState.Modified);
    
    var insertedEntities = ChangeTracker.Entries()
        .Where(x => x.State == EntityState.Added);
    

    从这里您可以分别通过OriginalValuesCurrentValues 访问原始和修改的属性值。

    ** 更新 ** 报告更改时,典型的简单方法是在 OriginalValues 和 CurrentValues 上使用 ToObject(),然后序列化为 Json 之类的东西,以捕获相关对象的前后状态。但是,这将显示所有修改和未修改的值。

    遍历这些值以查找实际更改有点复杂:

    foreach(var entity in updatedEntities)
    {
        foreach (var propertyName in entity.OriginalValues.PropertyNames)
        {
            if (!object.Equals(entity.OriginalValues.GetValue<object>(propertyName), 
            entity.CurrentValues.GetValue<object>(propertyName)))
            {
                var columnName = propertyName;
                var originalValue = entity.OriginalValues.GetValue<object>(propertyName)?.ToString() ?? "#null";
                var updatedValue = entity.CurrentValues.GetValue<object>(propertyName)?.ToString() ?? "#null";
                var message = $"The prop: {columnName} has been changed from {originalValue} to {updatedValue}";
            }
        }
    }
    

    【讨论】:

    • 如何获取属性名、旧值和新值?我做了一个 foreach 循环,我循环遍历的每个实体,我试图组成一个这样的字符串:string.Format("The prop: {0} has been changed from {1} to {2}", prop.ToString(), prop.OriginalValues, prop.CurrentValues);
    • 我已经扩展了答案以提供用于生成更改历史记录的选项。
    • OriginalValues 不包含 PropertyNames。我已经尝试过这种方式。如果将整个对象传递给 context.Update(objectToUpdate) 会发生什么? - OriginalValues 和 CurrentValues 将是相同的
    • ?我正在使用 EF 6,我可以从 OriginalValues 访问 PropertyNames。确保您使用的是 var updatedEntities = ChangeTracker.Entries().Where(x =&gt; x.State == EntityState.Modified); .. 如果您使用的是 EF Core 而不是 EF 6,则 OriginalValues 具有 Properties 这是包含 .Name 属性的对象。我不建议使用context.Update(objectToUpdate),因为这会为所有列生成一个更新语句,无论它们是否发生变化。如果您想跟踪更新,最好让 EF 更改跟踪器管理它。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多