博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
EntityFramework Core并发导致显式插入主键问题
阅读量:5115 次
发布时间:2019-06-13

本文共 5055 字,大约阅读时间需要 16 分钟。

前言

之前讨论过EntityFramework Core中并发问题,按照官网所给并发冲突解决方案以为没有什么问题,但是在做单元测试时发现too young,too simple,下面我们一起来看看。

.NET Core 1.1单元测试问题

我们循序渐进,首先从单元测试开始说起,可能其中就有你在.NET Core上进行单元测试会遇到的问题,别着急,不妨一看。我们需要创建.NET Core类库,,如下:

接下来对project.json进行如下修改。

{    "version": "1.0.0-*",    "testRunner": "xunit",    "dependencies": {        "xunit": "2.2.0-beta2-build3300",        "dotnet-test-xunit": "2.2.0-preview2-build1029"    },    "frameworks": {        "netcoreapp1.0": {            "dependencies": {                "Microsoft.NETCore.App": {                    "type": "platform",                    "version": "1.0.0"                }            }        }    }}

此时运行单元测试肯定是好使的,由于.NET Core最新为1.1版本此时我们将其版本修改为1.1如下:

"frameworks": {    "netcoreapp1.1": {      "dependencies": {        "Microsoft.NETCore.App": {          "type": "platform",          "version": "1.1.0"        }      }    }  }

此时我们写一个测试方法,如下:

public class Test    {        [Fact]        public void PassingTest()        {            Assert.Equal(4, Add(2, 2));        }        int Add(int x, int y)        {            return x + y;        }    }

此时运行会出现如下dotnet.exe出现异常关闭。

结果让我们大跌眼镜,根本不知道什么地方出错了,此时你再运行单元测试,因为dotnet.exe已经关闭将导致单元测试无法启动,于是乎我们通过 dotnet test 命令来运行看能否得到一点错误提示,结果还是让我找到了原因。

无法加载 Microsoft.DotNet.InternalAbstractions 程序集,此时我们添加该程序集再看看,如下。

"Microsoft.DotNet.InternalAbstractions": "1.0.1-beta-003206"

完美,结果测试通过,至此关于单元测试我们有必要做下结论:官网所给对应的是针对于.net core 1.0版本,运行测试没问题,若是.net core 1.1版本需要添加 Microsoft.DotNet.InternalAbstractions 包。我是从github上才找到解决方案【】,需要添加上述依赖包才可。

EntityFramework Core 1.1并发导致显式插入主键问题 

我们从头讲起,在仓储接口中定义插入Blog的接口,如下:

public interface IBlogRepository : IEntityBaseRepository
{ void Create(Blog b); }

然后则是实现该接口了,如下:

public class BlogRepository : EntityBaseRepository
, IBlogRepository { private EFCoreContext _efCoreContext; public BlogRepository(EFCoreContext efCoreContext) : base(efCoreContext) { _efCoreContext = efCoreContext; } public void Create(Blog b) { try { using (var transaction = _efCoreContext.Database.BeginTransaction()) { _efCoreContext.Blogs.Add(b); _efCoreContext.SaveChanges(); transaction.Commit(); } } catch (DbUpdateConcurrencyException ex) {...} } }

接下来一切准备就绪,我们来开始进行单元测试,我们开启两个线程来测试看看。

[Fact]        public void TestEFCore()        {            var blog = new Blog()            {                Name = "Jeffcky",                Url = "http://www.cnblogs.com/CreateMyself",                Posts = new List
() { new Post() { Title = "a", Content = "ss" } } }; var tasks = new Task[2]; for (int i = 0; i < tasks.Length; i++) { tasks[i] = Task.Factory.StartNew(() => { var _contextOptions = new DbContextOptionsBuilder() .UseSqlServer("server=WANGPENG;Database=EFCoreDb;Trusted_Connection=True;") .Options; using (var efcoreContext = new EFCoreContext(_contextOptions)) { var blogRepository = new BlogRepository(efcoreContext); blogRepository.Create(blog); } }); } Task.WaitAll(tasks); }

此时演示结果如下,测试也通过。

当修改测试所开线程,开启如下5个线程时。

var tasks = new Task[5];

此时将抛出异常,具体演示结果如下:

具体错误信息显示如下:

当 IDENTITY_INSERT 设置为 OFF 时,不能为表 'Blog' 中的标识列插入显式值。

当然这种情况不是一定会发生,有可能开启两个线程不会出现上述以上异常,有可能会抛出异常。为什么会出现上述异常呢,请看如下图。

当一个线程过来时,正常提交肯定是没问题,但是此时该插入的Blog已经被追踪,仅接着又来一个线程,此时Blog中的Id是上一个线程插入的值,所以会导致我们的Id本来主键是自动增长的,而此时Id却有了值出现上述异常。在项目中很难把握这样的情况,也尝试去修改实体的变更追踪的状态,结果依然出现上述问题,最终采用写SQL语句的方式来实现,如果有能够修改变更追踪解决的方案请在评论中给出。在我们项目中,利用SQL语句的方式来解决EF Core的并发,同时开启200个线程没有出任何问题,当然我们的逻辑也还算有一点复杂,所以不用担心EF Core的性能问题,我们更多的是关心业务逻辑。一直在思考怎么通过不写SQL语句的方式去解决这样的并发问题,我能够想到的是既然传过来的实体插入后会被变更追踪,那么我将传过来的参数再实例化一个对象,然后将参数传给它这样应该就能解决问题。

public void Create(Blog b)        {            var copyBlog = new Blog() { Name = b.Name, Url = b.Url };            try            {                using (var transaction = _efCoreContext.Database.BeginTransaction())                {                    _efCoreContext.Blogs.Add(copyBlog);                    _efCoreContext.SaveChanges();                    var posts = b.Posts.Select(d => new Post()                    {                        BlogId = copyBlog.Id,                        Content = d.Content,                        Title = d.Title                    });                    _efCoreContext.Set
().AddRange(posts); _efCoreContext.SaveChanges(); transaction.Commit(); } } catch (DbUpdateConcurrencyException ex) {...} }

此时我们开启200个线程来跑跑看看,此时测试通过,如下

我们再来看看数据库是否已经插入200条数据。

这个对于并发导致显式插入主键的问题比较另类的做法,如果有更好的方案请在评论区提出来。

总结 

本节我们讨论了有关EF Core中并发导致的问题,尚未找到更加可靠的方案,期待你阅读后给出最佳方案。

转载于:https://www.cnblogs.com/CreateMyself/p/6384125.html

你可能感兴趣的文章
面向对象的设计原则
查看>>
Swift3.0服务端开发(三) Mustache页面模板与日志记录
查看>>
【转】 FPGA设计的四种常用思想与技巧
查看>>
EntityFrameWork 实现实体类和DBContext分离在不同类库
查看>>
新手算法学习之路----二叉树(在一个二叉查找树中插入一个节点)
查看>>
autopep8
查看>>
GIT在Linux上的安装和使用简介
查看>>
基于C#编程语言的Mysql常用操作
查看>>
s3c2440实验---定时器
查看>>
MyEclipse10安装SVN插件
查看>>
[转]: 视图和表的区别和联系
查看>>
Regular Experssion
查看>>
图论例题1——NOIP2015信息传递
查看>>
uCOS-II中的任务切换-图解多种任务调度时机与问题
查看>>
CocoaPods的安装和使用那些事(Xcode 7.2,iOS 9.2,Swift)
查看>>
Android 官方新手指导教程
查看>>
幸运转盘v1.0 【附视频】我的Android原创处女作,请支持!
查看>>
UseIIS
查看>>
集合体系
查看>>
vi命令提示:Terminal too wide
查看>>