【发布时间】:2019-03-12 01:45:44
【问题描述】:
我尝试在带有 SQLite 的 EF Core 中使用乐观并发检查。
最简单的积极情景(即使没有并发本身)给了我
Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: 'Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded.
实体:
public class Blog
{
public Guid Id { get; set; }
public string Name { get; set; }
public byte[] Timestamp { get; set; }
}
上下文:
internal class Context : DbContext
{
public DbSet<Blog> Blogs { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite(@"Data Source=D:\incoming\test.db");
///optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Blogging;Trusted_Connection=True;");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasKey(p => p.Id);
modelBuilder.Entity<Blog>()
.Property(p => p.Timestamp)
.IsRowVersion()
.HasDefaultValueSql("CURRENT_TIMESTAMP");
}
}
示例:
internal class Program
{
public static void Main(string[] args)
{
var id = Guid.NewGuid();
using (var db = new Context())
{
db.Database.EnsureDeleted();
db.Database.EnsureCreated();
db.Blogs.Add(new Blog { Id = id, Name = "1" });
db.SaveChanges();
}
using (var db = new Context())
{
var existing = db.Blogs.Find(id);
existing.Name = "2";
db.SaveChanges(); // Exception thrown: 'Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException'
}
}
}
我怀疑这与 EF 和 SQLite 之间的数据类型有关。 日志记录为我的更新提供了以下查询:
Executing DbCommand [Parameters=[@p1='2bcc42f5-5fd9-4cd6-b0a0-d1b843022a4b' (DbType = String), @p0='2' (Size = 1), @p2='0x323031382D31302D30372030393A34393A3331' (Size = 19) (DbType = String)], CommandType='Text', CommandTimeout='30']
UPDATE "Blogs" SET "Name" = @p0
WHERE "Id" = @p1 AND "Timestamp" = @p2;
但是 Id 和 Timestamp 的列类型都是 BLOB(SQLite 不提供 UUID 和时间戳列类型):
同时,如果我使用 SQL Server(使用注释连接字符串 + 删除 .HasDefaultValueSql("CURRENT_TIMESTAMP")),示例可以正常工作并更新数据库中的时间戳。
使用过的包:
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.1.4" />
我是否为并发检查配置了错误的模型? 这让我发疯,我无法让它与这个最简单的场景一起工作。
更新:我最终是如何让它发挥作用的。这里只展示了一个想法,但它可能对任何人都有帮助:
public class Blog
{
public Guid Id { get; set; }
public string Name { get; set; }
public long Version { get; set; }
}
internal class Context : DbContext
{
public DbSet<Blog> Blogs { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite(@"Data Source=D:\incoming\test.db");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasKey(p => p.Id);
modelBuilder.Entity<Blog>()
.Property(p => p.Version)
.IsConcurrencyToken();
}
}
internal class Program
{
public static void Main(string[] args)
{
var id = Guid.NewGuid();
long ver;
using (var db = new Context())
{
db.Database.EnsureDeleted();
db.Database.EnsureCreated();
var res = db.Blogs.Add(new Blog { Id = id, Name = "xxx", Version = DateTime.Now.Ticks});
db.SaveChanges();
}
using (var db = new Context())
{
var existing = db.Blogs.Find(id);
existing.Name = "yyy";
existing.Version = DateTime.Now.Ticks;
db.SaveChanges(); // success
}
using (var db = new Context())
{
var existing = db.Blogs.Find(id);
existing.Name = "zzz";
existing.Version = DateTime.Now.Ticks;
db.SaveChanges(); // success
}
var t1 = Task.Run(() =>
{
using (var db = new Context())
{
var existing = db.Blogs.Find(id);
existing.Name = "yyy";
existing.Version = DateTime.Now.Ticks;
db.SaveChanges();
}
});
var t2 = Task.Run(() =>
{
using (var db = new Context())
{
var existing = db.Blogs.Find(id);
existing.Name = "zzz";
existing.Version = DateTime.Now.Ticks;
db.SaveChanges();
}
});
Task.WaitAll(t1, t2); // one of the tasks throws DbUpdateConcurrencyException
}
}
【问题讨论】:
-
您是否尝试只取消注释连接字符串代码?
-
@viveknuna,如果将 SQLite 连接字符串更改为 SQLServer 之一,我必须删除 .HasDefaultValueSql("CURRENT_TIMESTAMP") 因为它是 SQL Server 的非法值(Defaults无法在数据类型时间戳的列上创建。表“博客”,列“时间戳”。无法创建约束或索引。)
-
你能用“getdate()”代替“current_timestamp”吗?
-
@viveknuna,首先,我对 SQLite 的解决方案很感兴趣。正如我所说,我的示例适用于 SQL Server。第二: TIMESTAMP 值由 SQL Server 自动生成和分配。不需要为此数据类型的列指定 DEFAULT 约束,因此不允许。
-
我只建议 SQLite 的解决方案。你有哪个 EF Core 版本?
标签: c# sqlite entity-framework-core