如何将本地上下文中的实体与 EF Core 数据库中的实体进行比较?
How do I compare an entity in my local context with the one in the database in EF Core?
EF 核心 6 和 .NET 6。
假设我所有的实体都有一个 LastUpdateAt
属性,这是一个 DateTime
,每次添加或修改实体时都会更新。
我从上下文中获取一个实体并将其显示给用户(网页、WPF window,等等)。在某个时候,用户单击“保存”按钮。
在我保存之前,我想检查在我得到我的副本后是否其他人更新了该实体。但是,我正在努力寻找如何做到这一点。
如果我查询上下文,它只会返回我已有的实体(包括我的用户所做的任何更改)。
如果我刷新实体,它会覆盖我上下文中的实体,从而丢失用户的更改。
如何检查数据库版本的时间戳是否比我上下文中的版本更新?
谢谢
由于我需要粘贴更长的文本,所以将讨论移至此处。在 this article 中说,在 SaveChanges() 期间,如果在此期间修改了数据库版本,它将抛出 DbUpdateConcurrencyException。在该异常中,您拥有所有 3 个值,您可以决定如何解决冲突:
解决并发冲突涉及将当前 DbContext 中的未决更改与数据库中的值合并。合并的值因应用程序而异,可能由用户输入决定。
可以使用三组值来帮助解决并发冲突:
当前值是应用程序试图写入数据库的值。
原始值是在进行任何编辑之前最初从数据库中检索到的值。
数据库值是当前存储在数据库中的值。
如果您正在加载一个实体,保持 DbContext 实例打开,更新该实体,然后保存到同一个 DbContext 实例,那么默认情况下您依赖 EF 来管理并发。这是继“最后一次获胜”之后。您可以通过在 LastUpdateAt 属性 上添加 [ConcurrencyCheck]
或通过 [Timestamp]
使用行版本来让 EF 管理并发性。如果基础数据已更新,这将导致 EF 无法更新。从那里你必须决定你想如何处理它。
如果您想自己执行并发检查,那么有几个选项。
使用分离实体或投影视图模型构建代码以缩短 DbContext 的生命周期。这通常会对您的代码性能带来 flow-on 好处,因为原始 longer-lived DbContext 可以很容易地找到导致膨胀的方法,或者如果存活时间过长则积累“中毒”实体。 Automapper 是一个很好的辅助工具,您可以使用 ProjectTo
获取视图模型,然后使用 Map(source, destination)
复制值。通过这种方式,您加载数据,包括最后修改的值,进行更改,然后在保存时加载数据,验证修改的值等。然后复制值并保存。
范围一个 DbContext
实例以在保存前检查数据。
.
private DateTime getFooLastUpdateAt(int fooId)
{
using(var context = new AppDbContext())
{
var lastUpdateAt = context.Foos
.Where(x => x.FooId == fooId)
.Select(x => x.LastUpdateAt)
.Single();
return lastUpdateAt;
}
}
这可以使用注入的 DbContext
工厂等来创建 DbContext
实例..
EF 核心 6 和 .NET 6。
假设我所有的实体都有一个 LastUpdateAt
属性,这是一个 DateTime
,每次添加或修改实体时都会更新。
我从上下文中获取一个实体并将其显示给用户(网页、WPF window,等等)。在某个时候,用户单击“保存”按钮。
在我保存之前,我想检查在我得到我的副本后是否其他人更新了该实体。但是,我正在努力寻找如何做到这一点。
如果我查询上下文,它只会返回我已有的实体(包括我的用户所做的任何更改)。
如果我刷新实体,它会覆盖我上下文中的实体,从而丢失用户的更改。
如何检查数据库版本的时间戳是否比我上下文中的版本更新?
谢谢
由于我需要粘贴更长的文本,所以将讨论移至此处。在 this article 中说,在 SaveChanges() 期间,如果在此期间修改了数据库版本,它将抛出 DbUpdateConcurrencyException。在该异常中,您拥有所有 3 个值,您可以决定如何解决冲突:
解决并发冲突涉及将当前 DbContext 中的未决更改与数据库中的值合并。合并的值因应用程序而异,可能由用户输入决定。
可以使用三组值来帮助解决并发冲突:
当前值是应用程序试图写入数据库的值。 原始值是在进行任何编辑之前最初从数据库中检索到的值。 数据库值是当前存储在数据库中的值。
如果您正在加载一个实体,保持 DbContext 实例打开,更新该实体,然后保存到同一个 DbContext 实例,那么默认情况下您依赖 EF 来管理并发。这是继“最后一次获胜”之后。您可以通过在 LastUpdateAt 属性 上添加 [ConcurrencyCheck]
或通过 [Timestamp]
使用行版本来让 EF 管理并发性。如果基础数据已更新,这将导致 EF 无法更新。从那里你必须决定你想如何处理它。
如果您想自己执行并发检查,那么有几个选项。
使用分离实体或投影视图模型构建代码以缩短 DbContext 的生命周期。这通常会对您的代码性能带来 flow-on 好处,因为原始 longer-lived DbContext 可以很容易地找到导致膨胀的方法,或者如果存活时间过长则积累“中毒”实体。 Automapper 是一个很好的辅助工具,您可以使用
ProjectTo
获取视图模型,然后使用Map(source, destination)
复制值。通过这种方式,您加载数据,包括最后修改的值,进行更改,然后在保存时加载数据,验证修改的值等。然后复制值并保存。范围一个
DbContext
实例以在保存前检查数据。
.
private DateTime getFooLastUpdateAt(int fooId)
{
using(var context = new AppDbContext())
{
var lastUpdateAt = context.Foos
.Where(x => x.FooId == fooId)
.Select(x => x.LastUpdateAt)
.Single();
return lastUpdateAt;
}
}
这可以使用注入的 DbContext
工厂等来创建 DbContext
实例..