Ο αισιόδοξος έλεγχος ταυτοχρονισμού αποτρέπει την απώλεια δεδομένων λόγω ενημερώσεων τελευταίας νίκης με πολλαπλές προσβάσεις (SQL Server)
Περιβάλλον λειτουργίας
- Οπτικό στούντιο
-
- Visual Studio 2022
- .ΔΊΧΤΥ
-
- .NET 8
- Πυρήνας πλαισίου οντοτήτων
-
- Πλαίσιο οντότητας Core 8.0
- SQL Server
-
- SQL Server 2022
* Το παραπάνω είναι ένα περιβάλλον επαλήθευσης, αλλά μπορεί να λειτουργήσει με άλλες εκδόσεις.
Σχετικά με τις κερδοφόρες ενημερώσεις μετά από κανέναν έλεγχο
Σε μια εφαρμογή web ή μια εφαρμογή πελάτη-διακομιστή, πολλά άτομα μπορούν να έχουν πρόσβαση και να ενημερώνουν δεδομένα από μία βάση δεδομένων. Εάν δεν γίνει τίποτα συγκεκριμένο, τα δεδομένα του ατόμου που τα ενημέρωσε αργότερα θα αντικατοπτρίζονται στη βάση δεδομένων ως τα πιο πρόσφατα.
Κανονικά, δεν υπάρχει ιδιαίτερο πρόβλημα ότι τα δεδομένα του ατόμου που ενημέρωσε αργότερα αντικατοπτρίζονται ως τα τελευταία. Προβλήματα μπορεί να προκύψουν όταν πολλά άτομα προσπαθούν να αποκτήσουν πρόσβαση και να ενημερώσουν τα ίδια δεδομένα ταυτόχρονα.
Για παράδειγμα, ας υποθέσουμε ότι έχετε τα ακόλουθα δεδομένα βιβλίου.
Τιμή ονόματος παραμέτρου | |
---|---|
Όνομα του βιβλίου | Βιβλία βάσης δεδομένων |
τιμή | 1000 |
Εάν δύο άτομα ανοίξουν την οθόνη για να επεξεργαστούν αυτά τα δεδομένα ταυτόχρονα, θα εμφανιστεί η παραπάνω τιμή. Ο κύριος/κα Α προσπαθεί να αυξήσει την τιμή αυτού του βιβλίου κατά 500 γιεν. Ο κύριος/κα Β λαμβάνει αργότερα εντολή να αυξήσει την τιμή αυτού του βιβλίου κατά άλλα 300 γιεν.
Εάν οι δύο από αυτούς αυξήσουν την τιμή ξεχωριστά αντί ταυτόχρονα, η τιμή του βιβλίου θα είναι 1800 γιεν. Εάν έχετε πρόσβαση ταυτόχρονα, θα καταχωρηθεί ως 1500 γιεν και 1300 γιεν, αντίστοιχα, οπότε δεν θα είναι 1800 γιεν ανεξάρτητα από το ποιο καταχωρεί.
Το πρόβλημα εδώ είναι ότι το άτομο που ενημέρωσε αργότερα μπορεί να το ενημερώσει χωρίς να γνωρίζει τις πληροφορίες που ενημερώθηκαν προηγουμένως.
Αισιόδοξος ταυτοχρονισμός
Το προαναφερθέν πρόβλημα μπορεί να λυθεί εκτελώντας «αισιόδοξο έλεγχο ταυτοχρονισμού» αυτή τη φορά. Για να εξηγήσουμε απλά, τι είδους έλεγχος είναι να "κερδίσετε πρώτα αν προσπαθήσετε να επεξεργαστείτε τα δεδομένα ταυτόχρονα". Όσοι προσπαθήσουν να ανανεώσουν αργότερα θα λάβουν ένα σφάλμα κατά τη στιγμή της ενημέρωσης και δεν θα μπορούν να εγγραφούν.
Μπορεί να νομίζετε ότι δεν μπορείτε να καταχωρήσετε νέα δεδομένα με αυτό, αλλά αυτό είναι μόνο "όταν προσπαθείτε να το αλλάξετε ταυτόχρονα". Είναι δυνατό για δύο άτομα να επεξεργαστούν σε εντελώς διαφορετικές χρονικές στιγμές. Φυσικά, σε αυτή την περίπτωση, τα δεδομένα του τελευταίου ενημερωμένου ατόμου θα είναι τα τελευταία.
Συγκεκριμένα, τι είδους έλεγχος επεξεργασίας μπορεί να επιτευχθεί με την «ύπαρξη μιας έκδοσης των δεδομένων». Για παράδειγμα, στο παραπάνω παράδειγμα, θα έχετε τα ακόλουθα δεδομένα.
Τιμή ονόματος παραμέτρου | |
---|---|
Όνομα του βιβλίου | Βιβλία βάσης δεδομένων |
τιμή | 1000 |
εκδοχή | 1 |
Η έκδοση αυξάνεται κατά 1 για κάθε ενημερωμένη έκδοση εγγραφής. Για παράδειγμα, εάν ο κ. / κα Α ορίσει την τιμή στα 1500 γιεν, η έκδοση θα είναι 2. Εκείνη τη στιγμή, η προϋπόθεση που μπορεί να γίνει η ενημέρωση είναι ότι η έκδοση πριν από την ενημέρωση είναι η ίδια με την έκδοση στη βάση δεδομένων. Όταν ο κύριος/κυρία το ενημερώνει, η έκδοση στη βάση δεδομένων είναι 1 και η έκδοση των αρχικών δεδομένων που επεξεργάζονται αυτήν τη στιγμή είναι 1, ώστε να μπορεί να ενημερωθεί.
Με βάση αυτήν την προδιαγραφή, υποθέστε μια κατάσταση όπου ο κ. / κα Α και ο κ. / κα Β επεξεργάζονται τα ίδια δεδομένα. Όταν ο κύριος / κα όρισε για πρώτη φορά την τιμή στα 1500 γιεν και προσπάθησε να ενημερώσει τη βάση δεδομένων, η έκδοση ήταν η ίδια, ώστε να μπορεί να ενημερωθεί. Σε αυτήν την περίπτωση, η έκδοση στη βάση δεδομένων θα είναι 2. Ο κ. / κα Β προσπαθεί να επεξεργαστεί τα δεδομένα των 1000 γιεν και να τα ενημερώσει στη βάση δεδομένων ως 1300 γιεν. Η ενημερωμένη έκδοση αποτυγχάνει επειδή η διαθέσιμη έκδοση είναι 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
είναι για τον έλεγχο των τιμών στη βάση δεδομένων. Δεν το χρειάζομαι πραγματικά γιατί το Α ή το Β μπορούν να αντικατασταθούν.
// それぞれがデータを編集しようと読み込む
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
του βιβλίου Α έχουν ενημερωθεί.
Επίσης, εάν διαβάσετε απευθείας την τιμή DB, η ενημερωμένη τιμή θα είναι η ίδια με την ενημερωμένη τιμή.
Ήμουν σε θέση να το ενημερώσω επειδή και οι δύο μπήκαν στο 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 και τον πυρήνα Framework οντοτήτων, ο αισιόδοξος ταυτοχρονισμός υλοποιείται από προεπιλογή. Ωστόσο, δεδομένου ότι είναι μόνο για να αποφευχθεί η "καταστροφή δεδομένων που πρέπει να ενημερωθεί", είναι απαραίτητο να χειριστείτε σωστά τις εξαιρέσεις όταν εμπλέκονται άλλα δεδομένα ή εμπλέκονται λειτουργίες χρήστη.
Επίσης, αυτή τη φορά δεν κατάφερα τίποτα γιατί το εφάρμοσα RowVersion
σε μια απλή εφαρμογή κονσόλας.
Εάν θέλετε να εισαγάγετε την οθόνη επεξεργασίας μετά τη φόρτωση των δεδομένων σε μια εφαρμογή web ή εφαρμογή πελάτη,
RowVersion
με κάποιο τρόπο, ώστε να μπορεί να προσδιοριστεί σωστά όταν ενημερώνεται.
Ωστόσο, το Entity Framework Core διαθέτει μια λειτουργία παρακολούθησης αλλαγών, επομένως, εάν θέλετε να ορίσετε την παλιά RowVersion
στην τιμή που διαβάζεται από τη βάση δεδομένων κατά τη στιγμή της ενημέρωσης,
bookB.RowVersion = <古い RowVersion>;
Ακόμη και αν οριστεί ως εξής, δεν θα κριθεί σωστά ως "αισιόδοξος έλεγχος ταυτοχρονισμού".
RowVersion
Ακόμα κι αν ορίσετε την τιμή σε κανονική, θα αναγνωριστεί μόνο ως η αλλαγμένη τιμή, επομένως τα ακόλουθα είναι
dbContextB.Entry(bookB).Property("RowVersion").OriginalValue = <古い RowVersion>;
Είναι απαραίτητο να ξαναγράψετε την τιμή πριν από την αλλαγή.