https://www.learnentityframeworkcore.com/concurrency併發
不管什麼時候, 當一個更新操做或者刪除操做發生時, 即咱們的代碼調用 SaveChange() 時, target value 上面拿到當前的 concurrency token, 與調用 Save Change 以前記錄的 concurrency token 相比較.
- 若是二者 match, 那麼這次 operation 將會執行.
- 若是二者不 match, 那麼 EF Core 會認爲有其餘的 operation 在對 target value 進行操做. 則會放棄這次 operation.ide
其原理實際上是 EF Core 會在每一個 update / delete 語句的 where 條件中增添一個 statement: where xxx = yyy and concurrency token = old token, 隨後 ef core 會記錄這次 operation affect 的行數, 若是行數爲 0 那麼會拋出一個 DbUpdateConcurrencyException. but the exception will be informed to the user. what is the next step?ui
兩個方案去檢測是否併發衝突:code
public class Author { public int AuthorId { get; set; } public string FirstName { get; set; } [ConcurrencyCheck] public string LastName { get; set; } public ICollection<Book> Books { get; set; } }
或者orm
public class SampleContext : DbContext { public DbSet<Author> Authors { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Author>() .Property(a => a.LastName).IsConcurrencyToken(); } } public class Author { public int AuthorId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public ICollection<Book> Books { get; set; } }
具體的作法就是設置一列爲version, 自增列, 當user a, 和 user b 同時獲取到這個數據時, version爲0, 此時 user a 對數據進行了修改, 存回到db中, 此時version爲1, user b對數據也進行了修改, 再存入db時發現已經找不到version爲0的這行數據了, 此時user b會獲得一個 DbUpdateConcurrencyException.token
public class Author { public int AuthorId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public ICollection<Book> Books { get; set; } [TimeStamp] public byte[] RowVersion { get; set; } }
public class SampleContext : DbContext { public DbSet<Author> Authors { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Author>() .Property(a => a.RowVersion) .IsConcurrencyToken() .ValueGeneratedOnAddOrUpdate(); } } public class Author { public int AuthorId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public ICollection<Book> Books { get; set; } public byte[] RowVersion { get; set; } }
the example code like below:ci
using (var context = new PersonContext()) { // Fetch a person from database and change phone number var person = context.People.Single(p => p.PersonId == 1); person.PhoneNumber = "555-555-5555"; // Change the person's name in the database to simulate a concurrency conflict context.Database.ExecuteSqlRaw( "UPDATE dbo.People SET FirstName = 'Jane' WHERE PersonId = 1"); var saved = false; while (!saved) { try { // Attempt to save changes to the database context.SaveChanges(); saved = true; } catch (DbUpdateConcurrencyException ex) { foreach (var entry in ex.Entries) { if (entry.Entity is Person) { var proposedValues = entry.CurrentValues; var databaseValues = entry.GetDatabaseValues(); foreach (var property in proposedValues.Properties) { var proposedValue = proposedValues[property]; var databaseValue = databaseValues[property]; // TODO: decide which value should be written to database // proposedValues[property] = <value to be saved>; } // Refresh original values to bypass next concurrency check entry.OriginalValues.SetValues(databaseValues); } else { throw new NotSupportedException( "Don't know how to handle concurrency conflicts for " + entry.Metadata.Name); } } } } }