Bạn có thể giải thích khóa bi quan trong Entity Framework không?
Nói cách khác, câu hỏi như sau. làm cách nào để đảm bảo rằng ngay cả trong môi trường đa luồng, chúng tôi luôn có thể đảm bảo tính nhất quán ngay lập tức cho các quy tắc kinh doanh của mình? Show Tốt nhất là mô tả vấn đề này bằng một ví dụ… Vấn đềGiả sử rằng miền của chúng ta có khái niệm về Đơn hàng và Dòng đơn hàng. Chuyên gia tên miền cho rằng
Ngoài ra, ông nói rằng quy tắc này phải được đáp ứng mọi lúc (không có ngoại lệ). Chúng ta có thể mô hình hóa nó theo cách sau Mô hình khái niệmTổng hợpMột trong những mục tiêu chính của hệ thống của chúng tôi là thực thi các quy tắc kinh doanh. Một cách để thực hiện điều này là sử dụng khối xây dựng chiến thuật Thiết kế theo hướng miền – một Tổng hợp . Tổng hợp là một khái niệm được tạo ra để thực thi các quy tắc kinh doanh (bất biến). Việc triển khai nó có thể khác nhau tùy thuộc vào mô hình mà chúng ta sử dụng, nhưng trong lập trình hướng đối tượng, nó là một biểu đồ hướng đối tượng như Martin Fowler mô tả.
Và Eric Evans trong tài liệu tham khảo DDD mô tả
Quay lại ví dụ. Làm thế nào chúng tôi có thể đảm bảo rằng Đơn đặt hàng của bạn sẽ không bao giờ vượt quá 5 Dòng đặt hàng? . Để làm được điều này, nó phải có thông tin về số Dòng lệnh hiện tại. Vì vậy, nó phải có một trạng thái và dựa trên trạng thái này, trách nhiệm của nó là quyết định xem bất biến có bị hỏng hay không Trong trường hợp này, Order dường như là đối tượng hoàn hảo cho việc này. Nó sẽ trở thành gốc của Tổng hợp của chúng tôi, sẽ có Dòng thứ tự. Anh ta sẽ có trách nhiệm thêm dòng thứ tự và kiểm tra xem bất biến có bị hỏng không Đặt hàng tổng hợpHãy xem cách triển khai một cấu trúc như vậy có thể trông như thế nào Thực thể đặt hàng C#1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 lớp công khai Thứ tự . AggregateRootBase { công khai Hướng dẫn Id { get; private set; }
riêng tư Danh sách<Dòng đặt hàng> _orderLines;
riêng tư Ngày giờ? _modifyDate;
riêng tư Đặt hàng() { _orderLines = mới Danh sách<OrderLine>(); }
công khai vô hiệu AddOrderLine(string productCode) { if (_orderLines. Đếm >= 5) { ném mới Ngoại lệ("Order cannot have more than 5 order lines."); }
_orderLines. Thêm(Dòng đặt hàng. Tạo mới(Mã sản phẩm)); _modifyDate = DateTime. Bây giờ;
AddDomainEvent(new OrderLineAddedDomainEvent(this.Id)); } } Hiện tại mọi thứ đều ổn, nhưng đây chỉ là chế độ xem tĩnh của mô hình của chúng tôi. Hãy xem luồng hệ thống điển hình là gì khi bất biến không bị hỏng và khi nào bị hỏng Quá trình thêm Dòng đặt hàng – thành côngQuá trình thêm Dòng đặt hàng – quy tắc bị hỏngThực hiện đơn giản dưới đây Thêm dòng đặt hàng C#1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 [ApiController] [Tuyến đường("[bộ điều khiển]")] lớp công khai Trình điều khiển đơn hàng . ControllerBase { riêng tư chỉ đọc Bối cảnh đơn hàng _bối cảnh đơn hàng;
công khai OrdersController(OrdersContext ordersContext) { _ordersContext = ordersContext; }
[HttpPost] công khai không đồng bộ Tác vụ<IActionResult> AddOrderLine(AddOrderLineRequest request) { var orderId = Hướng dẫn.Phân tích cú pháp("33d4201c-4a8e-40a2-ae1d-50bc64097085");
var đặt hàng = đang chờ _ordersContext.Đơn đặt hàng. FindAsync(orderId);
Chủ đề. Ngủ(3000);
đặt hàng. AddOrderLine(yêu cầu. Mã sản phẩm);
chờ đợi _ordersContext. SaveChangesAsync();
return Bật(); } } vấn đề đồng thờiMọi thứ hoạt động tốt và sạch sẽ nếu chúng ta hoạt động trong môi trường không tải nặng. Tuy nhiên, hãy xem điều gì có thể xảy ra khi 2 luồng gần như cùng lúc muốn thực hiện thao tác của chúng ta Quy trình thêm Order Line – quy tắc nghiệp vụ bị phá vỡNhư bạn có thể thấy trong sơ đồ trên, 2 luồng tải chính xác cùng một tập hợp tại cùng một thời điểm. Giả sử rằng Đơn đặt hàng có 4 Dòng đặt hàng. Tổng hợp có 4 Dòng lệnh sẽ được tải trong cả luồng thứ nhất và luồng thứ hai. Ngoại lệ sẽ không được đưa ra, vì 4 < 5. Cuối cùng, tùy thuộc vào cách chúng tôi duy trì tổng hợp, các tình huống sau có thể xảy ra a) Nếu chúng ta có một cơ sở dữ liệu quan hệ và một bảng riêng cho các Dòng đặt hàng thì 2 Dòng đặt hàng sẽ được thêm vào với tổng số 6 Dòng đặt hàng – quy tắc kinh doanh bị phá vỡ b) Nếu chúng tôi lưu trữ tổng hợp ở một vị trí nguyên tử (ví dụ: trong cơ sở dữ liệu tài liệu dưới dạng đối tượng JSON), chuỗi thứ hai sẽ ghi đè hoạt động đầu tiên và chúng tôi (và Người dùng) thậm chí sẽ không biết về điều này Lý do cho hành vi này là luồng thứ hai đọc dữ liệu từ cơ sở dữ liệu (điểm 2. 1) trước khi người đầu tiên cam kết (điểm 1. 4. 3) Hãy xem làm thế nào chúng ta có thể giải quyết vấn đề này Dung dịchđồng thời bi quanCách đầu tiên để đảm bảo rằng quy tắc kinh doanh của chúng tôi sẽ không bị phá vỡ là sử dụng Bi quan đồng thời . Theo cách tiếp cận đó, chúng tôi chỉ cho phép một luồng xử lý một Tổng hợp nhất định. Điều này dẫn đến việc luồng xử lý phải chặn việc đọc các luồng khác bằng cách tạo khóa. Chỉ khi giải phóng khóa, luồng tiếp theo mới có thể lấy đối tượng và xử lý nó. đồng thời bi quanSự khác biệt chính so với cách tiếp cận trước đó là luồng thứ hai đợi luồng trước kết thúc (thời gian giữa các điểm 2. 1 và 1. 3). Cách tiếp cận này khiến chúng tôi giảm hiệu suất vì chúng tôi chỉ có thể xử lý giao dịch lần lượt. Hơn nữa, nó có thể dẫn đến bế tắc Làm cách nào chúng tôi có thể triển khai hành vi này bằng EntityFramework Core và máy chủ SQL? Thật không may, EF Core không hỗ trợ Đồng thời bi quan . Tuy nhiên, chúng ta có thể tự làm điều đó một cách dễ dàng bằng cách sử dụng SQL thô và cơ chế gợi ý truy vấn của công cụ SQL Server. Đầu tiên, giao dịch cơ sở dữ liệu phải được thiết lập. Sau đó, khóa phải được thiết lập. Điều này có thể được thực hiện theo hai cách – đọc dữ liệu với gợi ý truy vấn (XLOCK, PAGELOCK) hoặc bằng cách cập nhật bản ghi ngay từ đầu đồng thời bi quan C#1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 công khai không đồng bộ Tác vụ<IActionResult> AddOrderLine(AddOrderLineRequest request) { var orderId = Hướng dẫn.Phân tích cú pháp("33d4201c-4a8e-40a2-ae1d-50bc64097085");
chờ sử dụng (var tran = await _ordersContext.Cơ sở dữ liệu. BeginTransactionAsync()) { chờ đợi _ordersContext. Cơ sở dữ liệu . ExecuteSqlRawAsync($"CẬP NHẬT đơn đặt hàng. Đơn đặt hàng VỚI (XLOCK) SET Id = Id WHERE Id = '{orderId}'");
var đặt hàng = đang chờ _ordersContext.Đơn đặt hàng. FindAsync(orderId);
Chủ đề. Ngủ(3000);
đặt hàng. AddOrderLine(yêu cầu. Mã sản phẩm);
chờ đợi _ordersContext. SaveChangesAsync(); }
return Bật(); } Bằng cách này, giao dịch trên chuỗi đầu tiên sẽ nhận được một Khóa độc quyền (khi ghi) và cho đến khi nó giải phóng nó (thông qua . Tất nhiên, giả sử rằng các truy vấn của chúng tôi hoạt động ở mức cô lập giao dịch đã cam kết ít nhất là đọc để tránh cái gọi là đọc bẩn. đồng thời lạc quanMột giải pháp thay thế và thường được ưu tiên nhất là sử dụng Đồng thời lạc quan . Trong trường hợp này, toàn bộ quá trình diễn ra mà không khóa dữ liệu. Thay vào đó, dữ liệu trong cơ sở dữ liệu được tạo phiên bản và trong quá trình cập nhật, nó được kiểm tra – liệu có thay đổi phiên bản trong thời gian chờ đợi hay không. đồng thời lạc quanViệc triển khai giải pháp này như thế nào? . Nó đủ để chỉ ra những trường nào cần được kiểm tra khi ghi vào cơ sở dữ liệu và nó sẽ được thêm vào câu lệnh Optimistic Concurrency out of the box. It is enough to indicate which fields should be checked when writing to the database and it will be added to the WHERE . Nếu hóa ra câu lệnh của chúng tôi đã cập nhật 0 bản ghi, điều đó có nghĩa là phiên bản của bản ghi đã thay đổi và chúng tôi cần thực hiện khôi phục. Mặc dù các trường hiện tại thường không được sử dụng để kiểm tra phiên bản và cột đặc biệt có tên “Phiên bản” hoặc “Dấu thời gian” được thêm vào. Quay lại ví dụ của chúng tôi, việc thêm một cột có phiên bản và tăng nó mỗi khi thực thể Đơn hàng được thay đổi có giải quyết được sự cố không? . Tổng hợp phải được coi là một tổng thể, như một ranh giới của giao dịch và tính nhất quán Do đó, việc tăng phiên bản phải diễn ra khi chúng tôi thay đổi bất kỳ thứ gì trong tổng hợp của mình. Nếu chúng tôi đã thêm Dòng đặt hàng và chúng tôi giữ nó trong một bảng riêng biệt, hỗ trợ ORM cho đồng thời lạc quan sẽ không giúp ích gì cho chúng tôi vì nó hoạt động trên các bản cập nhật và ở đây có các phần chèn và xóa còn lại Làm cách nào để chúng tôi biết rằng trạng thái của Tổng hợp của chúng tôi đã thay đổi? . Nếu một Sự kiện miền bị ném khỏi Tổng hợp, điều đó có nghĩa là trạng thái đã thay đổi và chúng tôi cần tăng phiên bản Tổng hợp. Việc thực hiện có thể trông như thế này Đầu tiên, chúng tôi thêm trường _version vào mỗi Gốc tổng hợp và phương pháp để tăng phiên bản đó. AggregateRootBase với phiên bản C#1 2 3 4 5 6 7 8 9 lớp công khai AggregateRootBase . Thực thể, IAggregateRoot { riêng tư int _versionId;
công khai vô hiệu IncreaseVersion() { _versionId++; } } Thứ hai, chúng tôi thêm ánh xạ cho thuộc tính phiên bản và chỉ ra rằng đó là Mã thông báo đồng thời EF Ánh xạ cấu hình loại thực thể đặt hàng C#1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 nội bộ được niêm phong lớp OrderEntityTypeConfiguration . IEntityTypeConfiguration<Đơn hàng> { công khai vô hiệu Định cấu hình(EntityTypeBuilder<Order> builder) { người xây dựng. ToTable("Đơn hàng", "orders");
người xây dựng. HasKey(b => . b.Là); người xây dựng. Thuộc tính("_modifyDate").HasColumnName("ModifyDate"); người xây dựng. Thuộc tính("_versionId").HasColumnName("VersionId").IsConcurrencyToken();
người xây dựng. Sở hữu nhiều<Dòng đặt hàng>("_orderLines", orderLine => { orderLine. Với chủ sở hữu(). HasForeignKey("OrderId");
orderLine. ToTable("OrderLines", "orders");
orderLine. Thuộc tính<Hướng dẫn>( . "Id").ValueGeneratedNever(); orderLine. HasKey("Id"); orderLine. Thuộc tính<chuỗi>( . "_productCode").HasColumnName("ProductCode"); }); } } Điều cuối cùng cần làm là tăng phiên bản nếu có bất kỳ Sự kiện miền nào đã được xuất bản Tăng phiên bản tổng hợp đơn hàng C#1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 [HttpPost] công khai không đồng bộ Tác vụ<IActionResult> AddOrderLine(AddOrderLineRequest request) { var orderId = Hướng dẫn.Phân tích cú pháp("33d4201c-4a8e-40a2-ae1d-50bc64097085");
var đặt hàng = chờ đợi _ordersContext.Đơn đặt hàng. FindAsync(orderId);
Chủ đề. Ngủ(3000);
đặt hàng. AddOrderLine(yêu cầu. Mã sản phẩm);
var domainEvents = DomainEventsHelper.GetAllDomainEvents(đặt hàng);
if (sự kiện tên miền. Bất kỳ()) { đặt hàng. IncreaseVersion(); }
await _ordersContext. SaveChangesAsync();
return Bật(); } Với thiết lập này, tất cả các bản cập nhật sẽ thực thi câu lệnh này C#1 2 3 4 5 6 thông tin. Microsoft. EntityFrameworkCore. Cơ sở dữ liệu. Lệnh[20101] Đã thực thi DbCommand (0ms) [Parameters=[@p2='33d4201c-4a8e-40a2-ae1d-50bc64097085', @p0='2020-05-14T21:02:41' (Không thể = true), @p1='8', @p3='7'], CommandType='Text', CommandTimeout='30'] ĐẶT KHÔNG CÓ BẬT; CẬP NHẬT [đơn đặt hàng].[Đơn đặt hàng] ĐẶT [VersionId] = @p1 Ở ĐÂU [Id] = @p2 AND [VersionId] = @p3; CHỌN @@ROWCOUNT; Ví dụ của chúng tôi, đối với luồng thứ hai, sẽ không có bản ghi nào được cập nhật ( @@ ROWOCOUNT = 0 ), so EntityFramework will throw the following message:
và Tổng hợp của chúng tôi sẽ nhất quán – Dòng lệnh thứ 6 sẽ không được thêm vào. Quy tắc kinh doanh không bị phá vỡ, nhiệm vụ đã hoàn thành Tóm lượcTóm lại, những vấn đề quan trọng nhất ở đây là
Kho lưu trữ mẫu GitHubĐặc biệt để phục vụ cho nhu cầu của bài viết này, tôi đã tạo một kho lưu trữ hiển thị việc triển khai 3 kịch bản Khóa bi quan là gì?Khóa bi quan
. Những người dùng khác cố gắng cập nhật hồ sơ này được thông báo rằng một người dùng khác đang tiến hành cập nhật. Những người dùng khác phải đợi cho đến khi người dùng đầu tiên thực hiện xong các thay đổi của họ, do đó giải phóng khóa bản ghi. As soon as one user starts to update a record, a lock is placed on it. Other users who attempt to update this record are informed that another user has an update in progress. The other users must wait until the first user has finished committing their changes, thereby releasing the record lock.
Đồng thời lạc quan trong Entity Framework là gì?Đồng thời lạc quan liên quan đến cố gắng lưu thực thể của bạn vào cơ sở dữ liệu một cách lạc quan với hy vọng rằng dữ liệu trong đó không thay đổi kể từ khi thực thể được tải . Nếu hóa ra dữ liệu đã thay đổi thì một ngoại lệ sẽ được đưa ra và bạn phải giải quyết xung đột trước khi thử lưu lại.
Hai loại khóa trong cơ sở dữ liệu là gì?Có hai loại khóa Cơ sở dữ liệu. . Khóa chung Khóa độc quyền Điều khiển đồng thời lạc quan hay bi quan nào tốt hơn?Nói chung, đồng thời lạc quan sẽ hiệu quả hơn khi các xung đột cập nhật dự kiến sẽ không xảy ra thường xuyên ; . |