簡體   English   中英

在某些情況下,OptimisticConcurrencyException在實體框架中不起作用

[英]OptimisticConcurrencyException Does Not Work in Entity Framework In Certain Situations

更新(2010-12-21):根據我一直在做的測試完全重寫了這個問題。 此外,這曾經是一個POCO特定的問題,但事實證明我的問題不一定是POCO特定的。

我正在使用實體框架,我的數據庫表中有一個時間戳列,應該用於跟蹤樂觀並發的更改。 我已將實體設計器中此屬性的並發模式設置為“已修復”,並且我得到的結果不一致。 以下是幾個簡化的場景,它們演示了並發檢查在一個場景中工作但在另一個場景中不起作用。

成功拋出OptimisticConcurrencyException:

如果我附加一個斷開連接的實體,那么如果存在時間戳沖突,SaveChanges將拋出一個OptimisticConcurrencyException:

    [HttpPost]
    public ActionResult Index(Person person) {
        _context.People.Attach(person);
        var state = _context.ObjectStateManager.GetObjectStateEntry(person);
        state.ChangeState(System.Data.EntityState.Modified);
        _context.SaveChanges();
        return RedirectToAction("Index");
    }

不拋出OptimisticConcurrencyException:

另一方面,如果我從數據庫中檢索我的實體的新副本並對某些字段進行部分更新,然后調用SaveChanges(),那么即使存在時間戳沖突,我也不會得到OptimisticConcurrencyException :

    [HttpPost]
    public ActionResult Index(Person person) {
        var currentPerson = _context.People.Where(x => x.Id == person.Id).First();
        currentPerson.Name = person.Name;

        // currentPerson.VerColm == [0,0,0,0,0,0,15,167]
        // person.VerColm == [0,0,0,0,0,0,15,166]
        currentPerson.VerColm = person.VerColm;

        // in POCO, currentPerson.VerColm == [0,0,0,0,0,0,15,166]
        // in non-POCO, currentPerson.VerColm doesn't change and is still [0,0,0,0,0,0,15,167]
        _context.SaveChanges();
        return RedirectToAction("Index");
    }

基於SQL Profiler,看起來Entity Framework忽略了新的VerColm(這是時間戳屬性),而是使用最初加載的VerColm。 因此,它永遠不會拋出OptimisticConcurrencyException。


更新:根據Jan的要求添加其他信息:

請注意,我還在上面的代碼中添加了注釋,以便與我在執行此示例時在控制器操作中看到的內容一致。

這是更新前我的DataBase中VerColm的值:0x0000000000000FA7

以下是SQL Profiler在執行更新時顯示的內容:

exec sp_executesql N'update [dbo].[People]
set [Name] = @0
where (([Id] = @1) and ([VerColm] = @2))
select [VerColm]
from [dbo].[People]
where @@ROWCOUNT > 0 and [Id] = @1',N'@0 nvarchar(50),@1 int,@2 binary(8)',@0=N'hello',@1=1,@2=0x0000000000000FA7

請注意,@ 2應該是0x0000000000000FA6,但它是0x0000000000000FA7

這是更新后我的DataBase中的VerColm:0x0000000000000FA8


有誰知道如何解決這個問題? 當我更新現有實體並且存在時間戳沖突時,我希望實體框架拋出異常。

謝謝

說明

您沒有在第二個代碼示例上獲得預期的OptimisticConcurrencyException的原因是由於EF檢查並發的方式:

通過查詢數據庫檢索實體時,EF會在查詢時將ConcurrencyMode.Fixed標記的屬性記錄為all的值,作為原始未修改的值。

然后更改一些屬性(包括Fixed標記的屬性)並在DataContext上調用SaveChanges()

EF通過將所有已Fixed標記的db列的當前值與Fixed標記屬性的原始未修改值進行比較來檢查並發更新。 這里的關鍵點是EF將時間戳屬性的更新視為普通數據屬性更新。 你看到的行為是設計的。

解決方案/解決方法

要解決此問題,您有以下選擇:

  1. 使用您的第一種方法:不要為您的實體重新查詢數據庫,而是將重新創建的實體附加到您的上下文。

  2. 將您的timestamp值偽造為當前db值,以便EF並發檢查使用您提供的值,如下所示(另請參閱類似問題的答案 ):

     var currentPerson = _context.People.Where(x => x.Id == person.Id).First(); currentPerson.VerColm = person.VerColm; // set timestamp value var ose = _context.ObjectStateManager.GetObjectStateEntry(currentPerson); ose.AcceptChanges(); // pretend object is unchanged currentPerson.Name = person.Name; // assign other data properties _context.SaveChanges(); 
  3. 您可以通過將時間戳值與已獲取時間戳值進行比較來自行檢查並發性:

     var currentPerson = _context.People.Where(x => x.Id == person.Id).First(); if (currentPerson.VerColm != person.VerColm) { throw new OptimisticConcurrencyException(); } currentPerson.Name = person.Name; // assign other data properties _context.SaveChanges(); 

這是另一種更通用的方法,適合數據層:

// if any timestamps have changed, throw concurrency exception
var changed = this.ChangeTracker.Entries<>()
    .Any(x => !x.CurrentValues.GetValue<byte[]>("Timestamp").SequenceEqual(
        x.OriginalValues.GetValue<byte[]>("Timestamp")));
if (changed) throw new OptimisticConcurrencyException();
this.SaveChanges();

它只是檢查TimeStamp是否已更改並引發並發異常。

如果它首先是EF代碼,那么使用類似於下面代碼的代碼。 這將把從db加載的原始TimeStamp更改為UI中的一個,並確保發生OptimisticConcurrencyEception

db.Entry(request).OriginalValues["Timestamp"] = TimeStamp;

我修改了@JarrettV解決方案以使用Entity Framework Core。 現在它正在遍歷上下文中的所有已修改條目,並查找標記為並發令牌的屬性中的任何不匹配。 適用於TimeStamp(RowVersion):

private void ThrowIfInvalidConcurrencyToken()
{
    foreach (var entry in _context.ChangeTracker.Entries())
    {
        if (entry.State == EntityState.Unchanged) continue;

        foreach (var entryProperty in entry.Properties)
        {
            if (!entryProperty.IsModified || !entryProperty.Metadata.IsConcurrencyToken) continue;

            if (entryProperty.OriginalValue != entryProperty.CurrentValue)
            {                    
                throw new DbUpdateConcurrencyException(
                    $"Entity {entry.Metadata.Name} has been modified by another process",
                    new List<IUpdateEntry>()
                    {
                        entry.GetInfrastructure()
                    });
            }
        }
    }
}

我們只需要在保存EF上下文中的更改之前調用此方法:

public async Task SaveChangesAsync(CancellationToken cancellationToken)
{
    ThrowIfInvalidConcurrencyToken();
    await _context.SaveChangesAsync(cancellationToken);
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM