实施 "generic" 机制来处理 Entity Framework 中的时间数据
Implementing a "generic" mechanism to handle temporal data in Entity Framework
我正在尝试使用 Entity Framework.
完成 "generic" 机制来更新我的 SQL 服务器数据库中的时间数据
我所做的是创建一个名为 ITemporalData
的 "marker" 接口,它定义了两个需要存在的属性 - DateTime ValidFrom
和 DateTime? ValidTo
.
public interface ITemporalData
{
DateTime ValidFrom { get; set; }
DateTime? ValidTo { get; set; }
}
我希望在我的 DbContext.SaveChanges()
重写中实施 "generic" 方法:
- 克隆任何
ITemporalData
对象,这将给我一个要存储的新对象 (EntityState.Added
),并将其 ValidFrom
值设置为当前日期和时间
- 将修改后的原始条目重置为其数据库值(在实体上调用
.Reset()
),然后将 "old" 记录的 ValidTo
设置为当前日期和时间
虽然我可以像这样轻松过滤掉 SaveChanges()
覆盖中修改过的 ITemporalData
对象:
public partial class MyDbContext
{
// override the "SaveChanges" method
public override int SaveChanges()
{
DateTime currentDateTime = DateTime.Now;
// get the modified entities that implement the ITemporalData interface
IEnumerable<DbEntityEntry<ITemporalData>> temporalEntities = ChangeTracker.Entries<ITemporalData>().Where(e => e.State == EntityState.Modified);
foreach (var temporalEntity in temporalEntities)
{
// how would I do that, really? I only have an interface - can't clone an interface......
var cloned = temporalEntity.Entity.Clone();
// and once it's cloned, I would need to add the new record to the correct DbSet<T> to store it
// set the "old" records "ValidTo" property to the current date&time
temporalEntity.Entity.ValidTo = currentDateTime;
}
return base.SaveChanges();
}
}
我正在努力使用 "clone the modified record" 方法 - 我只有一个 ITemporalData
接口,真的 - 但克隆(使用 AutoMapper 或其他方法)总是取决于 实际,底层具体数据类型.....
要克隆实体,您可以通过反射创建新实例 (Activator.CreateInstance
) 并通过反射将所有原始(非导航)属性复制到它。最好不要为此使用自动映射器工具,因为它们也会访问导航属性,这可能会导致延迟加载(或至少确保禁用延迟加载)。
如果您不喜欢反射(请注意自动映射器无论如何都会使用它)- 您也可以从 ICloneable
继承接口并为每个 ITemporalData
实现 Clone
方法] 实体(如果您的实体是自动生成的 - 为此使用部分 class)。然后每个实体自己决定如何克隆,而不进行任何反思。如果您的克隆逻辑很复杂(例如涉及从导航属性克隆相关对象),这种方式也有好处。
要添加实体以更正 DbSet,请使用 DbContext
的非类型化 Set 方法:
this.Set(temporalEntity.GetType()).Add(temporalEntity);
您可以将此 Clone
方法添加到您的上下文中:
T Clone<T>(DbEntityEntry<T> entry)
where T : class
{
var proxyCreationEnabled = this.Configuration.ProxyCreationEnabled;
try
{
this.Configuration.ProxyCreationEnabled = false;
var clone = (T)entry.CurrentValues.ToObject();
Set(clone.GetType()).Add(clone);
return clone;
}
finally
{
this.Configuration.ProxyCreationEnabled = proxyCreationEnabled;
}
}
并按如下方式使用:
var cloned = Clone(temporalEntity);
clone.GetType
将 return 克隆对象的实际类型,而 T
将是编译时类型,ITemporalData
.
这使用 EF 自己的基础设施创建克隆,这无疑比反射更快。
虽然克隆的状态立即设置为Added
,但它不会执行延迟加载。但是确保克隆永远不会成为代理可能更安全,因此,如果您决定对克隆做其他事情,永远不会触发延迟加载。 (感谢 Evk 的热心评论)。
我正在尝试使用 Entity Framework.
完成 "generic" 机制来更新我的 SQL 服务器数据库中的时间数据我所做的是创建一个名为 ITemporalData
的 "marker" 接口,它定义了两个需要存在的属性 - DateTime ValidFrom
和 DateTime? ValidTo
.
public interface ITemporalData
{
DateTime ValidFrom { get; set; }
DateTime? ValidTo { get; set; }
}
我希望在我的 DbContext.SaveChanges()
重写中实施 "generic" 方法:
- 克隆任何
ITemporalData
对象,这将给我一个要存储的新对象 (EntityState.Added
),并将其ValidFrom
值设置为当前日期和时间 - 将修改后的原始条目重置为其数据库值(在实体上调用
.Reset()
),然后将 "old" 记录的ValidTo
设置为当前日期和时间
虽然我可以像这样轻松过滤掉 SaveChanges()
覆盖中修改过的 ITemporalData
对象:
public partial class MyDbContext
{
// override the "SaveChanges" method
public override int SaveChanges()
{
DateTime currentDateTime = DateTime.Now;
// get the modified entities that implement the ITemporalData interface
IEnumerable<DbEntityEntry<ITemporalData>> temporalEntities = ChangeTracker.Entries<ITemporalData>().Where(e => e.State == EntityState.Modified);
foreach (var temporalEntity in temporalEntities)
{
// how would I do that, really? I only have an interface - can't clone an interface......
var cloned = temporalEntity.Entity.Clone();
// and once it's cloned, I would need to add the new record to the correct DbSet<T> to store it
// set the "old" records "ValidTo" property to the current date&time
temporalEntity.Entity.ValidTo = currentDateTime;
}
return base.SaveChanges();
}
}
我正在努力使用 "clone the modified record" 方法 - 我只有一个 ITemporalData
接口,真的 - 但克隆(使用 AutoMapper 或其他方法)总是取决于 实际,底层具体数据类型.....
要克隆实体,您可以通过反射创建新实例 (Activator.CreateInstance
) 并通过反射将所有原始(非导航)属性复制到它。最好不要为此使用自动映射器工具,因为它们也会访问导航属性,这可能会导致延迟加载(或至少确保禁用延迟加载)。
如果您不喜欢反射(请注意自动映射器无论如何都会使用它)- 您也可以从 ICloneable
继承接口并为每个 ITemporalData
实现 Clone
方法] 实体(如果您的实体是自动生成的 - 为此使用部分 class)。然后每个实体自己决定如何克隆,而不进行任何反思。如果您的克隆逻辑很复杂(例如涉及从导航属性克隆相关对象),这种方式也有好处。
要添加实体以更正 DbSet,请使用 DbContext
的非类型化 Set 方法:
this.Set(temporalEntity.GetType()).Add(temporalEntity);
您可以将此 Clone
方法添加到您的上下文中:
T Clone<T>(DbEntityEntry<T> entry)
where T : class
{
var proxyCreationEnabled = this.Configuration.ProxyCreationEnabled;
try
{
this.Configuration.ProxyCreationEnabled = false;
var clone = (T)entry.CurrentValues.ToObject();
Set(clone.GetType()).Add(clone);
return clone;
}
finally
{
this.Configuration.ProxyCreationEnabled = proxyCreationEnabled;
}
}
并按如下方式使用:
var cloned = Clone(temporalEntity);
clone.GetType
将 return 克隆对象的实际类型,而 T
将是编译时类型,ITemporalData
.
这使用 EF 自己的基础设施创建克隆,这无疑比反射更快。
虽然克隆的状态立即设置为Added
,但它不会执行延迟加载。但是确保克隆永远不会成为代理可能更安全,因此,如果您决定对克隆做其他事情,永远不会触发延迟加载。 (感谢 Evk 的热心评论)。