ConcurrencyCheck(DbQueryCommit readerWriter1, DbQueryCommit readerWriter2) { int id = 1; Blog blog1 = readerWriter1.Query<Blog>(id); Blog blog2 = readerWriter2.Query<Blog>(id); readerWriter1.Commit(() => { blog1.Name = nameof(readerWriter1); blog1.Count = 2; }); readerWriter2.Commit(() => { blog2.Name = nameof(readerWriter2); blog2.Count = 2; }); }
此时再来调用该方法:
var efContext1 = new EFCoreContext(); var d1 = new DbQueryCommit(efContext1); var efContext2 = new EFCoreContext(); var d2 = new DbQueryCommit(efContext2); //var efContext3 = new EFCoreContext(); //var d3 = new DbQueryCommit(efContext3); Concurrency.ConcurrencyCheck(d1, d2);
当我们利用两个上下文1和2去读取数据时此时Name = 'Jeffcky',当上下文1更新时在快照中根据主键和Name去查找数据库,查找到Name后并令Name = 'readerWriter1'成功更新,但是上下2去更新Name = 'readerWriter2'时,此时在快照中根据主键和Name去查找数据库,发现不存在该条数据,同时我们设置了并发Token,最终导致出现 DbUpdateConcurrencyException 并发更新异常。解决并发个两点一个是上述设置并发Token,另外一个则是设置行版本,下面我们也来看下,首先我们在类中增加一个行版本的字节属性。
public byte[] RowVersion { get; set; }
同时对该行版本进行映射标识。
pc.Property(p => p.RowVersion).IsRequired().IsRowVersion().ValueGeneratedOnAddOrUpdate();
为了很好演示行版本并发,我们增加一个属性来打印行版本字符串。
public string RowVersionString => $X16;
同样我们定义一个调用行版本的方法:
RowVersion(DbQueryCommit readerWriter1, DbQueryCommit readerWriter2) { int id = 1; Blog blog1 = readerWriter1.Query<Blog>(id); Console.WriteLine(blog1.RowVersionString); Blog blog2 = readerWriter2.Query<Blog>(id); Console.WriteLine(blog2.RowVersionString); readerWriter1.Commit(() => blog1.Name = nameof(readerWriter1)); Console.WriteLine(blog1.RowVersionString); readerWriter2.Commit(() => readerWriter2.Set<Blog>().Remove(blog2)); }
接下来我们调用演示看看。
var efContext1 = new EFCoreContext(); var d1 = new DbQueryCommit(efContext1); var efContext2 = new EFCoreContext(); var d2 = new DbQueryCommit(efContext2); //var efContext3 = new EFCoreContext(); //var d3 = new DbQueryCommit(efContext3); Concurrency.RowVersion(d1, d2);
我们从上可以明显看出当查出数据库中的行版本值为 0x000000000073 ,接着readerWriter1更新后其行版本增加为 0x000000000074 ,当我们利用readerWriter2去删除查询出id = 1的数据时,此时会根据当前主键和行版本为 0x000000000073 去查找数据库,但是此时没有找到数据,导致同样如上述并发Token一样出现并发异常。
EntityFramework Core并发中级版解析并发异常我们可以通过 DbUpdateConcurrencyException 来获取,该类继承自 DbUpdateException ,该类中的参数 EntityEntry 为一个集合,利用它则可以获取到对应的数据库中的值以及当前更新值等,所以我们可以自定义并发异常解析,如下:
public class DbUpdateException : Exception { public virtual IReadOnlyList<EntityEntry> Entries { get; } } public class DbUpdateConcurrencyException : DbUpdateException { //TODO }
这里我们需要弄明白存在EntityEntry中的值类型,比如DbUpdateConcurrencyException的参数为exception。我们通过如下则可以获取到被跟踪的实体状态。
var tracking = exception.Entries.Single();
此时存在数据库中的原始值则为如下:
var original = tracking.OriginalValues.ToObject();
而当前需要更新的值则为如下:
var current = tracking.CurrentValues.ToObject();
而数据库中的值则为已经提交更新的值:
;