别跟我谈EF抵抗并发,敢问你到底会不会用EntityFramework
前言
一直以來寫的博文都是比較溫婉型的博文,今天這篇博文算是一篇批判性博文,有問題歡迎探討,如標題,你到底會不會用EntityFramework啊。
你到底會不會用EntityFramework啊
面試過三年至六年的同行,作為過面試者到如今作為面試官也算是老大對我的信任,對來面試的面試者的任何一位同行絕沒有刁難之意,若還裝逼那就沒有什么意義。我也基本不看面試者的項目經(jīng)歷,因為我個人覺得每個面試者所在公司所做項目都不一樣,可能面試者項目所做的業(yè)務(wù)我一點都不知道,而我所關(guān)心的是項目當中所用到的技術(shù),稍微看下了簡歷讓面試者簡單做個自我介紹,這算是基本流程吧。然后直接問面試者最擅長的技術(shù)是哪些?比如ASP.NET MVC、比如ASP.NET Web APi、比如EntityFramework,再比如數(shù)據(jù)庫等等。如果面試者沒有特別擅長的技術(shù)那我就簡歷上提出所熟悉和項目當中用到的技術(shù)進行提問。這里暫且不提其他技術(shù),單單說EntityFramework,面試的面試者大部分都有用過EntityFramework,我就簡單問了下,比如您用的EntityFramework版本是多少?答案是不知道,這個我理解,可能沒去關(guān)心過這個問題,再比如我問您知道EntityFramework中有哪些繼承策略,然后面試者要么是一臉懵逼,要么是不知道,要么回了句我們不用。這個我也能理解,重點來了,我問您在EntityFramwork中對于批量添加操作是怎么做的,無一例外遍歷循環(huán)一個一個添加到上下文中去,結(jié)果令我驚呆了,或許是只關(guān)注于實現(xiàn),很多開發(fā)者只關(guān)注這個能實現(xiàn)就好了,這里不過多探討這個問題,每個人觀點不一樣。
大部分人用EntityFramework時出現(xiàn)了問題,就吐槽EntityFramework啥玩意啊,啥ORM框架啊,各種問題,我只能說您根本不會用EntityFramework,甚至還有些人并發(fā)測試EntityFramework的性能,是的,沒錯,EntityFramework性能不咋的(這里我們只討論EF 6.x),或者說在您實際項目當中有了點并發(fā)發(fā)現(xiàn)EF出了問題,又開始抱怨EF不行了,同時對于輕量級、跨平臺、可擴展的EF Core性能秒殺EF,即使你并發(fā)測試EF Core性能也就那么回事,我想說的是你并發(fā)測試EF根本沒有任何意義,請好生理解EF作為ORM框架出現(xiàn)的意義是什么,不就是為了讓我們關(guān)注業(yè)務(wù)么,梳理好業(yè)務(wù)對象,在EF中用上下文操作對象就像直接操作表一樣。然后我們回到EF抵抗并發(fā)的問題,有的童鞋認為EF中給我提供了并發(fā)Token和行版本以及還有事務(wù),這不就是為了并發(fā)么,童鞋對于并發(fā)Token和行版本這是對于少量的請求可能存在的并發(fā)EF團隊提出的基本解決方案,對于事務(wù)無論是同一上文抑或是跨上下文也好只是為了保證數(shù)據(jù)一致性罷了。要是大一點的并發(fā)來了,您難道還讓EF不顧一切沖上去么,這無疑是飛蛾撲火自取滅亡,你到底會不會用EntityFramework啊。EF作為概念上的數(shù)據(jù)訪問層應(yīng)該是處于最底層,如果我們項目可預(yù)見沒有所謂的并發(fā)問題,將上下文直接置于最上層比如控制器中并沒有什么問題,但是項目比較大,隨著用戶量的增加,我們肯定是可預(yù)知的,這個我們需要從項目架構(gòu)層面去考慮,此時在上下文上游必定還有其他比如C#中的并發(fā)隊列或者Redis來進行攔截使其串行進行。
有些人號稱是對EntityFramwork非常了解,認為不就是增、刪、該、查么,但是有的時候用出了問題就開始自我開解,我這么用沒有任何問題啊,我們都知道在EF 6.x中確實有很多坑,這個時候就借這個緣由洗白了,這不是我的鍋,結(jié)果EF背上了無名之鍋,妄名之冤。是的,您們沒有說錯,EF 6.x是有很多坑,您避開這些坑不就得了,我只能說這些人太浮于表面不了解基本原理就妄下結(jié)論,你到底會不會用EntityFramework啊。好了來,免說我紙上談兵,我來舉兩個具體例子,您們看你到底會不會用。
EntityFramework 6.x查詢
static void Main(string[] args)
? ? ? ? {
? ? ? ? ? ? using (var ctx = new EfDbContext())
? ? ? ? ? ? {
? ? ? ? ? ? ? ? ctx.Database.Log = Console.WriteLine;
? ? ? ? ? ? ? ? var code = "Jeffcky";
? ? ? ? ? ? ? ? var order = ctx.Orders.FirstOrDefault(d => d.Code == code);? ? ? ? ? ?
? ? ? ? ? ? };
? ? ? ? ? ? Console.ReadKey();
? ? ? ? }
這樣的例子用過EF 6.x的童鞋估計用爛了吧,然后查詢出來的結(jié)果讓我們也非常滿意至少是達到了我們的預(yù)期,我們來看看生成的SQL語句。
?
請問用EF的您們發(fā)現(xiàn)什么沒有,在WHERE查詢條件加上了一堆沒有用的東西,我只是查詢Code等于Jeffcky的實體數(shù)據(jù),從生成的SQL來看可查詢Code等于Jeffcky的也可查詢Code等于空的數(shù)據(jù),要是我們?nèi)缦虏樵?#xff0c;生成如上SQL語句我覺得才是我們所預(yù)期的對不對。
using (var ctx = new EfDbContext())
? ? ? ? ? ? {
? ? ? ? ? ? ? ? ctx.Database.Log = Console.WriteLine;
? ? ? ? ? ? ? ? var code = "Jeffcky";
? ? ? ? ? ? ? ? var orders = ctx.Orders.Where(d => d.Code == null || d.Code == code).ToList();
? ? ? ? ? ? };
如果您真的會那么一點點用EntityFramework,那么請至少了解背后生成的SQL語句吧,這是其中之一,那要是我們直接使用值查詢呢,您們覺得是否和利用參數(shù)生成的SQL語句是一樣的呢?
using (var ctx = new EfDbContext())
? ? ? ? ? ? {
? ? ? ? ? ? ? ? ctx.Database.Log = Console.WriteLine;
? ? ? ? ? ? ? ? var order = ctx.Orders.FirstOrDefault(d => d.Code == "Jeffcky");
? ? ? ? ? ? };
出乎意料吧,利用值查詢在WHERE條件上沒有過多的條件過濾,而利用參數(shù)查詢則是生成過多的條件篩選,到這里是不是就到此為止了呢,如果您們對于參數(shù)查詢不想生成對空值的過濾,我們在上下文構(gòu)造函數(shù)中可關(guān)閉這種所謂【語義可空】判斷,如下:
public class EfDbContext : DbContext
? ? {
? ? ? ? public EfDbContext() : base("name=ConnectionString")
? ? ? ? {
? ? ? ? ? ? Configuration.UseDatabaseNullSemantics = true;
? ? ? ? }
? ? ?}
// 摘要:
// 獲取或設(shè)置一個值,該值指示當比較兩個操作數(shù),而它們都可能為 null 時,是否展示數(shù)據(jù)庫 null 語義。默認值為 false。例如:如果 UseDatabaseNullSemantics
// 為 true,則 (operand1 == operand2) 將轉(zhuǎn)換為 (operand1 = operand2);如果 UseDatabaseNullSemantics
// 為 false,則將轉(zhuǎn)換為 (((operand1 = operand2) AND (NOT (operand1 IS NULL OR operand2
// IS NULL))) OR ((operand1 IS NULL) AND (operand2 IS NULL)))。
//
// 返回結(jié)果:
// 如果啟用數(shù)據(jù)庫 null 比較行為,則為 true;否則為 false。
在EF 6.x中對于查詢默認情況下會進行【語義可空】篩選,通過如上分析,不知您們是否知道如上的配置呢。
EntityFramework 6.x更新
EF 6.x更新操作又是用熟透了吧,在EF中沒有Update方法,而在EF Core中存在Update和UpdateRange方法,您們是否覺得更新又是如此之簡單呢?我們下面來首先看一個例子,看看您們是否真的會用。
static Customer GetCustomer()
? ? ? ? {
? ? ? ? ? ? var customer = new Customer()
? ? ? ? ? ? {
? ? ? ? ? ? ? ? Id = 2,
? ? ? ? ? ? ? ? CreatedTime = DateTime.Now,
? ? ? ? ? ? ? ? ModifiedTime = DateTime.Now,
? ? ? ? ? ? ? ? Email = "2752154844@qq.com",
? ? ? ? ? ? ? ? Name = "Jeffcky1"
? ? ? ? ? ? };
? ? ? ? ? ? return customer;
? ? ? ? }
如上實體如我們請求傳到后臺需要修改的實體(假設(shè)該實體在數(shù)據(jù)庫中存在哈),這里我們進行寫死模擬。接下來我們來進行如下查詢,您們思考一下是否能正常更新呢?
using (var ctx = new EfDbContext())
? ? ? ? ? ? {
? ? ? ? ? ? ? ? var customer = GetCustomer();
? ? ? ? ? ? ? ? var dataBaseCustomer = ctx.Customers.FirstOrDefault(d => d.Id == customer.Id);
? ? ? ? ? ? ? ? if (dataBaseCustomer != null)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ctx.Customers.Attach(customer);
? ? ? ? ? ? ? ? ? ? ctx.Entry(customer).State = EntityState.Modified;
? ? ? ? ? ? ? ? ? ? if (ctx.SaveChanges() > 0)
? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? Console.WriteLine("更新成功");
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? else
? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? Console.WriteLine("更新失敗");
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? };
首先我們根據(jù)傳過來的實體主鍵去數(shù)據(jù)庫中查詢是否存在,若存在則將傳過來的實體附加到上下文中(因為此時請求過來的實體還未被跟蹤),然后將其狀態(tài)修改為已被修改,最后提交,解釋的是不是非常合情合理且合法,那是不是就打印更新成功了呢?
看到上述錯誤想必有部分童鞋一下子就明白問題出在哪里,當我們根據(jù)傳過來的實體主鍵去數(shù)據(jù)庫查詢,此時在數(shù)據(jù)庫中存在就已被上下文所跟蹤,然后我們又去附加已傳過來的實體且修改狀態(tài),當然會出錯因為在上下文已存在相同的對象,此時必然會產(chǎn)生已存在主鍵沖突。有的童鞋想了直接將傳過來的實體狀態(tài)修改為已修改不就得了么,如下:
using (var ctx = new EfDbContext())
? ? ? ? ? ? {
? ? ? ? ? ? ? ? var customer = GetCustomer();
? ? ? ? ? ? ? ? ctx.Entry(customer).State = EntityState.Modified;
? ? ? ? ? ? ? ? if (ctx.SaveChanges() > 0)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? Console.WriteLine("更新成功");
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? else
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? Console.WriteLine("更新失敗");
? ? ? ? ? ? ? ? }
? ? ? ? ? ? };
如此肯定能更新成功了,我想都不會這么干吧,要是客戶端進行傳過來的主鍵在數(shù)據(jù)庫中不存在呢(至少我們得保證數(shù)據(jù)是已存在才修改),此時進行如上操作將拋出如下異常。
此時為了解決這樣的問題最簡單的方法之一則是在查詢實體是否存在時直接通過AsNoTracking方法使其不能被上下文所跟蹤,這樣就不會出現(xiàn)主鍵沖突的問題。
var dataBaseCustomer = ctx.Customers.AsNoTracking().FirstOrDefault(d => d.Id == customer.Id);我們繼續(xù)往下探討 ,此時我們將數(shù)據(jù)庫Email修改為可空(映射也要對應(yīng)為可空,否則拋出驗證不通過的異常,你懂的),如下圖:
?
然后將前臺傳過來的實體進行如下修改,不修改Email,我們注釋掉。
static Customer GetCustomer()
? ? ? ? {
? ? ? ? ? ? var customer = new Customer()
? ? ? ? ? ? {
? ? ? ? ? ? ? ? Id = 2,
? ? ? ? ? ? ? ? CreatedTime = DateTime.Now,
? ? ? ? ? ? ? ? ModifiedTime = DateTime.Now,
? ? ? ? ? ? ? ? //Email = "2752154844@qq.com",
? ? ? ? ? ? ? ? Name = "Jeffcky1"
? ? ? ? ? ? };
? ? ? ? ? ? return customer;
? ? ? ? }
我們接著再來進行如下查詢試試看。
using (var ctx = new EfDbContext())
? ? ? ? ? ? {
? ? ? ? ? ? ? ? var customer = GetCustomer();
? ? ? ? ? ? ? ? var dataBaseCustomer = ctx.Customers
? ? ? ? ? ? ? ? ? ? .AsNoTracking()
? ? ? ? ? ? ? ? ? ? .FirstOrDefault(d => d.Id == customer.Id);
? ? ? ? ? ? ? ? if (dataBaseCustomer != null)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ctx.Customers.Attach(customer);
? ? ? ? ? ? ? ? ? ? ctx.Entry(customer).State = EntityState.Modified;
? ? ? ? ? ? ? ? ? ? if (ctx.SaveChanges() > 0)
? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? Console.WriteLine("更新成功");
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? else
? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? Console.WriteLine("更新失敗");
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? };
按 Ctrl+C 復(fù)制代碼
此時Email為可空,因為我們設(shè)置實體狀態(tài)為Modified,此時將對實體進行全盤更新,所以對于設(shè)置實體狀態(tài)為Modified是針對所有列更新,要是我們只想更新指定列,那這個就不好使了,此時我們可通過Entry().Property()...來手動更新指定列,比如如下:
using (var ctx = new EfDbContext())
? ? ? ? ? ? {
? ? ? ? ? ? ? ? var customer = GetCustomer();
? ? ? ? ? ? ? ? var dataBaseCustomer = ctx.Customers
? ? ? ? ? ? ? ? ? ? .AsNoTracking()
? ? ? ? ? ? ? ? ? ? .FirstOrDefault(d => d.Id == customer.Id);
? ? ? ? ? ? ? ? if (dataBaseCustomer != null)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ctx.Customers.Attach(customer);
? ? ? ? ? ? ? ? ? ? ctx.Entry(customer).Property(p => p.Name).IsModified = true;
? ? ? ? ? ? ? ? ? ? ctx.Entry(customer).Property(p => p.Email).IsModified = true;
? ? ? ? ? ? ? ? ? ? if (ctx.SaveChanges() > 0)
? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? Console.WriteLine("更新成功");
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? else
? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? Console.WriteLine("更新失敗");
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? };
我們繼續(xù)往下走。除了上述利用AsNoTracking方法外使其查詢出來的實體未被上下文跟蹤而成功更新,我們還可以使用手動賦值的方式更新數(shù)據(jù),如下:
using (var ctx = new EfDbContext())
? ? ? ? ? ? {
? ? ? ? ? ? ? ? var customer = GetCustomer();
? ? ? ? ? ? ? ? var dataBaseCustomer = ctx.Customers
? ? ? ? ? ? ? ? ? ? .FirstOrDefault(d => d.Id == customer.Id);
? ? ? ? ? ? ? ? if (dataBaseCustomer != null)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? dataBaseCustomer.CreatedTime = customer.CreatedTime;
? ? ? ? ? ? ? ? ? ? dataBaseCustomer.ModifiedTime = customer.ModifiedTime;
? ? ? ? ? ? ? ? ? ? dataBaseCustomer.Email = customer.Email;
? ? ? ? ? ? ? ? ? ? dataBaseCustomer.Name = customer.Name;
? ? ? ? ? ? ? ? ? ? if (ctx.SaveChanges() > 0)
? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? Console.WriteLine("更新成功");
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? else
? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? Console.WriteLine("更新失敗");
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? };
如上也能更新成功而不用將查詢出來的實體未跟蹤,然后將前臺傳過來的實體進行附加以及修改狀態(tài),下面我們刪除數(shù)據(jù)庫中創(chuàng)建時間和修改時間列,此時我們保持數(shù)據(jù)庫中數(shù)據(jù)和從前臺傳過來的數(shù)據(jù)一模一樣,如下:
static Customer GetCustomer()
? ? ? ? {
? ? ? ? ? ? var customer = new Customer()
? ? ? ? ? ? {
? ? ? ? ? ? ? ? Id = 2,
? ? ? ? ? ? ? ? Email = "2752154844@qq.com",
? ? ? ? ? ? ? ? Name = "Jeffcky1"
? ? ? ? ? ? };
? ? ? ? ? ? return customer;
? ? ? ? }
接下來我們再來進行如下賦值修改,你會發(fā)現(xiàn)此時更新失敗的:
using (var ctx = new EfDbContext())
? ? ? ? ? ? {
? ? ? ? ? ? ? ? var customer = GetCustomer();
? ? ? ? ? ? ? ? var dataBaseCustomer = ctx.Customers
? ? ? ? ? ? ? ? ? ? .FirstOrDefault(d => d.Id == customer.Id);
? ? ? ? ? ? ? ? if (dataBaseCustomer != null)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? dataBaseCustomer.Email = customer.Email;
? ? ? ? ? ? ? ? ? ? dataBaseCustomer.Name = customer.Name;
? ? ? ? ? ? ? ? ? ? if (ctx.SaveChanges() > 0)
? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? Console.WriteLine("更新成功");
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? else
? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? Console.WriteLine("更新失敗");
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? };
這是為何呢?因為數(shù)據(jù)庫數(shù)據(jù)和前臺傳過來的數(shù)據(jù)一模一樣,但是不會進行更新,毫無疑問EF這樣處理是明智且正確的,無需多此一舉更新,那我們怎么知道是否有不一樣的數(shù)據(jù)進行更新操作呢,換句話說EF怎樣知道數(shù)據(jù)未發(fā)生改變就不更新呢?我們可以用上下文屬性中的ChangeTacker中的HasChanges方法,如果上下文知道數(shù)據(jù)未發(fā)生改變,那么直接返回成功,如下:
using (var ctx = new EfDbContext())
? ? ? ? ? ? {
? ? ? ? ? ? ? ? var customer = GetCustomer();
? ? ? ? ? ? ? ? var dataBaseCustomer = ctx.Customers
? ? ? ? ? ? ? ? ? ? .FirstOrDefault(d => d.Id == customer.Id);
? ? ? ? ? ? ? ? if (dataBaseCustomer != null)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? dataBaseCustomer.Email = customer.Email;
? ? ? ? ? ? ? ? ? ? dataBaseCustomer.Name = customer.Name;
? ? ? ? ? ? ? ? ? ? if (!ctx.ChangeTracker.HasChanges())
? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? Console.WriteLine("更新成功");
? ? ? ? ? ? ? ? ? ? ? ? return;
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? if (ctx.SaveChanges() > 0)
? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? Console.WriteLine("更新成功");
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? else
? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? Console.WriteLine("更新失敗");
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? };
好了到此為止我們已經(jīng)看到關(guān)于更新已經(jīng)有了三種方式,別著急還有最后一種,通過Entry().CurrentValues.SetValues()方式,這種方式也是指定更新,將當前實體的值設(shè)置數(shù)據(jù)庫中查詢出來所被跟蹤的實體的值。如下:
using (var ctx = new EfDbContext())
? ? ? ? ? ? {
? ? ? ? ? ? ? ? var customer = GetCustomer();
? ? ? ? ? ? ? ? var dataBaseCustomer = ctx.Customers
? ? ? ? ? ? ? ? ? ? .FirstOrDefault(d => d.Id == customer.Id);
? ? ? ? ? ? ? ? if (dataBaseCustomer != null)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ctx.Entry(dataBaseCustomer).CurrentValues.SetValues(customer);
? ? ? ? ? ? ? ? ? ? if (ctx.SaveChanges() > 0)
? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? Console.WriteLine("更新成功");
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? else
? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? Console.WriteLine("更新失敗");
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? };
關(guān)于EF更新方式講了四種,其中有關(guān)細枝末節(jié)就沒有再細說可自行私下測試,不知道用過EF的您們是否四種都知道以及每一種對應(yīng)的場景是怎樣的呢?對于數(shù)據(jù)更新我一般直接通過查詢進行賦值的形式,當然我們也可以用AutoMapper,然后通過HasChanges方法來進行判斷。
EntityFramework 6.x批量添加
對于批量添加已經(jīng)是EF 6.x中老掉牙的話題,但是依然有很多面試者不知道,我這里再重新講解一次,對于那些私下不學(xué)習(xí),不與時俱進的童鞋好歹也看看前輩們(不包括我)總經(jīng)的經(jīng)驗吧,不知道為何這樣做,至少回答答案是對的吧。看到下面的批量添加數(shù)據(jù)代碼是不是有點想打人。
using (var ctx = new EfDbContext())
? ? ? ? ? ? {
? ? ? ? ? ? ? ? for (var i = 0; i <= 100000; i++)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? var customer = new Customer
? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? Email = "2752154844@qq.com",
? ? ? ? ? ? ? ? ? ? ? ? Name = i.ToString()
? ? ? ? ? ? ? ? ? ? };
? ? ? ? ? ? ? ? ? ? ctx.Customers.Add(customer);
? ? ? ? ? ? ? ? ? ? ctx.SaveChanges();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? };
至于原因無需我過多解釋,如果您這樣操作,那您這一天的工作大概也就是等著數(shù)據(jù)添加完畢,等啊等。再不濟您也將SaveChanges放在最外層一次性提交啊,這里我就不再測試,浪費時間在這上面沒必要,只要您稍微懂點EF原理至少會如下這么使用。
var customers = new List<Customer>();
? ? ? ? ? ? using (var ctx = new EfDbContext())
? ? ? ? ? ? {
? ? ? ? ? ? ? ? for (var i = 0; i <= 100000; i++)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? var customer = new Customer
? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? Email = "2752154844@qq.com",
? ? ? ? ? ? ? ? ? ? ? ? Name = i.ToString()
? ? ? ? ? ? ? ? ? ? };
? ? ? ? ? ? ? ? ? ? customers.Add(customer);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ctx.Customers.AddRange(customers);
? ? ? ? ? ? ? ? ctx.SaveChanges();
? ? ? ? ? ? };
如果您給我的答案如上,我還是認可的,要是第一種真的說不過去了啊。經(jīng)過如上操作依然有問題,我們將所有記錄添加到同一上下文實例,這意味著EF會跟蹤這十萬條記錄, 對于剛開始添加的幾個記錄,會運行得很快,但是當越到后面到達到十萬時,EF正在追蹤更大的對象圖,您們覺得恐怖不,這就是您不懂EF原理的代價,還對其進行詬病,吐槽性能可以,至少保證你寫的代碼沒問題吧,我們進一步優(yōu)化需要關(guān)閉自調(diào)用的DetectChanges方法無需進行對每一個添加的實體進行掃描。
?
var customers = new List<Customer>();
? ? ? ? ? ? using (var ctx = new EfDbContext())
? ? ? ? ? ? {
? ? ? ? ? ? ? ? bool acd = ctx.Configuration.AutoDetectChangesEnabled;
? ? ? ? ? ? ? ? try
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? for (var i = 0; i <= 100000; i++)
? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? var customer = new Customer
? ? ? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? ? ? Email = "2752154844@qq.com",
? ? ? ? ? ? ? ? ? ? ? ? ? ? Name = i.ToString()
? ? ? ? ? ? ? ? ? ? ? ? };
? ? ? ? ? ? ? ? ? ? ? ? customers.Add(customer);
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ctx.Customers.AddRange(customers);
? ? ? ? ? ? ? ? ? ? ctx.SaveChanges();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? finally
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ctx.Configuration.AutoDetectChangesEnabled = acd;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? };
此時我們通過局部關(guān)閉自調(diào)用DetectChanges方法,此時EF不會跟蹤實體,這樣將不會造成全盤掃描而使得我們不會處于漫長的等待,如此優(yōu)化將節(jié)省大量時間。如果在我們了解原理的前提下知道添加數(shù)據(jù)到EF上下文中,隨著數(shù)據(jù)添加到集合中也會對已添加的數(shù)據(jù)進行全盤掃描,那我們何不創(chuàng)建不同的上下文進行批量添加呢?未經(jīng)測試在這種情況下是否比關(guān)閉自調(diào)用DetectChanges方法效率更高,僅供參考,代碼如下:
?
public static class EFContextExtensions
? ? {
? ? ? ? public static EfDbContext BatchInsert<T>(this EfDbContext context, T entity, int count, int batchSize) where T : class
? ? ? ? {
? ? ? ? ? ? context.Set<T>().Add(entity);
? ? ? ? ? ? if (count % batchSize == 0)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? context.SaveChanges();
? ? ? ? ? ? ? ? context.Dispose();
? ? ? ? ? ? ? ? context = new EfDbContext();
? ? ? ? ? ? }
? ? ? ? ? ? return context;
? ? ? ? }
? ? }
static void Main(string[] args)
? ? ? ? {
? ? ? ? ? ? var customers = new List<Customer>();
? ? ? ? ? ? EfDbContext ctx;
? ? ? ? ? ? using (ctx = new EfDbContext())
? ? ? ? ? ? {
? ? ? ? ? ? ? ? for (var i = 0; i <= 100000; i++)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? var customer = new Customer
? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? Email = "2752154844@qq.com",
? ? ? ? ? ? ? ? ? ? ? ? Name = i.ToString()
? ? ? ? ? ? ? ? ? ? };
? ? ? ? ? ? ? ? ? ? ctx = ctx.BatchInsert(customer, i, 100);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ctx.SaveChanges();
? ? ? ? ? ? };
? ? ? ? ? ? Console.ReadKey();
? ? ? ? }
總結(jié)
不喜勿噴,敢問您們到底會不會用EntityFramework啊,EF 6.x性能令人詬病但是至少得保證您們寫的代碼沒問題吧,您們可結(jié)合Dapper使用啊,擔心EF 6.x性能不行,那請用EntityFramework Core吧,你值得擁有。謹以此篇批判那些不會用EF的同行,還將EF和并發(fā)扯到一塊,EF不是用來抵抗并發(fā),它的出現(xiàn)是為了讓我將重心放在梳理業(yè)務(wù)對象,關(guān)注業(yè)務(wù)上,有關(guān)我對EF 6.x和EF Core 2.0理解全部集成到我寫的書《你必須掌握的EntityFramework 6.x與Core 2.0》最近即將出版,想了解的同行可關(guān)注下,謝謝。
原文地址: https://www.cnblogs.com/CreateMyself/p/8989983.html
.NET社區(qū)新聞,深度好文,歡迎訪問公眾號文章匯總 http://www.csharpkit.com
總結(jié)
以上是生活随笔為你收集整理的别跟我谈EF抵抗并发,敢问你到底会不会用EntityFramework的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: .net core DI 注册 Lazy
- 下一篇: 《你必须掌握的Entity Framew