簡體   English   中英

SqLite 中的正確 RowVersion 模擬,並在 Entity Framework Core 上轉換為 ulong

[英]Proper RowVersion simulation in SqLite with conversion to ulong on Entity Framework Core

我正在嘗試使 RowVersion 在 SqLite 和 SqlServer 上正常工作,並在 rowversion 列上輕松查詢。 為了能夠做到這一點,我需要將 rowversion 列轉換為 ulong 而不是 byte[] 並且仍然讓它正常工作。

public abstract class VersionEntity
{
    public ulong RowVersion { get; set; }
}

public class Observation : VersionEntity
{
    public Guid Id { get; set; }
    public Guid TaskId { get; set; }
    public string Description { get; set; }
    public DateTime DueDate { get; set; }
    public Severity Severity { get; set; }
}

public class TestDbContext : DbContext
{
    public static string ConnectionString { get; set; } = "Data Source=dummy.db";
    public DbSet<Observation> Observation { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
         modelBuilder.Entity<Observation>().HasKey(o => o.Id);
         modelBuilder.Entity<Observation>().Property(o => o.RowVersion).HasConversion(new NumberToBytesConverter<ulong>()).IsRowVersion();
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlite(ConnectionString);
        optionsBuilder.UseLazyLoadingProxies();
    }

}

在我的第一個添加遷移中,將 RowVersion 更改為具有 rowVersion: true (沒有自動添加)。 還添加了

private string _triggerQuery = @"CREATE TRIGGER Set{0}RowVersion{1}
   AFTER {1} ON {0}
   BEGIN
      UPDATE {0}
      SET RowVersion = current_timestamp
      WHERE rowid = NEW.rowid;
   END
";

migrationBuilder.Sql(String.Format(_triggerQuery, tableName, "UPDATE"));
migrationBuilder.Sql(String.Format(_triggerQuery, tableName, "INSERT"));

這樣它是用觸發器創建的,以模擬 SqlServer RowVersion 增量全局值。

遷移工作,首先保存工作

context.Database.Migrate();
var id = Guid.NewGuid();
context.Observation.Add(new Observation
{
    Id = id,
    Description = "Test description1",
    TaskId = Guid.NewGuid(),
    Severity = Severity.Low,
    DueDate = DateTime.Now
});
context.Observation.Add(new Observation
{
    Id = Guid.NewGuid(),
    Description = "Test description2",
    TaskId = Guid.NewGuid(),
    Severity = Severity.Low,
    DueDate = DateTime.Now
});
context.SaveChanges(); // This works, and saves data
var observation = context.Observation.FirstOrDefault(o => o.Id == id);
observation.Description = "changed.."; // Checking here will show a value on RowVersion property
context.SaveChanges(); // This fail with concurrency error

並發錯誤:數據庫操作預計會影響 1 行,但實際上影響了 0 行。 自加載實體以來,數據可能已被修改或刪除。 有關了解和處理樂觀並發異常的信息,請參閱http://go.microsoft.com/fwlink/?LinkId=527962

我不明白為什么這應該是一個問題。 任何人都知道為什么這不起作用? 獲取的實體似乎在 RowVersion 屬性上有一個值。 但是當它被保存時,它認為它已經改變了。

我放棄了嘗試將 IsRowVersion 與 SqLite 一起使用。

最終將 RowVersion 的類型設置為 long,並使用 IsConcurrencyToken().ValueGeneratedOnAddOrUpdate().HasDefaultValue(0); 而不是 IsRowVersion().HasConversion(..)

也使用了 julianday 而不是 current_timestamp

private static string _triggerQuery = @"CREATE TRIGGER Set{0}RowVersion{1}
        AFTER {1} ON {0}
        BEGIN
            UPDATE {0}
            SET RowVersion = CAST(ROUND((julianday('now') - 2440587.5)*86400000) AS INT)
            WHERE rowid = NEW.rowid;
        END
    ";

migrationBuilder.Sql(String.Format(_triggerQuery, tableName, "UPDATE"));
migrationBuilder.Sql(String.Format(_triggerQuery, tableName, "INSERT"));

現在它可以正常工作了,它會下降到 ms 以進行更改。 這樣,在某個時間點之后查詢所有更改也很容易。

這可能是因為在兩次 SaveChanges 操作之間更改了 RowVersion 值。

我想 EntityFramework 理解 SQL 服務器的 RowVersion 並將僅使用 RowVersion 作為“並發令牌”。 在 SQLite 數據庫上,它可能使用所有字段作為並發標記,即在我們加載它們時更新所有字段的位置。 由於觸發器,模擬的 RowVersion 實際上已經改變,所以它認為存在並發問題。

我不熟悉 EF,但也許您可以告訴它排除模擬的 RowVersion 作為並發令牌,或者只是在更新之間重新加載記錄。

我發現了這個: https://www.infoworld.com/article/3085390/how-to-handle-concurrency-conflicts-in-entity-framework.html

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM