实体框架,跟踪包含导航属性的实体更改的通用方法?

时间:2015-11-17 22:20:04

标签: entity-framework change-tracking audit-logging

我已经看到了用于跟踪实体更改的所有问题,但这些解决方案都没有包含导航属性。这甚至可能吗?我不需要一些仅记录所有内容的SUPER通用解决方案。我只想跟踪具体的" main"实体。审计日志将是一个人类可读的自定义日志,所以不只是像我在这里的示例中看到的那样具有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()上调整实体上的值,包括导航属性,所以一旦我调用save,那个旧的导航属性数据消失了。

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

1 个答案:

答案 0 :(得分:0)

不幸的是,没有简单的方法来检测EF SaveChanges上的导航属性更改,特别是对于EF 7(Core)版本。查看thisthis MSDN文章。

所以,如果你没有关系实体,你的实体就是这样的:

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类。因此,您将看到关系的更改是对该实体的更改。

到目前为止,this是我发现获取关系变化的唯一方法,以防您没有关系实体。但是......只适用于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; }
}

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