EntityFramework Core並發導致顯式插入主鍵問題


前言

之前討論過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上才找到解決方案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[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<Post>().AddRange(posts);
                    _efCoreContext.SaveChanges();
                    transaction.Commit();
                }

            }
            catch (DbUpdateConcurrencyException ex)
            {...}
       }

此時我們開啟200個線程來跑跑看看,此時測試通過,如下

我們再來看看數據庫是否已經插入200條數據。

這個對於並發導致顯式插入主鍵的問題比較另類的做法,如果有更好的方案請在評論區提出來。

總結 

本節我們討論了有關EF Core中並發導致的問題,尚未找到更加可靠的方案,期待你閱讀后給出最佳方案。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM