上述既然出现并发异常,接下来我们则需要解析并发异常并解决异常,大部分情况下无论是提交事务失败也好还是对数据进行操作也好都会进行重试机制,所以这里我们解析到并发异常并采取重试机制。之前我们进行提交时定义如下:
public int Commit(Action change) { change(); return context.SaveChanges(); }
此时我们对该方法进行重载,遇到并发异常后并采取重试机制重试三次,如下:
public int Commit(Action change, Action<DbUpdateConcurrencyException> handleException, int retryCount = 3) { change(); for (int retry = 0; retry < retryCount; retry++) { try { return context.SaveChanges(); } catch (DbUpdateConcurrencyException exception) { handleException(exception); } } return context.SaveChanges(); }
然后我们定义一个需要出现并发并调用上述重试机制的更新方法,如下:
UpdateBlog( DbQueryCommit readerWriter1, DbQueryCommit readerWriter2, DbQueryCommit readerWriter3, Action<EntityEntry> resolveConflict) { int id = 1; Blog blog1 = readerWriter1.Query<Blog>(id); Blog blog2 = readerWriter2.Query<Blog>(id); Console.WriteLine($); Console.WriteLine(); Console.WriteLine($); Console.WriteLine(); readerWriter1.Commit(() => { blog1.Name = nameof(readerWriter1); blog1.Count = 2; }); Console.WriteLine($); Console.WriteLine(); readerWriter2.Commit( change: () => { blog2.Name = nameof(readerWriter2); blog2.Count = 1; }, handleException: exception => { EntityEntry tracking = exception.Entries.Single(); Blog original = (Blog)tracking.OriginalValues.ToObject(); Blog current = (Blog)tracking.CurrentValues.ToObject(); Blog database = blog1; ; Console.WriteLine(original); Console.WriteLine(); ; Console.WriteLine(databaseValue); Console.WriteLine(); ; Console.WriteLine(update); Console.WriteLine(); resolveConflict(tracking); }); Blog resolved = readerWriter3.Query<Blog>(id); ; Console.WriteLine(resolvedValue); }
接下来我们实例化三个上下文,稍后我会一一进行解释,避免看文章的童鞋看晕了几个值。
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);
【温馨提示】:有很多童鞋在用.net core时控制台时会遇见中文乱码的问题,主要是.net core都需要安装包来进行,以此来说明不再依赖本地程序集达到更好的跨平台,真正实现模块化,所以需要在控制台注册中文编码包,如下:
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
同时需要安装如下包:
System.Text.Encoding.CodePages
在注册后还需要设置控制台输出行编码为GB2312
Console.OutputEncoding = Encoding.GetEncoding();
接下来进行方法调用:
Concurrency.UpdateBlog(d1, d2, d3, (d) => { PropertyValues databaseValues = d.GetDatabaseValues(); if (databaseValues == null) { d.State = EntityState.Detached; } else { d.OriginalValues.SetValues(databaseValues); } });
此时控制台打印和数据库更新值如下:
我们从调用方法开始解释:
当执行到更新上下文一中的Name值时为readerWriter1都没有问题,此时行版本将变为 0x0000000000833 ,当执行到上下文2中时此时去更新Name值时,此时会根据主键和行版本 0x0000000000832 去数据库中查找值,此时却没找到,然后将执行并发异常,最终开始执行 resolveConflict(tracking); 来解析冲突,然后就来到了上述图片所示,此时databaseValues中所存的值就是readrWriter1,也就是说此时数据库中的原始值变为了Name = 'readerWriter1',我们需要做的是将数据库中的Name = 'readerWriter1'设置为原始值,这样下次再去解析冲突会根据主键id = 1和行版本0x00000000833去查找,此时找到该行数据最终进行更新到数据库,所以结果如上图所给。到这里你是不是就觉得难道就这么结束了吗?NO,这个是最简单的一个场景,上述还只是两个并发客户端,如果是多个根本无法保证能够完全解析并发,同时中间还存在一个问题,我们到底是让客户端更新值获胜还是让数据库中原始值获胜呢,这又是一个问题,如果我们完全不借助SQL语句或者存储过程来执行事务的话,这个将是个很严重的问题,比如在秒杀场景中,产品只有1000个,那么每次都让客户端获胜,好吧,那就导致库存溢出的问题,那就呵呵了,还有一个很大的问题则是合并,如果有多个并发请求过来可能我们只需要对于产品中的数量进行并发控制,其他的数据更新完全可以进行合并,这又是一个问题,那么到底该如何解决呢,请继续往下看终极解决方案。
EntityFramweork Core高并发获胜者初级版解析 EntityFramework Core并发数据库获胜既然是数据库中获胜那么对于客户端出现的并发异常我们就不需要进行解析,此时我们只需要终止异常直接返回值即可,如下定义方法: