5

我试图通过对 rowversion 列的简单查询使 RowVersion 在 SqlLite 和 SqlServer 上正常工作。为了能够做到这一点,我需要将 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 属性上有一个值。但是当它被保存时,它认为它已经改变了。

4

2 回答 2

1

我放弃了尝试将 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 以进行更改。这样,在某个时间点之后查询所有更改也很容易。

于 2019-10-28T08:57:34.443 回答
0

这可能是因为在两次 SaveChanges 操作之间更改了 RowVersion 值。

我想 EntityFramework 理解 SQL Server 的 RowVersion 并将仅使用 RowVersion 作为“并发令牌”。在 SQLite 数据库上,它可能使用所有字段作为并发标记,即在我们加载它们时更新所有字段的位置。由于触发器,模拟的 RowVersion 实际上已经改变,所以它认为存在并发问题。

我不熟悉 EF,但也许您可以告诉它排除模拟的 RowVersion 作为并发令牌,或者只是在更新之间重新加载记录。

我发现了这个: https ://www.infoworld.com/article/3085390/how-to-handle-concurrency-conflicts-in-entity-framework.html

于 2019-10-18T11:37:56.840 回答