0
0
Lập trình
Harry Tran
Harry Tran106580903228332612117

Ngăn Ngừa Ghi Đè Dữ Liệu với EF Core: Chỉ Với Một Thuộc Tính

Đăng vào 3 tuần trước

• 7 phút đọc

Vấn Đề Mất Dữ Liệu Im Lặng Trong Ứng Dụng Đa Người Dùng

Trong các ứng dụng đa người dùng, có một hành vi mặc định nguy hiểm mà nhiều lập trình viên không nhận ra cho đến khi quá muộn. Khi nhiều người dùng cùng sửa đổi dữ liệu một cách đồng thời, người cuối cùng lưu thay đổi sẽ là người chiến thắng, và mọi thay đổi của những người khác sẽ biến mất mà không để lại dấu vết.

Mất dữ liệu im lặng. Không có ngoại lệ nào được ném ra, không có thông báo lỗi, không có cảnh báo - chỉ là dữ liệu âm thầm biến mất.

Tin tốt là gì? EF Core có một giải pháp tích hợp sẵn chỉ với một thuộc tính.

Vấn Đề: Khi "Ghi Cuối Là Thắng" Trở Thành "Mọi Người Đều Thua"

Trong các ứng dụng đa người dùng, hành vi mặc định thực sự rất nguy hiểm. Khi nhiều người dùng cùng sửa đổi cùng một thực thể một cách đồng thời, người cuối cùng lưu sẽ ghi đè lên mọi thay đổi của những người khác mà không để lại dấu vết.

Dưới đây là cách mà kịch bản phổ biến này diễn ra:

  1. Người dùng A tải một sản phẩm (Tồn kho: 100, Giá: 25.99$, Tên: "Widget")
  2. Người dùng B tải cùng một sản phẩm (Tồn kho: 100, Giá: 25.99$, Tên: "Widget")
  3. Người dùng A thay đổi tồn kho thành 1000 (có thể là thay đổi hàng tồn kho số lượng lớn)
  4. Người dùng B giảm tồn kho xuống 75 thông qua SQL thô (cập nhật trực tiếp cơ sở dữ liệu)
  5. Người dùng A lưu thay đổi của họ ✅
  6. Kết quả: Giá trị tồn kho của Người dùng A (1000) ghi đè lên thay đổi tồn kho của Người dùng B (75) 💥

Điểm mấu chốt: Mất dữ liệu xảy ra khi cả hai người dùng cùng sửa đổi cùng một trường một cách đồng thời. Nếu Người dùng A chỉ thay đổi tên và giá (để tồn kho không bị thay đổi), thay đổi tồn kho của Người dùng B sẽ vẫn tồn tại. Nhưng khi cả hai thao tác đều chạm đến cùng một trường, việc lưu của EF Core sẽ ghi đè lên thay đổi trực tiếp trong cơ sở dữ liệu.

Lưu ý: Điều này xảy ra đặc biệt khi SaveChanges() của EF Core bao gồm một trường cũng đã được sửa đổi bởi SQL thô hoặc một thao tác đồng thời khác.

Người Hùng: Một Thuộc Tính Để Quản Lý Mọi Thứ

Gặp gỡ thuộc tính [Timestamp] - siêu anh hùng tích hợp sẵn của EF Core cho tính toàn vẹn dữ liệu:

csharp Copy
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Stock { get; set; }
    public decimal Price { get; set; }

    [Timestamp]
    public byte[] RowVersion { get; set; } = new byte[8];  // 🦸‍♂️ Người bảo vệ dữ liệu của bạn
}

Chỉ cần như vậy. Một thuộc tính. Tám byte. Không mất dữ liệu.

Ma Thuật Đằng Sau Hậu Trường

Khi bạn thêm [Timestamp] vào một thuộc tính, EF Core chuyển từ một người đứng xem thụ động thành một người bảo vệ chủ động:

Không Có RowVersion (Cách Nguy Hiểm):

sql Copy
-- Lưu của Người dùng B ghi đè mọi thứ
UPDATE Products 
SET Name = 'Original Widget', Stock = 50, Price = 25.99 
WHERE Id = 1

Với RowVersion (Cách An Toàn):

sql Copy
-- EF Core bao gồm phiên bản trong điều kiện WHERE
UPDATE Products 
SET Name = 'Premium Widget', Stock = 50, Price = 29.99 
WHERE Id = 1 AND RowVersion = 0x00000000000007D0

Nếu một người dùng khác đã thay đổi dữ liệu (cập nhật RowVersion), truy vấn này sẽ ảnh hưởng đến 0 hàng, kích hoạt một DbUpdateConcurrencyException. Không còn mất dữ liệu im lặng. Bao giờ cũng vậy.

Xem Nó Hoạt Động: Bản Demo Thay Đổi Cách Bạn Nghĩ Về Dữ Liệu

Tôi đã xây dựng một bản demo hoạt động cho thấy chính xác những gì xảy ra với và không có kiểm soát đồng thời. Đây là những gì bạn có thể kiểm tra:

🔴 Kịch Bản Nguy Hiểm

http Copy
POST /demo-concurrency-with-stock-change

Điều gì xảy ra: EF Core âm thầm ghi đè các thay đổi đồng thời. Mất dữ liệu xảy ra và không ai biết.

🟢 Kịch Bản Được Bảo Vệ

http Copy
POST /demo-with-rowversion

Điều gì xảy ra: DbUpdateConcurrencyException được ném ra. Xung đột được phát hiện và cần được xử lý rõ ràng.

🟡 Trường Hợp Biên

http Copy
POST /demo-concurrency-no-stock-change

Điều gì xảy ra: Khi EF Core không sửa đổi một trường, các thay đổi SQL thô đồng thời vẫn tồn tại. Thú vị, nhưng không đáng tin cậy cho sản xuất.

Xử Lý Xung Đột Như Một Chuyên Gia

Khi DbUpdateConcurrencyException xảy ra, bạn có ba chiến lược đã được kiểm chứng:

1. Lưu Thắng (Tải lại và Hiển Thị Dữ Liệu Hiện Tại)

csharp Copy
catch (DbUpdateConcurrencyException)
{
    await context.Entry(product).ReloadAsync();
    // Hiển thị cho người dùng giá trị hiện tại của cơ sở dữ liệu
    // Để họ quyết định cách xử lý
}

2. Khách Hàng Thắng (Ép Cập Nhật)

csharp Copy
catch (DbUpdateConcurrencyException ex)
{
    var entry = ex.Entries.Single();
    entry.OriginalValues.SetValues(entry.GetDatabaseValues());
    await context.SaveChangesAsync(); // Ép lưu
}

3. Hợp Nhất Thông Minh (Tốt Nhất Của Cả Hai Thế Giới)

csharp Copy
catch (DbUpdateConcurrencyException ex)
{
    var entry = ex.Entries.Single();
    var currentValues = entry.CurrentValues;
    var databaseValues = entry.GetDatabaseValues();

    // Ví dụ: Giữ lại thay đổi tên/giá của người dùng, bảo tồn tồn kho trong cơ sở dữ liệu
    currentValues["Stock"] = databaseValues["Stock"];

    entry.OriginalValues.SetValues(databaseValues);
    await context.SaveChangesAsync();
}

Tại Sao Điều Này Quan Trọng

Chi Phí Của Mất Dữ Liệu:

  • Thương mại điện tử: Tồn kho không chính xác dẫn đến bán vượt quá
  • Tài chính: Số tiền giao dịch bị hỏng
  • Chăm sóc sức khỏe: Dữ liệu bệnh nhân trở nên không nhất quán
  • Bất kỳ Doanh Nghiệp Nào: Niềm tin của người dùng giảm sút, danh tiếng bị ảnh hưởng

Chi Phí Thực Hiện:

  • Thời gian phát triển: 5 phút để thêm thuộc tính
  • Tác động hiệu suất: ~1ms mỗi thao tác
  • Chi phí lưu trữ: 8 byte mỗi hàng
  • Bảo trì: Không có - nó chỉ hoạt động

ROI: Vô hạn. Bạn không thể đặt giá trị cho tính toàn vẹn dữ liệu.

Danh Sách Kiểm Tra Đồng Thời Của Lập Trình Viên

✅ Luôn Sử Dụng RowVersion Cho:

  • Ứng dụng đa người dùng
  • Giao dịch tài chính
  • Quản lý tồn kho
  • Bất kỳ dữ liệu quan trọng nào
  • Các biểu mẫu dài hạn

📋 Thực Hành Tốt Nhất Trong Triển Khai:

Tạo Một Thực Thể Cơ Sở:

csharp Copy
public abstract class BaseEntity
{
    public int Id { get; set; }

    [Timestamp]
    public byte[] RowVersion { get; set; } = new byte[8];
}

// Bây giờ tất cả các thực thể của bạn đều được bảo vệ
public class Product : BaseEntity
{
    public string Name { get; set; } = "";
    public int Stock { get; set; }
    public decimal Price { get; set; }
}

Kiểm Tra Các Kịch Bản Đồng Thời:

csharp Copy
[Fact]
public async Task Should_Detect_Concurrent_Updates()
{
    // Tải cùng một thực thể trong hai ngữ cảnh
    var product1 = await context1.Products.FindAsync(id);
    var product2 = await context2.Products.FindAsync(id);

    // Sửa đổi cả hai
    product1.Name = "Phiên bản 1";
    product2.Stock = 50;

    // Lưu đầu tiên thành công
    await context1.SaveChangesAsync();

    // Lưu thứ hai nên ném ra
    await Assert.ThrowsAsync<DbUpdateConcurrencyException>(
        () => context2.SaveChangesAsync());
}

Kiểm Tra Thực Tế: Hiệu Suất So Với Bảo Vệ

Khía Cạnh Không Có RowVersion Có RowVersion
Nguy Cơ Mất Dữ Liệu ❌ Cao ✅ Không
Phát Hiện Xung Đột ❌ Thất bại im lặng ✅ Ngoại lệ rõ ràng
Nỗ Lực Triển Khai Không có 1 thuộc tính
Tác Động Hiệu Suất Cơ sở +8 byte, +~1ms
Tâm Lý An Tâm ❌ Đêm không ngủ ✅ Ngủ như một đứa trẻ

Hãy Thử Ngay

Bản demo hoàn chỉnh có sẵn trên GitHub. Sao chép nó, chạy nó và xem phép màu xảy ra:

bash Copy
git clone https://github.com/abdebek/efcore-db-concurrency-demo.git
cd efcore-db-concurrency-demo
dotnet restore
dotnet run

# Kiểm tra các kịch bản
curl -X POST https://localhost:7112/demo-with-rowversion

Kết Luận

Trong một thế giới mà dữ liệu là tài sản quý giá nhất của bạn, bạn có thể đủ khả năng không bảo vệ nó không?

Thuộc tính [Timestamp] là:

  • ✅ Tích hợp sẵn trong EF Core
  • ✅ Tự động và đáng tin cậy
  • ✅ Không cần bảo trì
  • ✅ Đã được kiểm tra trong sản xuất
  • ✅ Sự khác biệt giữa mất dữ liệu và an toàn dữ liệu

Một thuộc tính. Không mất dữ liệu. Đó là sức mạnh của [Timestamp].


Bạn đã từng trải qua mất dữ liệu im lặng trong các ứng dụng của mình? Bạn đã giải quyết nó như thế nào? Chia sẻ câu chuyện của bạn trong phần bình luận bên dưới!

🔗 Tài Nguyên Hữu Ích:

  • Tài liệu về Đồng Thời EF Core
  • Xử lý Xung Đột Đồng Thời
  • Kho demo

👏 Nếu bạn thấy điều này hữu ích? Hãy tán thưởng và theo dõi để nhận thêm những thông tin phát triển thực tiễn!

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào