如何使用WebAPI OData控制器的乐观并发

时间:2014-01-06 14:40:15

标签: asp.net asp.net-web-api odata entity-framework-6 asp.net-web-api-odata

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

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

我正在使用fiddler使用Version字段的陈旧值发回部分更新。我从上下文中加载当前记录,然后在顶部修改我更改的字段,这会更改Version列中的值而不会抛出错误,然后当我保存对上下文的更改时,所有内容都会保存而不会出错。显然这是预期的,正在保存的实体没有脱离上下文,所以我如何实现与Delta的乐观并发。

我正在使用所有内容的最新版本(或者就在圣诞节前),所以实体框架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);
    }
}

由于

3 个答案:

答案 0 :(得分:3)

我也使用Entity Framework 6和Web API 2 OData控制器来解决这个问题。

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

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

我发现你可以修复&#34;通过强制将时间戳的原始值作为上下文中当前值的原始值来实现此行为。

例如,您可以通过覆盖上下文中的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();
    }
}

(假设并发列的名称为&#34; Timestamp&#34;此列的并发模式设置为&#34;已修复&#34;在EDMX中)

对此的进一步改进是为需要此修复的所有模型编写并应用自定义界面,并且只需替换&#34; Job&#34;使用上面代码中的接口。

Rowan在实体框架小组中的反馈(2015年8月4日):

  

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

     

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

答案 1 :(得分:1)

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

应该是这样的;

RowVersion: "AAAAAAAAB9E="

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

基本上,当您调用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();

答案 2 :(得分:0)

简单,就像您一直使用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