您如何将乐观并发与 WebAPI OData 控制器一起使用

Posted

技术标签:

【中文标题】您如何将乐观并发与 WebAPI OData 控制器一起使用【英文标题】:How do you use optimistic concurrency with WebAPI OData controller 【发布时间】:2014-01-23 23:55:21 【问题描述】:

我有一个 WebAPI OData 控制器,它使用 Delta 对我的实体进行部分更新。

在我的实体框架模型中,我有一个版本字段。这是 SQL Server 数据库中的行版本,映射到实体框架中的字节数组,其并发模式设置为固定(它首先使用数据库)。

我正在使用 fiddler 使用 Version 字段的陈旧值发回部分更新。我从上下文中加载当前记录,然后在顶部修补更改的字段,这会更改 Version 列中的值而不会引发错误,然后当我在上下文中保存更改时,所有内容都会正确保存。显然这是意料之中的,正在保存的实体尚未从上下文中分离出来,所以我怎样才能使用 Delta 实现乐观并发。

我正在使用所有东西的最新版本(或者就在圣诞节前),所以 Entity Framework 6.0.1 和 OData 5.6.0

public IHttpActionResult Put([FromODataUri]int key, [FromBody]Delta<Job> delta)

    using (var tran = new TransactionScope())
    
        Job j = this._context.Jobs.SingleOrDefault(x => x.JobId == key);

        delta.Patch(j);

        this._context.SaveChanges();

        tran.Complete();

        return Ok(j);
    

谢谢

【问题讨论】:

【参考方案1】:

我也刚刚使用 Entity Framework 6 和 Web API 2 OData 控制器遇到了这个问题。

EF DbContext 似乎使用在 PUT/PATCH 方法开始时加载实体时获得的时间戳的原始值进行后续更新时的并发检查。

在保存更改之前将时间戳的当前值更新为与数据库中不同的值不会导致并发错误。

我发现您可以通过将时间戳的原始值强制为上下文中的当前值来“修复”此行为。

例如,您可以通过在上下文中覆盖 SaveChanges 来做到这一点,例如:

public partial class DataContext

    public override int SaveChanges()
    
        foreach (DbEntityEntry<Job> entry in ChangeTracker.Entries<Job>().Where(u => u.State == EntityState.Modified))
            entry.Property("Timestamp").OriginalValue = entry.Property("Timestamp").CurrentValue;

        return base.SaveChanges();
    

(假设并发列被命名为“Timestamp”,并且该列的并发模式在 EDMX 中设置为“Fixed”)

对此的进一步改进是编写自定义界面并将其应用于需要此修复的所有模型,只需将“Job”替换为上面代码中的界面。

来自 Rowan 在实体框架团队中的反馈(2015 年 8 月 4 日):

这是设计使然。在某些情况下,更新 a 是完全有效的 并发令牌,在这种情况下,我们需要当前值来保存 应该设置的值和包含该值的原始值 我们应该检查一下。例如,您可以配置 Person.LastName 作为并发标记。这是缺点之一 此操作中使用的“查询和更新”模式。

逻辑 您添加以设置正确的原始值是正确的方法 在这种情况下使用。

【讨论】:

登录实体框架团队:entityframework.codeplex.com/workitem/2789【参考方案2】:

当您将数据发布到服务器时,您还需要发送RowVersion 字段。如果您使用 fiddler 对其进行测试,请从您的数据库中获取最新的 RowVersion 值并将该值添加到您的 Request Body

应该是这样的;

RowVersion: "AAAAAAAAB9E="

如果是网页,当您从服务器加载数据时,再次从服务器获取 RowVersion 字段,将其保存在隐藏字段中,并将其与其他更改一起发送回服务器。

基本上,当您调用PATCH 方法时,RowField 需要在您的patch 对象中。

然后像这样更新你的代码;

Job j = this._context.Jobs.SingleOrDefault(x => x.JobId == key);

// Concurrency check
if (!j.RowVersion.SequenceEqual(patch.GetEntity().RowVersion))

    return Conflict();


this._context.Entry(entity).State = EntityState.Modified; // Probably you need this line as well?
this._context.SaveChanges();

【讨论】:

【参考方案3】:

简单,你总是用 Entity Framework 做的方式:你添加一个Timestamp 字段并将该字段的Concurrency Mode 放入Fixed。这可确保 EF 知道此时间戳字段不是任何查询的一部分,而是用于确定版本控制。

另见http://blogs.msdn.com/b/alexj/archive/2009/05/20/tip-19-how-to-use-optimistic-concurrency-in-the-entity-framework.aspx

【讨论】:

不幸的是,这不是解决办法。仅当记录在Post postFromCtx1 = ctx1.Post.First(p =&gt; p.ID == 1);ctx1.SaveChanges(); 之间的很短的时间内更改时才有效。这实际上被描述为msdn.microsoft.com/en-us/magazine/dd882522.aspx 的反模式。在我的场景中,“bob”加载记录的 v1,“jane”然后将记录更新为 v2,20 分钟后,“bob”尝试保存他的 v1,这将成功。 那么你必须自己实现并发检查。然后当 Bob 尝试保存他的 v1 时,他不会成功,因为 Jane 的更新将更改 Bob 持有的原始值的时间戳。 在问题中使用我的代码 Bob 的更新总是成功(除了在瞬间发生两次更新的极少数情况),因为他加载了 v2 然后将他的更改应用于它,因此上下文是总是更新一个新的实体。我想问题是,我如何在这种“断开连接”的情况下自己实现并发检查。

以上是关于您如何将乐观并发与 WebAPI OData 控制器一起使用的主要内容,如果未能解决你的问题,请参考以下文章

将 ODATA $expand 查询选项与 WebAPI 和 ViewModel 一起使用

php并发控制 , 乐观锁

ElasticSearch 并发冲突+悲观锁与乐观锁+基于_version和external version进行乐观锁并发控制

在 WebAPI 中使用 OData 时如何将 SelectExpandQueryOption 转换为 IQueryable<T>?

WebApi 2 中 OData 控制器 http 请求的完整生命周期是啥

如何正确地将 OData 与 ASP.net Core 集成