覆盖 SaveChanges 以创建用于版本控制的实体副本

Overriding SaveChanges to Create Copy of Entity for Versioning

我们有一个实体 DataClass,我们希望通过创建对象的副本并使用 ParentVersionId 将更改的对象引用到父对象来保留对该实体的更改.

public class DataClass : IVersionedEntity
{
    public int Id { get; set; }
    public string Adi { get; set; }

    public int? ParentVersionId { get; set; }
    public virtual DataClass? ParentVersion { get; set; }

    public virtual ICollection<DataClass> ParentVersions { get; set; }
}

下面的SerializeDeserialize方法用于创建对象的副本。

public static T SerializeDeserialize<T>(T obj, bool convertComplexProperties = false)
{
    JsonSerializerSettings settings = new JsonSerializerSettings { Converters = { new XXXXDateTimeConverter() } };

    var strSource = JsonConvert.SerializeObject(obj, settings);    
    var destination = JsonConvert.DeserializeObject<T>(strSource, settings);
    return destination;
}

当我们使用 DataClass 类型的参数对象调用 SerializeDeserialize 时,一切正常。然而,我们想要覆盖 DbContext.SaveChangesAsync 并创建副本并将它们添加到上下文中,同时保存更改。

public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())
{
    var modifiedEntries = this.ChangeTracker.Entries().Where(e => e.State == EntityState.Modified);

    foreach (var modifiedEntry in modifiedEntries)
    {
        var clonedEntity = ObjectUtility.SerializeDeserialize(modifiedEntry.Entity) as IVersionedEntity;

        clonedEntity.Id = 0;
        cloned.ParentVersionId = (modifiedEntry.Entity as IVersionedEntity).Id;

        this.Add(clonedEntity);
    }
    return base.SaveChangesAsync(cancellationToken);
}

上面,modifiedEntry.EntitySystem.Object的类型,当SerializeDeserialize 方法用它调用,它 returns System.Object (clonedEntity) 而不是抛出异常的真实类型。

据我所知,将实体添加到 DBContext 的唯一通用方法是 Add 方法。有什么办法可以解决这个问题吗?

更新:

我正在考虑在我的实体上实现一个 IVersionable 接口 类:

public interface IVersionable
{
    int Id { get; set; }
    int? ParentVersionId { get; set; }
}

并将modifiedEntry.OriginalValues.ToObject()投射到该接口,设置相关属性并添加到DBContext。

private void VersionEntity()
{
    var modifiedEntries = this.ChangeTracker.Entries().Where(e => e.State == EntityState.Modified && VersionedTypes.Contains(e.Entity.GetType()) && e.Entity is IVersionable);

    foreach (var modifiedEntry in modifiedEntries)
    {
        if (modifiedEntry.OriginalValues.ToObject() is not IVersionable clonedTypedEntity) 
            continue;

        clonedTypedEntity.Id = 0;
        clonedTypedEntity.ParentVersionId = (modifiedEntry.Entity as IVersionable)?.Id;

        Add(clonedTypedEntity);
    }
}
public override int SaveChanges()
{
    VersionEntity();
    return base.SaveChanges();
}

您必须使用反射来调用泛型方法。我添加了额外的方法 CloneObject 来使用它而不是直接调用 SerializeDeserialize

public static class ObjectUtility
{
    private static readonly MethodInfo _serializeDeserialize =
        typeof(ObjectUtility).GetMethod(nameof(SerializeDeserialize));

    public static T SerializeDeserialize<T>(T obj, bool convertComplexProperties = false)
    {
        JsonSerializerSettings settings = new JsonSerializerSettings { Converters = { new XXXXDateTimeConverter() } };

        var strSource = JsonConvert.SerializeObject(obj, settings);    
        var destination = JsonConvert.DeserializeObject<T>(strSource, settings);
        return destination;
    }

    public static object CloneObject(object obj, bool convertComplexProperties = false)
    {
        var method = _serializeDeserialize.MakeGenericMethod(obj.GetType());
        return method.Invoke(null, new[] { obj, convertComplexProperties });
    }
}

但我也可以提出没有这种克隆的变体:

private void VersionEntity()
{
    ChangeTracker.DetectChanges();
    var modifiedEntries = ChangeTracker.Entries().Where(e => e.State == EntityState.Modified && e.Entity is IVersionable);

    foreach (var modifiedEntry in modifiedEntries)
    {
        var cloned = (IVersionable)Activator.CreateInstance(modifiedEntry.Entity.GetType());
        modifiedEntry.CurrentValues.SetValues(cloned);

        // rollback
        modifiedEntry.CurrentValues.SetValues(modifiedEntry.OriginalValues);

        cloned.Id = 0;
        cloned.ParentVersionId = ((IVersionable)(modifiedEntry).Entity).Id;

        Add((object)cloned);
    }
}