SqLite 中正确的 RowVersion 模拟,在 Entity Framework Core 上转换为 ulong
Proper RowVersion simulation in SqLite with conversion to ulong on Entity Framework Core
我试图通过在 rowversion 列上轻松查询来使 RowVersion 在 SqLite 和 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 属性 上有一个值。但是当它保存的时候,它认为它已经改变了。
可能是因为RowVersion值在两次SaveChanges操作之间发生了变化。
我想 EntityFramework 理解 SQL 服务器的 RowVersion 并且将仅使用 RowVersion 作为 "concurrency token"。在 SQLite 数据库上,它可能使用所有字段作为并发标记,即在我们加载它们时更新所有字段的位置。因为触发器模拟的RowVersion实际上已经改变所以它认为存在并发问题。
我不熟悉 EF,但也许您可以告诉它排除模拟的 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 进行更改。这样查询某个时间点之后的所有变化也很容易。
我试图通过在 rowversion 列上轻松查询来使 RowVersion 在 SqLite 和 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 属性 上有一个值。但是当它保存的时候,它认为它已经改变了。
可能是因为RowVersion值在两次SaveChanges操作之间发生了变化。
我想 EntityFramework 理解 SQL 服务器的 RowVersion 并且将仅使用 RowVersion 作为 "concurrency token"。在 SQLite 数据库上,它可能使用所有字段作为并发标记,即在我们加载它们时更新所有字段的位置。因为触发器模拟的RowVersion实际上已经改变所以它认为存在并发问题。
我不熟悉 EF,但也许您可以告诉它排除模拟的 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 进行更改。这样查询某个时间点之后的所有变化也很容易。