覆盖 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.Entity是System.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);
}
}
我们有一个实体 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.Entity是System.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);
}
}