前言

之前讨论过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(, Add(, ));
} 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上才找到解决方案【https://github.com/xunit/xunit/issues/1031】,需要添加上述依赖包才可。

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

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

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

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

    public class BlogRepository : EntityBaseRepository<Blog>,
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<Post>() { new Post() { Title = "a", Content = "ss" } } }; var tasks = new Task[];
for (int i = ; 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[];

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

具体错误信息显示如下:

当 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<Post>().AddRange(posts);
_efCoreContext.SaveChanges();
transaction.Commit();
} }
catch (DbUpdateConcurrencyException ex)
{...}
}

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

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

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

总结

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

EntityFramework Core并发导致显式插入主键问题的更多相关文章

  1. EntityFramework Core并发导致显示插入主键问题

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

  2. EntityFramework Core并发深挖详解,一纸长文,你准备好看完了吗?

    前言 之前有关EF并发探讨过几次,但是呢,博主感觉还是有问题,为什么会觉得有问题,其实就是理解不够透彻罢了,于是在项目中都是用的存储过程或者SQL语句来实现,利用放假时间好好补补EF Core并发的问 ...

  3. java并发多线程显式锁Condition条件简介分析与监视器 多线程下篇(四)

    Lock接口提供了方法Condition newCondition();用于获取对应锁的条件,可以在这个条件对象上调用监视器方法 可以理解为,原本借助于synchronized关键字以及锁对象,配备了 ...

  4. java 并发多线程显式锁概念简介 什么是显式锁 多线程下篇(一)

    目前对于同步,仅仅介绍了一个关键字synchronized,可以用于保证线程同步的原子性.可见性.有序性 对于synchronized关键字,对于静态方法默认是以该类的class对象作为锁,对于实例方 ...

  5. MyBatis 插入主键方式和返回主键

    这使用的mysql数据库,下面这种方式(没有给mysql设置自动增长)是插入主键方式: <insert id="insertBook" parameterType=" ...

  6. 解决mybatisplus saveBatch 或者save 无法插入主键问题

    解决mybatisplus saveBatch 或者save 无法插入主键问题 通过跟踪源码后得出结论,由于插入的表的主键不是自增的,而是手动赋值的,所以在调用saveBatch 执行的sql语句是没 ...

  7. SQLServer 自增主键创建, 指定自增主键列值插入数据,插入主键

    http://blog.csdn.net/zh2qiang/article/details/5323981 SQLServer 中含自增主键的表,通常不能直接指定ID值插入,可以采用以下方法插入. 1 ...

  8. oracle插入主键数据、sequence和触发器

    一.创建表:   id number;并设为主键 name VARCHAR2(20 BYTE) 二. 插入数据 2.1 insert into addservice.test_table (id,na ...

  9. MyBatis_SelectKey使用oracle 序列插入主键

    mapper 如下: 使用<selectkey>实现 也可以使用oracle的row 级触发器trigger实现: <?xml version="1.0" enc ...

随机推荐

  1. 浅谈servlet与jsp的关系

    servlet是用java语言编写的,是一个java类.主要功能是用来接受.处理客户端的请求,并把处理结果返回到客户端显示.Jsp是servlet发展后期的产物.在没有jsp之前,servlet利用输 ...

  2. Android--使用JobService实现进程保活

    进程保活一直是广大APP开发者所希望的,因为进程活着我们就可以操作很多事情(推送,数据同步等等),但是google大大是不允许这样做的(优化),所以我们要另辟蹊径. 先来看看android中有几种进程 ...

  3. (python)数据结构---元组

    一.描述 一个有序的元素组成的集合 元组是不可变的线性数据结构 二.元组的相关操作 1.元组元素的访问 索引不可超界,否则抛异常IndexError 支持正负索引 t = (2, 3) print(t ...

  4. 第二章 基础查询 2-1 SQL语句基础

    一.列的查询 基本的SELECT语句: SELECT <列名 >,...... FROM < 表名>; 注:子句是SQL的组成要素. 注:查询结果中的列的顺序和SELECT子句 ...

  5. 自动化测试基础篇--Selenium鼠标键盘事件

    摘自https://www.cnblogs.com/sanzangTst/p/7477382.html 前面几篇文章我们学习了怎么定位元素,同时通过实例也展示了怎么切换到iframe,怎么输入用户名和 ...

  6. C#从http上拿返回JSON数据

    C#如何拿到从http上返回JSON数据? 第一章:C#如何拿到从http上返回JSON数据? 第二章:C#如何解析JSON数据?(反序列化对象) 第三章:C#如何生成JSON字符串?(序列化对象) ...

  7. 深入理解C语言内存管理

    之前在学Java的时候对于Java虚拟机中的内存分布有一定的了解,但是最近在看一些C,发现居然自己对于C语言的内存分配了解的太少. 问题不能拖,我这就来学习一下吧,争取一次搞定. 在任何程序设计环境及 ...

  8. 基数排序python实现

    基数排序python实现 基数排序 基数排序(英语:Radix sort)是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较.由于整数也可以表达字符串(比如名字或 ...

  9. FastCGI Error Number: 5 (0x80070005).

    在访问网站的时候,出现了以上这个错误: 在网上搜了很多方法,归纳起来就如下几种: 1, 网站安全狗]的安全策略问题 解决方案: 主动防御/禁止IIS执行程序 添加"php\php-cgi.e ...

  10. shell 关于字符切割 cut

    shell中运用于字符切割的是cut 其中 参数分别代表得意义是: -d :后面接分隔字符,将一段信息分割为数段,与 -f 一起使用 -f :将-d分割出来的数段 用 -f 取出第几段的意思 -c : ...