การควบคุมการทํางานพร้อมกันในแง่ดีป้องกันการสูญหายของข้อมูลเนื่องจากการอัปเดตที่ชนะล่าสุดด้วยการเข้าถึงหลายครั้ง (SQL Server)
สภาพแวดล้อมในการทํางาน
- วิชวลสตูดิโอ
-
- วิชวลสตูดิโอ 2022
- ตาข่าย
-
- .NET 8
- แกน Entity Framework
-
- Entity Framework Core 8.0
- เซิร์ฟเวอร์ SQL
-
- SQL Server 2022
* ข้างต้นเป็นสภาพแวดล้อมการตรวจสอบ แต่อาจใช้ได้กับเวอร์ชันอื่น
เกี่ยวกับการชนะการอัปเดตหลังจากไม่มีการควบคุม
ในเว็บแอปพลิเคชันหรือแอปพลิเคชันไคลเอ็นต์เซิร์ฟเวอร์ หลายคนอาจเข้าถึงและอัปเดตข้อมูลจากฐานข้อมูลเดียว หากไม่มีการดําเนินการใดๆ เป็นพิเศษ ข้อมูลของผู้ที่อัปเดตในภายหลังจะปรากฏในฐานข้อมูลเป็นข้อมูลล่าสุด
โดยปกติจะไม่มีปัญหาเฉพาะที่ข้อมูลของบุคคลที่อัปเดตในภายหลังจะสะท้อนเป็นข้อมูลล่าสุด ปัญหาอาจเกิดขึ้นได้เมื่อหลายคนพยายามเข้าถึงและอัปเดตข้อมูลเดียวกันในเวลาเดียวกัน
ตัวอย่างเช่น สมมติว่าคุณมีข้อมูลหนังสือต่อไปนี้
ค่าชื่อ | พารามิเตอร์ |
---|---|
ชื่อหนังสือ | หนังสือฐานข้อมูล |
ราคา | 1000 |
หากคนสองคนเปิดหน้าจอเพื่อแก้ไขข้อมูลนี้พร้อมกันค่าข้างต้นจะปรากฏขึ้น คุณ/นางสาว A พยายามขึ้นราคาหนังสือเล่มนี้ 500 เยน ต่อมานาย/นางสาว B ได้รับคําสั่งให้ขึ้นราคาหนังสือเล่มนี้อีก 300 เยน
หากทั้งสองคนขึ้นราคาแยกกันแทนที่จะขึ้นพร้อมกันราคาของหนังสือจะอยู่ที่ 1800 เยน หากคุณเข้าถึงพร้อมกัน จะลงทะเบียนเป็น 1500 เยนและ 1300 เยนตามลําดับ ดังนั้นจึงไม่ใช่ 1800 เยนไม่ว่าใครจะลงทะเบียน
ปัญหาที่นี่คือผู้ที่อัปเดตในภายหลังสามารถอัปเดตได้โดยไม่ต้องทราบข้อมูลที่อัปเดตก่อนหน้านี้
การทํางานพร้อมกันในแง่ดี
ปัญหาดังกล่าวสามารถแก้ไขได้โดยการดําเนินการ "การควบคุมการทํางานพร้อมกันในแง่ดี" ในครั้งนี้ เพื่ออธิบายง่ายๆ การควบคุมแบบไหนคือ "ชนะก่อนหากคุณพยายามแก้ไขข้อมูลในเวลาเดียวกัน" ผู้ที่พยายามต่ออายุในภายหลังจะได้รับข้อผิดพลาดในช่วงเวลาของการอัปเดตและจะไม่สามารถลงทะเบียนได้
คุณอาจคิดว่าคุณไม่สามารถลงทะเบียนข้อมูลใหม่กับสิ่งนี้ได้ แต่นี่เป็นเพียง "เมื่อคุณพยายามเปลี่ยนแปลงในเวลาเดียวกัน" เท่านั้น เป็นไปได้ที่คนสองคนจะแก้ไขในเวลาที่ต่างกันโดยสิ้นเชิง แน่นอนว่าในกรณีนั้น ข้อมูลของบุคคลที่อัปเดตล่าสุดจะเป็นข้อมูลล่าสุด
โดยเฉพาะอย่างยิ่งการควบคุมการประมวลผลประเภทใดที่สามารถทําได้โดย "การมีเวอร์ชันของข้อมูล" ตัวอย่างเช่น ในตัวอย่างข้างต้น คุณจะมีข้อมูลต่อไปนี้
ค่าชื่อ | พารามิเตอร์ |
---|---|
ชื่อหนังสือ | หนังสือฐานข้อมูล |
ราคา | 1000 |
เวอร์ชัน | 1 |
รุ่นจะเพิ่มขึ้น 1 สําหรับการอัปเดตเรกคอร์ดแต่ละครั้ง ตัวอย่างเช่น หากนาย/นางสาว A ตั้งราคาเป็น 1500 เยน เวอร์ชันจะเป็น 2 ในขณะนั้น เงื่อนไขที่การปรับปรุงสามารถทําได้คือรุ่นก่อนการปรับปรุงจะเหมือนกับรุ่นบนฐานข้อมูล เมื่อ Mr./Ms. อัปเดต เวอร์ชันในฐานข้อมูลคือ 1 และเวอร์ชันของข้อมูลต้นฉบับที่กําลังแก้ไขอยู่ในปัจจุบันคือ 1 ดังนั้นจึงสามารถอัปเดตได้
ตามข้อกําหนดนี้ สมมติว่าสถานการณ์ที่ Mr./Ms. A และ Mr./Ms. B แก้ไขข้อมูลเดียวกัน เมื่อ Mr./Ms. ตั้งราคาเป็น 1500 เยนเป็นครั้งแรกและพยายามอัปเดตฐานข้อมูลเวอร์ชันก็เหมือนกันดังนั้นจึงสามารถอัปเดตได้ ในกรณีนั้น เวอร์ชันในฐานข้อมูลจะเป็น 2 คุณ/นางสาว B พยายามแก้ไขข้อมูล 1,000 เยนและอัปเดตเป็นฐานข้อมูลเป็น 1,300 เยน การปรับปรุงล้มเหลวเนื่องจากรุ่นที่อยู่ในมือคือ 1 แต่รุ่นบนฐานข้อมูลเป็น 2 แล้ว นี่คือวิธีการทํางานของการทํางานพร้อมกันในแง่ดี
Entity Framework Core มี "ความพร้อมกันในแง่ดี" นี้ตั้งแต่แกะกล่อง ทําให้นําไปใช้ค่อนข้างง่าย
อย่างไรก็ตาม "การล็อกในแง่ดี" เรียกอีกอย่างว่า "ล็อคในแง่ดี" หรือ "ล็อคในแง่ดี" และบางครั้งก็มีการตรวจสอบและพูดถึงชื่อนี้ เป็นวิธีการล็อคที่ไม่สามารถอัปเดตข้อมูลได้ แต่สามารถอ่านข้อมูลได้ นอกจากนี้ยังมีการควบคุมที่เรียกว่า "การล็อคในแง่ร้าย" ซึ่งเป็นวิธีการล็อคอีกวิธีหนึ่งนอกเหนือจาก "การล็อคในแง่ดี" นี่เป็นวิธีการที่ล็อคการโหลดข้อมูลเมื่อบุคคลแรกอ่านข้อมูลและไม่อนุญาตให้ดําเนินการแก้ไขด้วยซ้ํา สามารถแก้ปัญหาที่ไม่สามารถอัปเดตข้อมูลได้แม้ว่าจะมีการเปลี่ยนแปลง แต่เมื่อมีคนกําลังแก้ไขข้อมูล บุคคลอื่นจะไม่สามารถเปิดหน้าจอแก้ไขข้อมูลได้ และหากการปลดล็อกล้มเหลว ข้อมูลจะถูกล็อคตลอดไป ทั้งสองมีข้อดีและข้อเสียดังนั้นจึงขึ้นอยู่กับการดําเนินการที่จะนํามาใช้
การสร้างฐานข้อมูล
ในบทความนี้ฉันจะอธิบายวิธีสร้างฐานข้อมูลสําหรับ SQL Server ก่อนแล้วจึงสร้างโค้ดโดยอัตโนมัติ หากคุณต้องการนําไปใช้ในลักษณะที่เน้นรหัสเป็นอันดับแรก โปรดดูโค้ดที่สร้างขึ้นโดยอัตโนมัติในครั้งนี้และนําไปใช้ในขั้นตอนย้อนกลับ
การสร้างฐานข้อมูล
คุณยังสามารถสร้างใน SQL ได้ แต่มันง่ายกว่าที่จะสร้างด้วย GUI ดังนั้นฉันจึงสร้างด้วย GUI ในครั้งนี้ ยกเว้นชื่อฐานข้อมูล จะถูกสร้างขึ้นตามค่าเริ่มต้น
สร้างตาราง
สร้างด้วย SQL ต่อไปนี้:
USE [TestDatabase]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Book](
[ID] [int] NOT NULL,
[Name] [nvarchar](100) NOT NULL,
[Price] [money] NOT NULL,
[RowVersion] [timestamp] NOT NULL,
CONSTRAINT [PK_Book] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
คุณไม่ต้องกังวลเกี่ยวกับพารามิเตอร์ส่วนใหญ่เพราะเป็นเพียงการอัปเดตข้อมูลเท่านั้น
พารามิเตอร์ที่น่าสนใจในครั้งนี้คือ RowVersion
คอลัมน์ที่อธิบาย นี่คือการกําหนดเวอร์ชันของระเบียน
โดยการระบุเป็น timestamp
ชนิด เวอร์ชันจะเพิ่มขึ้นโดยอัตโนมัติทุกครั้งที่มีการอัปเดตเรกคอร์ด
นอกจากนี้ เนื่องจากเวอร์ชันนี้ได้รับการจัดการตามตาราง จึงไม่มีบันทึกเวอร์ชันเดียวกัน เว้นแต่คุณจะตั้งค่าด้วยตนเอง
เพิ่มเรกคอร์ด
คุณสามารถเพิ่มด้วย SQL ต่อไปนี้:
begin transaction;
insert into Book ([ID],[Name],[Price]) values (1,'C#の本','1000.00');
insert into Book ([ID],[Name],[Price]) values (2,'VB.NETの本','1500.00');
insert into Book ([ID],[Name],[Price]) values (3,'SQL Serverの本','2000.00');
commit;
RowVersion
คุณไม่จําเป็นต้องตั้งค่าคอลัมน์เนื่องจากคอลัมน์จะถูกตั้งค่าโดยอัตโนมัติ
สร้างโปรเจ็กต์และสร้างโค้ดโดยอัตโนมัติ
คราวนี้เราจะตรวจสอบการทํางานด้วยแอปพลิเคชันคอนโซล ขั้นตอนในการสร้างโครงการและสร้างโค้ดโดยอัตโนมัติอธิบายไว้ในเคล็ดลับต่อไปนี้ดังนั้นฉันจะไม่พูดถึงที่นี่
รหัสที่สร้างขึ้นมีดังนี้:
TestDatabaseDbContext.cs
using Microsoft.EntityFrameworkCore;
namespace ConcurrencySqlServer.Models.Database;
public partial class TestDatabaseDbContext : DbContext
{
public TestDatabaseDbContext() { }
public TestDatabaseDbContext(DbContextOptions<TestDatabaseDbContext> options) : base(options) { }
public virtual DbSet<Book> Book { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see https://go.microsoft.com/fwlink/?LinkId=723263.
=> optionsBuilder.UseSqlServer("Data Source=<サーバー名>;Database=<データベース名>;user id=<ユーザー名>;password=<パスワード>;TrustServerCertificate=true");
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Book>(entity =>
{
entity.Property(e => e.ID).ValueGeneratedNever();
entity.Property(e => e.RowVersion)
.IsRowVersion()
.IsConcurrencyToken();
});
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}
Book.cs
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ConcurrencySqlServer.Models.Database;
public partial class Book
{
[Key]
public int ID { get; set; }
[StringLength(100)]
public string Name { get; set; } = null!;
[Column(TypeName = "money")]
public decimal Price { get; set; }
public byte[] RowVersion { get; set; } = null!;
}
การตรวจสอบการทํางานของการควบคุมการทํางานพร้อมกันในแง่ดี
เนื่องจากเรากําลังเรียกใช้ในแอปพลิเคชันเดียวในครั้งนี้เราจึงไม่ได้เข้าถึงอย่างเคร่งครัดในเวลาเดียวกัน แต่เราต้องการนําไปใช้ในรูปแบบที่ใกล้เคียงกัน
เช่นเดียวกับในตัวอย่างในตอนต้นจะมีการได้ข้อมูลสองชิ้นและเมื่อแต่ละรายการได้รับการอัปเดตตามข้อมูลแรกให้ตรวจสอบว่าตัวอัปเดตหลังจะได้รับข้อผิดพลาดหรือไม่
Program.cs
using ConcurrencySqlServer.Models.Database;
using Microsoft.EntityFrameworkCore;
using System.Text.Json;
Console.WriteLine("Hello, World!");
// 2人が別々にデータベースにアクセスする想定で2つのデータベースコンテキストを作成
Console.WriteLine("データベースコンテキストを作成します。");
using var dbContextA = new TestDatabaseDbContext();
using var dbContextB = new TestDatabaseDbContext();
Console.WriteLine("データベースコンテキストを作成しました。");
Console.WriteLine("");
// それぞれがデータを編集しようと読み込む
Console.WriteLine("ID:1 の Book を読み込みます。");
var bookA = dbContextA.Book.First(x => x.ID == 1);
var bookB = dbContextB.Book.First(x => x.ID == 1);
Console.WriteLine("ID:1 の Book を読み込みました。");
Console.WriteLine($"A Book : {JsonSerializer.Serialize(bookA)}");
Console.WriteLine($"B Book : {JsonSerializer.Serialize(bookB)}");
Console.WriteLine($"DB Book : {JsonSerializer.Serialize(dbContextC.Book.AsNoTracking().First(x => x.ID == 1))}");
Console.WriteLine("");
// A の人が最初にデータベースに更新する
bookA.Price += 500;
UpdateToDatabase(dbContextA, "A");
Console.WriteLine($"A Book : {JsonSerializer.Serialize(bookA)}");
Console.WriteLine($"B Book : {JsonSerializer.Serialize(bookB)}");
Console.WriteLine($"DB Book : {JsonSerializer.Serialize(dbContextC.Book.AsNoTracking().First(x => x.ID == 1))}");
Console.WriteLine("");
// そのあと B の人が最初にデータベースに更新する
bookB.Price += 300;
UpdateToDatabase(dbContextB, "B");
Console.WriteLine($"A Book : {JsonSerializer.Serialize(bookA)}");
Console.WriteLine($"B Book : {JsonSerializer.Serialize(bookB)}");
Console.WriteLine($"DB Book : {JsonSerializer.Serialize(dbContextC.Book.AsNoTracking().First(x => x.ID == 1))}");
Console.WriteLine("");
Console.WriteLine("処理を終了します。");
// データベースに更新するメソッド
void UpdateToDatabase(TestDatabaseDbContext dbContext, string updateUser)
{
try
{
Console.WriteLine($"{updateUser} のデータベースコンテキストを変更保存します。");
dbContext.SaveChanges();
Console.WriteLine($"{updateUser} のデータベースコンテキストを変更保存しました。");
}
catch (DbUpdateConcurrencyException ex)
{
Console.WriteLine($"{updateUser} の更新でエラーが発生しました。");
Console.WriteLine(ex.Message);
}
}
เป็นเรื่องยาว แต่ส่วนใหญ่เขียนลงในคอนโซล
ผลของการดําเนินการมีดังนี้
Hello, World!
データベースコンテキストを作成します。
データベースコンテキストを作成しました。
ID:1 の Book を読み込みます。
ID:1 の Book を読み込みました。
A Book : {"ID":1,"Name":"C#\u306E\u672C","Price":1000.0000,"RowVersion":"AAAAAAAAH2k="}
B Book : {"ID":1,"Name":"C#\u306E\u672C","Price":1000.0000,"RowVersion":"AAAAAAAAH2k="}
DB Book : {"ID":1,"Name":"C#\u306E\u672C","Price":1000.0000,"RowVersion":"AAAAAAAAH2k="}
A のデータベースコンテキストを変更保存します。
A のデータベースコンテキストを変更保存しました。
A Book : {"ID":1,"Name":"C#\u306E\u672C","Price":1500.0000,"RowVersion":"AAAAAAAAH2o="}
B Book : {"ID":1,"Name":"C#\u306E\u672C","Price":1000.0000,"RowVersion":"AAAAAAAAH2k="}
DB Book : {"ID":1,"Name":"C#\u306E\u672C","Price":1500.0000,"RowVersion":"AAAAAAAAH2o="}
B のデータベースコンテキストを変更保存します。
B の更新でエラーが発生しました。
The database operation was expected to affect 1 row(s), but actually affected 0 row(s); data may have been modified or deleted since entities were loaded. See https://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.
A Book : {"ID":1,"Name":"C#\u306E\u672C","Price":1500.0000,"RowVersion":"AAAAAAAAH2o="}
B Book : {"ID":1,"Name":"C#\u306E\u672C","Price":1300.0000,"RowVersion":"AAAAAAAAH2k="}
DB Book : {"ID":1,"Name":"C#\u306E\u672C","Price":1500.0000,"RowVersion":"AAAAAAAAH2o="}
処理を終了します。
ฉันจะแบ่งออกเป็นส่วนๆ
Console.WriteLine("Hello, World!");
// 2人が別々にデータベースにアクセスする想定で2つのデータベースコンテキストを作成
Console.WriteLine("データベースコンテキストを作成します。");
using var dbContextA = new TestDatabaseDbContext();
using var dbContextB = new TestDatabaseDbContext();
using var dbContextC = new TestDatabaseDbContext();
Console.WriteLine("データベースコンテキストを作成しました。");
ฉันกําลังสร้างบริบทฐานข้อมูลสองบริบท
หากคุณแชร์บริบทฐานข้อมูลหนึ่งบริบทนั้นจะถูกแคชเมื่ออ่านข้อมูลและจะเป็นอินสแตนซ์เดียวกัน
สมมติว่าแต่ละรายการมีการเข้าถึงแยกกัน จะมีการสร้างบริบทฐานข้อมูลสองแบบ
dbContextC
ใช้สําหรับตรวจสอบค่าในฐานข้อมูล ฉันไม่ต้องการมันจริงๆ เพราะ A หรือ B สามารถแทนที่ได้
// それぞれがデータを編集しようと読み込む
Console.WriteLine("ID:1 の Book を読み込みます。");
var bookA = dbContextA.Book.First(x => x.ID == 1);
var bookB = dbContextB.Book.First(x => x.ID == 1);
Console.WriteLine("ID:1 の Book を読み込みました。");
Console.WriteLine($"A Book : {JsonSerializer.Serialize(bookA)}");
Console.WriteLine($"B Book : {JsonSerializer.Serialize(bookB)}");
Console.WriteLine($"DB Book : {JsonSerializer.Serialize(dbContextC.Book.AsNoTracking().First(x => x.ID == 1))}");
สมมติว่าพวกเขาถูกเข้าถึงแยกกัน พวกเขากําลังอ่านหนึ่งจากบริบทฐานข้อมูล Book
ที่แตกต่างกัน
ณ จุดนี้ เราไม่ได้เปลี่ยนแปลงอะไรเลย ดังนั้นพวกเขาจึงเหมือนกันทั้งหมด
ID:1 の Book を読み込みます。
ID:1 の Book を読み込みました。
A Book : {"ID":1,"Name":"C#\u306E\u672C","Price":1000.0000,"RowVersion":"AAAAAAAAH2k="}
B Book : {"ID":1,"Name":"C#\u306E\u672C","Price":1000.0000,"RowVersion":"AAAAAAAAH2k="}
DB Book : {"ID":1,"Name":"C#\u306E\u672C","Price":1000.0000,"RowVersion":"AAAAAAAAH2k="}
ขั้นแรกฉันเปลี่ยนและอัปเดตหนังสือของนาย/นางสาว
กระบวนการปรับปรุงจะถูก UpdateToDatabase
สรุปในวิธีการ และข้อความจะปรากฏขึ้นเมื่อมีข้อยกเว้นเกิดขึ้น
// A の人が最初にデータベースに更新する
bookA.Price += 500;
UpdateToDatabase(dbContextA, "A");
Console.WriteLine($"A Book : {JsonSerializer.Serialize(bookA)}");
Console.WriteLine($"B Book : {JsonSerializer.Serialize(bookB)}");
Console.WriteLine($"DB Book : {JsonSerializer.Serialize(dbContextC.Book.AsNoTracking().First(x => x.ID == 1))}");
A のデータベースコンテキストを変更保存します。
A のデータベースコンテキストを変更保存しました。
A Book : {"ID":1,"Name":"C#\u306E\u672C","Price":1500.0000,"RowVersion":"AAAAAAAAH2o="}
B Book : {"ID":1,"Name":"C#\u306E\u672C","Price":1000.0000,"RowVersion":"AAAAAAAAH2k="}
DB Book : {"ID":1,"Name":"C#\u306E\u672C","Price":1500.0000,"RowVersion":"AAAAAAAAH2o="}
ส่งผลให้ได้รับการอัปเดตสําเร็จ และPrice
RowVersion
และของหนังสือ A ได้รับการอัปเดต
นอกจากนี้ หากคุณอ่านค่า DB โดยตรง ค่าที่อัปเดตจะเหมือนกับค่าที่อัปเดต
ฉันสามารถอัปเดตได้เพราะทั้ง A RowVersion
และRowVersion
ใน DB เป็น AAAAAAAAH2k=
.
เวอร์ชันมีการเปลี่ยนแปลงAAAAAAAAH2o=
เนื่องจากการอัปเดต
หลังจากอัปเดต A แล้ว ให้อัปเดต B ในลักษณะเดียวกัน
// そのあと B の人が最初にデータベースに更新する
bookB.Price += 300;
UpdateToDatabase(dbContextB, "B");
Console.WriteLine($"A Book : {JsonSerializer.Serialize(bookA)}");
Console.WriteLine($"B Book : {JsonSerializer.Serialize(bookB)}");
Console.WriteLine($"DB Book : {JsonSerializer.Serialize(dbContextC.Book.AsNoTracking().First(x => x.ID == 1))}");
B のデータベースコンテキストを変更保存します。
B の更新でエラーが発生しました。
The database operation was expected to affect 1 row(s), but actually affected 0 row(s); data may have been modified or deleted since entities were loaded. See https://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.
A Book : {"ID":1,"Name":"C#\u306E\u672C","Price":1500.0000,"RowVersion":"AAAAAAAAH2o="}
B Book : {"ID":1,"Name":"C#\u306E\u672C","Price":1300.0000,"RowVersion":"AAAAAAAAH2k="}
DB Book : {"ID":1,"Name":"C#\u306E\u672C","Price":1500.0000,"RowVersion":"AAAAAAAAH2o="}
ผลลัพธ์คือข้อยกเว้น DbUpdateConcurrencyException
และการปรับปรุงล้มเหลว
bookB.RowVersion
AAAAAAAAH2k=
คือ แต่RowVersion
เนื่องจากเป็นแล้ว AAAAAAAAH2o=
จึงถูกตัดสินว่าไม่ตรงกันและเกิดข้อผิดพลาดในการอัปเดต
คุณจะเห็นว่าPrice
ตัวแปรท้องถิ่นbookB
มีการเปลี่ยนแปลง แต่RowVersion
คุณจะเห็นว่าค่าในฝั่งฐานข้อมูลไม่ได้เปลี่ยนแปลงเลย
สรุป
นี่เป็นวิธีที่ง่ายที่สุดในบรรดาชนิดล็อคหลายชนิดที่จะนําไปใช้ เนื่องจากเมื่อคุณใช้รหัสที่สร้างขึ้นโดยอัตโนมัติใน SQL Server และ Entity Framework Core การทํางานพร้อมกันในแง่ดีจะถูกนําไปใช้ตามค่าเริ่มต้น อย่างไรก็ตาม เนื่องจากเป็นเพียงการป้องกัน "ข้อมูลเสียหายที่จะอัปเดต" จึงจําเป็นต้องจัดการข้อยกเว้นอย่างเหมาะสมเมื่อมีข้อมูลอื่นเกี่ยวข้องหรือการดําเนินการของผู้ใช้ที่เกี่ยวข้อง
นอกจากนี้คราวนี้ฉันไม่ได้จัดการอะไรเลยเพราะฉันนําไปใช้ RowVersion
ในแอปพลิเคชันคอนโซลธรรมดา
หากคุณต้องการแทรกหน้าจอแก้ไขหลังจากโหลดข้อมูลในเว็บแอปพลิเคชันหรือแอปพลิเคชันไคลเอ็นต์
RowVersion
ในทางใดทางหนึ่งเพื่อให้สามารถกําหนดได้อย่างถูกต้องเมื่อมีการอัปเดต
อย่างไรก็ตาม Entity Framework Core มีฟังก์ชันการติดตามการเปลี่ยนแปลง ดังนั้นหากคุณต้องการตั้งค่าเก่า RowVersion
เป็นค่าที่อ่านจาก DB ในขณะที่อัปเดต
bookB.RowVersion = <古い RowVersion>;
แม้ว่าจะตั้งค่าดังนี้ แต่ก็จะไม่ถูกตัดสินอย่างถูกต้องว่าเป็น "การควบคุมการทํางานพร้อมกันในแง่ดี"
RowVersion
แม้ว่าคุณจะตั้งค่าเป็น ปกติ แต่จะถูกจดจําว่าเป็นค่าที่เปลี่ยนแปลงเท่านั้น ดังนั้นต่อไปนี้คือ
dbContextB.Entry(bookB).Property("RowVersion").OriginalValue = <古い RowVersion>;
จําเป็นต้องเขียนค่าใหม่ก่อนการเปลี่ยนแปลง