2

我已经看到了所有用于跟踪实体更改的问题,但这些解决方案都没有包含导航属性。这甚至可能吗?我不需要一些只记录所有内容的超级通用解决方案。我只想跟踪特定“主要”实体的事情。并且审计日志将是一个人类可读的自定义日志,所以不仅仅是一些具有 oldData newData 的表,就像我在此处的示例中看到的那样。

除了跟踪导航属性之外,我几乎已经做到了这一点,我在 POCO 的服务上有一个接口来确定我是否要跟踪更改,然后在我的entityService.Save方法中我有这个检查:

var trackChangesService = this as ITrackChangesService<TClass>;
if (trackChangesService != null)
{
      TClass oldEntity = Repository.GetOldEntity(entity);
      trackChangesService.SaveChanges(entity, oldEntity);
}

保存更改方法是 ITrackChanges 接口公开的内容,并且将在每个实体的基础上具有自定义逻辑(这包括电子邮件通知等),但问题是我无法正确获取 oldEntity。目前该方法如下所示:

public TClass GetOldEntity(TClass entity)
    {
        //DbEntityEntry<TClass> oldEntry = Context.Entry(entity);
        ////oldEntry.CurrentValues.SetValues(oldEntry.OriginalValues);
        //var values = oldEntry.OriginalValues;
        //return (TClass)oldEntry.GetDatabaseValues().ToObject();

        return Context.Set<TClass>().AsNoTracking().FirstOrDefault(x => x.Id == entity.Id);
    }

你可以看到我一直在玩的代码。我可以很好地获取常规属性的旧值和新值,但导航属性不会延续。但我需要这些信息,例如我正在编辑一个用户,在编辑用户屏幕上,可以删除和添加组。单击保存按钮时,它会执行这些更新。

所以我的更新视图如下所示:

private UserSaveModel Update(Guid id, UserSaveModel userSaveModel)
    {
        User user = _userService.GetOne(x => x.Id == id);
        user = _userService.ConvertFromSaveModel(userSaveModel, user);

        _userService.Save(user);

        return userSaveModel;
    }

我想解决问题的一种方法是,我正在调整.ConvertFromSaveeModel()现有用户记录中上下文中实体的值,包括导航属性,所以一旦我调用保存,旧的导航属性数据就消失了。

是否有使用此工作流获取实体的原始导航属性信息?如果没有,我该如何更改工作流程以使其成为可能?如果我一开始不查询用户对象,而是从头开始创建一个,那么一些可能不会在 viewModel 中公开的数据将不会传递,所以我不确定这是否会起作用。还有其他建议吗?也许在我进行更改之前克隆实体并将其用作原始实体?这会继承导航属性吗?

4

1 回答 1

0

不幸的是,没有简单的方法可以检测 EF 上的 Navigation Properties 更改SaveChanges,特别是对于 EF 7 (Core) 版本。检查这个这个MSDN 文章。

因此,如果您没有关系 entity,并且您的实体是这样相关的:

public class User
{
    [Key]
    public int UserId { get; set; }
    public ICollection<Department> Departments { get; set; }
}

public class Department
{
    [Key]
    public int DepartmentId { get; set; }
    public ICollection<User> Users { get; set; }
}

并且您更改导航属性,例如您将部门添加到用户,如下所示:

using (var ctx = new MyContext())
{
    var user1 = ctx.Users.Include(u => u.Departments).First(u => u.Id == 1);
    var dept3 = ctx.Departments.First(d => d.Id == 3);

    user1.Departments.Add(dept3);

    ctx.SaveChanges();
}

Audit.EntityFramework不会检测到更改,因为您进行了关系更改,但没有进行实体更改。

你可以避免这个问题,为你的关系创建一个实体,即一个UserDepartment类。因此,您会将关系的更改视为对该实体的更改。

到目前为止,是我发现在没有关系实体的情况下获取关系更改的唯一方法。但是...仅适用于 EF 6,所以我没有将它包含在我的库中。

另一种解决方法是确保更改实体的另一个属性值(即LastUpdatedDate列),或者在 之前将实体显式标记为已修改SaveChanges,以便库可以检测到更改,并且至少您会看到实体当前导航属性。

例如:

using (var ctx = new MyContext())
{
    var user1 = ctx.Users.Include(u => u.Departments).First(u => u.Id == 1);
    var dept3 = ctx.Departments.First(d => d.Id == 3);
    user1.Departments.Add(dept3);
    ctx.Entry(user1).State = EntityState.Modified; // <-- Here
    ctx.SaveChanges();
}

现在库将检测到更改,并将包含有关更改的一些数据。

这就是输出(作为 JSON)的样子:

{
    "EventType": "MyContext",
    //...
    "EntityFrameworkEvent": {
        "Database": "Users",
        "Entries": [{
            "Table": "User",
            "Action": "Update",
            "PrimaryKey": {
                "UserId": 1
            },
            "Entity": {
                "UserId": 1,
                "Username": "federico",
                "Departments": [{
                    "DepartmentId": 3,
                    "DepartmentName": "Dept3",
                    "Users": []
                }]
            },
            "Changes": [],
            "ColumnValues": {
                "UserId": 1,
                "Username": "federico"
            },
            "Valid": true,
            "ValidationResults": []
        }],
        "Result": 1,
        "Success": true
    }
}

您需要从 AuditDbContext 继承上下文并将 IncludeEntityObjects 设置为 true:

[AuditDbContext(IncludeEntityObjects = true)]
public class MyContext : AuditDbContext
{
    public DbSet<User> Users { get; set; }
    public DbSet<Department> Departments { get; set; }
}

我知道这不是您问题的解决方案,但它可能会有所帮助。

于 2016-09-10T02:59:55.297 回答