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