日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

efcore技巧贴-也许有你不知道的使用技巧

發(fā)布時(shí)間:2023/12/4 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 efcore技巧贴-也许有你不知道的使用技巧 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

前言

.net 環(huán)境近些年也算是穩(wěn)步發(fā)展。在開發(fā)的過程中,與數(shù)據(jù)庫打交道是必不可少的。早期的開發(fā)者都是DbHelper一擼到底,到現(xiàn)在的各種各樣的ORM框架大行其道。孰優(yōu)孰劣誰也說不清楚,文無第一武無第二說的就是這個(gè)理。沒有什么最好的,只有最適合你的。

本人也是從DbHelper開始,期間用過SugarSql,再到EFCODE。本著學(xué)習(xí)分享的初衷分享本人工作中總結(jié)的一些小技巧,希望能幫助更多開發(fā)者,期望能達(dá)到共同進(jìn)步。文中若有錯(cuò)誤地方,歡迎大家不吝賜教。

1. DbContext配置

在asp.net中,通常情況下,通過在Startup類的ConfigureServices方法中,將ef服務(wù)注入。

示例代碼如下:

services.AddDbContext<DemoDbContext>(opt=>opt.UseMySql("server=.;Database=demo;Uid=root;Pwd=123;Port=3306;"));

以上代碼表示使用MySql數(shù)據(jù)庫。如果使用SqlServer數(shù)據(jù)庫,可以把UseMySql改為UseSqlServer,其他數(shù)據(jù)庫的使用方式也是通過調(diào)用不同的方法進(jìn)行選擇。但需要安裝對應(yīng)的擴(kuò)展方法的程序包,如 Microsoft.EntityFrameworkCore.SqlServer 或 Microsoft.EntityFrameworkCore.Sqlite。

另外,UseMySql方法還包含了一個(gè)可空的Action <MySqlDbContextOptionsBuilder>類型的參數(shù),可以通過此參數(shù)進(jìn)行一些個(gè)性化的配置,比如配置重試機(jī)制。如下所示:

services.AddDbContext<DemoDbContext>(opt => opt.UseMySql("server=.;Database=demo;Uid=root;Pwd=123456;Port=3306;",provideropt => provideropt.EnableRetryOnFailure(3,TimeSpan.FromSeconds(10),new List<int>(){0} )));

這個(gè)重試機(jī)制在某些場景下還是比較有用的。比如,由于網(wǎng)絡(luò)波動或訪問量導(dǎo)致的一瞬間的連接超時(shí)。如果不設(shè)置重試機(jī)制,則會直接觸發(fā)異常,設(shè)置了超時(shí)后,則會根據(jù)設(shè)置的時(shí)間間隔以及重試次數(shù)進(jìn)行重試。EnableRetryOnFailure方法的最后一個(gè)參數(shù)是用來設(shè)置錯(cuò)誤代碼的,只有設(shè)置了錯(cuò)誤代碼的錯(cuò)誤,才會觸發(fā)重試。獲取錯(cuò)誤代碼的方法有很多種,個(gè)人比較推薦的是,通過異常信息進(jìn)行獲取,比如,使用MySql數(shù)據(jù)時(shí),觸發(fā)的異常類型是MySqlException,此類的Number屬性的值EnableRetryOnFailure方法所需要的Number

2. DbContext線程問題

efcore不支持在同一個(gè)DbContext實(shí)例上運(yùn)行多個(gè)并行操作,這包括異步查詢的并行執(zhí)行以及從多個(gè)線程進(jìn)行的任何顯式并發(fā)使用。因此,始終 await 異步調(diào)用,或?qū)Σ⑿袌?zhí)行的操作使用單獨(dú)的 DbContext 實(shí)例。

當(dāng) EF Core 檢測到并行操作或多個(gè)線程同時(shí)嘗試使用 DbContext 實(shí)例時(shí),你將看到一條 InvalidOperationException,其中包含類似于下面的消息:

A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.

意思是,在上一個(gè)操作沒有執(zhí)行完畢之前,又啟動了一個(gè)新的操作,所以不能保證線程是安全的。

下面是一段錯(cuò)誤的,可以觸發(fā)這個(gè)異常的示例代碼:

所以,請始終await異步調(diào)用。如果在多個(gè)多個(gè)線程中使用DbContext,需保證每個(gè)線程的DbContext的實(shí)例是唯一的。

3. 數(shù)據(jù)庫使用連接池

使用 services.AddDbContextPool比使用 services.AddDbContext吞吐量提升在10~20的百分點(diǎn)(非官方說法,對性能提高數(shù)據(jù)是本人測試后得到的結(jié)果)。

需要注意的是,連接池大小并不是越大越好。

4. 日志記錄

在使用ef時(shí),基本上絕大多數(shù)和數(shù)據(jù)庫的交互都是通過linq實(shí)現(xiàn)的,然后ef將linq翻譯成對應(yīng)的sql語句,在排查問題的時(shí)候,在開發(fā)或者排查問題時(shí),往往需要關(guān)注最終執(zhí)行的sql腳本,所以就需要通過日志的方式查看。

efcore2.x的版本默認(rèn)是注入日志服務(wù),所以不需要額外的操作,就可以查看對應(yīng)的sql腳本。但efcore3.x的版本默認(rèn)移除了日志服務(wù),具體原因參照:https://docs.microsoft.com/zh-cn/ef/core/what-is-new/ef-core-3.0/breaking-changes#adddbc。

可通過自定義DbContext的方式注入日志任務(wù),示例代碼如下:

public static readonly ILoggerFactory MyLoggerFactory= LoggerFactory.Create(builder => { builder.AddConsole(); }); protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {base.OnConfiguring(optionsBuilder);optionsBuilder.UseLoggerFactory(MyLoggerFactory); }

當(dāng)執(zhí)行ef代碼時(shí),可在控制臺中查看相關(guān)的sql腳本,如下圖所示:

5. 增

插入數(shù)據(jù)到數(shù)據(jù)庫常用的場景有:普通單表單行插入,多表級聯(lián)插入,批量插入。

普通單表單行插入比較簡單,實(shí)例代碼如下:

var student = new Student {CreateTime = DateTime.Now, Name = "zjjjjjj"}; await _context.Students.AddAsync(student); await _context.SaveChangesAsync();

多表級聯(lián)插入,需要在實(shí)體映射中配置屬性導(dǎo)航。

比如Blog表和Post是的關(guān)系是1對多的關(guān)系。則在Blog的實(shí)體中,定義一個(gè)類型為List <Post>的屬性。示例代碼如下:

[Table("blog")] public class Blog {[Column("id")]public long Id { get; set; }[Column("title")]public string Title { get; set; }public List<Post> Posts { get; set; }[Column("create_date")]public DateTime CreateDate { get; set; } }

對應(yīng)的插入語句如下所示:

var blog = new Blog {Title = "測試標(biāo)題",Posts = new List<Post>{new Post{Content = "評論1"},new Post{Content = "評論2"},new Post{Content = "評論3"},} }; await _context.Blog.AddAsync(blog); await _context.SaveChangesAsync();

執(zhí)行此代碼,會生成如下的日志:

從日志中可以看出,通過這種方式實(shí)現(xiàn)了級聯(lián)插入的效果。

批量插入實(shí)現(xiàn)方式有兩種,一種是EF默認(rèn)實(shí)現(xiàn),適用于數(shù)據(jù)源較少的情況。另一種,我們基于EF開發(fā)一個(gè)大數(shù)據(jù)量批量插入的服務(wù),適合于數(shù)據(jù)源大于1000的場景。在萬級及以上的數(shù)據(jù)量上,較EF默認(rèn)的批量插入性能上有非常明顯的提升。具體參考:https://www.cnblogs.com/fulu/p/13370335.html

EF默認(rèn)實(shí)現(xiàn):

var list = new List<Student>(); for (int i = 0; i < num; i++) {list.Add(new Student { CreateTime = DateTime.Now, Name = "zjjjjjj" }); } await _context.Students.AddRangeAsync(list); await _context.SaveChangesAsync();

ISqlBulk實(shí)現(xiàn):

var list = new List<Student>(); for (int i = 0; i < 100000; i++) {list.Add(new Student { CreateTime = DateTime.Now, Name = "zjjjjjj" }); } await _bulk.InsertAsync(list);

自增 OR GUID

int自增的優(yōu)點(diǎn):

1、需要很小的數(shù)據(jù)存儲空間,僅僅需要4 byte 。

2、insert和update操作時(shí)使用INT的性能比GUID好,所以使用int將會提高應(yīng)用程序的性能。

3、index和Join 操作,int的性能最好。

4、容易記憶。

int自增的缺點(diǎn):

1、使用INT數(shù)據(jù)范圍有限制。如果存在大量的數(shù)據(jù),可能會超出INT的取值范圍。

2、很難處理分布式存儲的數(shù)據(jù)表。

GUID做主鍵的優(yōu)點(diǎn):

1、唯一性。

2、適合大量數(shù)據(jù)中的插入和更新操作。

3、跨服務(wù)器數(shù)據(jù)合并非常方便。

GUID做主鍵的缺點(diǎn):

1、存儲空間大(16 byte),因此它將會占用更多的磁盤大小。

2、很難記憶。join操作性能比int要低。

3、沒有內(nèi)置的函數(shù)獲取最新產(chǎn)生的guid主鍵。

4、EF默認(rèn)生成的GUID是無序的,會影響數(shù)據(jù)插入性能。

結(jié)論:

在數(shù)據(jù)量比較少的場景下,建議使用int自增,比如分類。對于大數(shù)據(jù)量,建議使用有序GUID。因?yàn)槟J(rèn).net生成GUID是無序的,而數(shù)據(jù)庫中主鍵默認(rèn)是聚集索引,而聚集索引在物理上的存儲是有序的,當(dāng)插入數(shù)據(jù)時(shí),如果插入的是無序的GUID,可能就會涉及到移動數(shù)據(jù)的情況,進(jìn)而影響插入的性能,特別是百萬級數(shù)據(jù)量的時(shí)候,性能影響則較為明顯。參考資料:https://www.cnblogs.com/CameronWu/p/guids-as-fast-primary-keys-under-multiple-database.html

其他可選方案:

經(jīng)過個(gè)人多番了解,目前市面上常用的分布式id生成算法和Twitter發(fā)布的雪花算法大同小異,個(gè)人也在項(xiàng)目中使用過雪花算法,有興趣的朋友可以在博客園找下相關(guān)的內(nèi)容。不過目前用.net封裝的雪花算法普遍較基礎(chǔ),很難在docker或者k8s環(huán)境下簡單的使用,所以在此預(yù)告下,本人根據(jù)雪花算法編寫的可用于k8s環(huán)境的即將開源,敬請期待。

6. 查

EF使用Linq查詢數(shù)據(jù)庫中的數(shù)據(jù),使用Linq可編寫強(qiáng)類型的查詢。當(dāng)命令執(zhí)行時(shí),EF先將Linq表達(dá)式轉(zhuǎn)換成sql腳本,然后再提交給數(shù)據(jù)庫執(zhí)行。可在日志中查看生成的sql腳本。

根據(jù)條件查詢:
await _context.Blog.Where(x=>x.Id>0).ToListAsync();

上述代碼執(zhí)行時(shí)生成的sql腳本如下所示:

SELECT `x`.`id`, `x`.`create_date`, `x`.`title`FROM `blog` AS `x`WHERE `x`.`id` > 0
獲取單個(gè)實(shí)體

可實(shí)現(xiàn)獲取單個(gè)實(shí)體的方式有First,FirstOrDefault,Single,SingleOrDefault

其中First,FirstOrDefault執(zhí)行時(shí)生成的sql腳本如下:

SELECT `x`.`id`, `x`.`create_date`, `x`.`title`FROM `blog` AS `x`WHERE `x`.`id` > 10LIMIT 1

Single,SingleOrDefault執(zhí)行時(shí)生成的sql腳本如下:

SELECT `x`.`id`, `x`.`create_date`, `x`.`title`FROM `blog` AS `x`WHERE `x`.`id` > 10LIMIT 2

細(xì)心的你應(yīng)該已經(jīng)發(fā)現(xiàn)了兩者的區(qū)別,Single需要查詢2條數(shù)據(jù),當(dāng)返回的數(shù)據(jù)多余一條時(shí),Single,SingleOrDefault方法就會報(bào)Source sequence contains more than one element.異常。所以Single方法僅適用于查詢條件對應(yīng)的數(shù)據(jù)只有一條的場景,比如查詢主鍵的值。如下所示:

await _context.Blog.SingleOrDefaultAsync(x => x.Id==100);

后綴帶OrDefault和不帶后綴的區(qū)別是,當(dāng)sql腳本執(zhí)行查詢不到數(shù)據(jù)時(shí),帶后綴的會返回空值,而不帶后綴的則會直接報(bào)異常。

判斷數(shù)據(jù)庫是否存在

可通過Any()和Count()方法實(shí)現(xiàn)是否存在數(shù)據(jù)。示例代碼如下:

await _context.Blog.AnyAsync(x => x.Id > 100); await _context.Blog.CountAsync(x => x.Id > 100)>0;

生成的sql腳本對應(yīng)如下:

SELECT CASEWHEN EXISTS (SELECT 1FROM `blog` AS `x`WHERE `x`.`id` > 100)THEN TRUE ELSE FALSEEND SELECT COUNT(*)FROM `blog` AS `x`WHERE `x`.`id` > 100

乍一看,Any方法生成的腳本貌似更復(fù)雜些,但實(shí)際上,Any方法的性能在大數(shù)據(jù)量下比Count方法高了很多。所以在判斷是否存在時(shí),請使用Any方法。

連接查詢

連接查詢是關(guān)系數(shù)據(jù)庫中最主要的查詢,主要包括內(nèi)連接、外連接(左連接、外連接)和交叉連接等。通過連接運(yùn)算符可以實(shí)現(xiàn)多個(gè)表查詢。本文主要講解下常用的內(nèi)連接和左連接。

內(nèi)連接的示例代碼如下:

var query = from post in _context.Postjoin blog in _context.Blog on post.BlogId equals blog.Idwhere blog.Id > 0select new {blog, post};

左連接的示例代碼如下:

var query = from post in _context.Postjoin blog in _context.Blog on post.BlogId equals blog.Idinto pbsfrom pb in pbs.DefaultIfEmpty()where pb.Id>0 && post.Content.Contains("1")select new {post,pb.Title};
級聯(lián)查詢

在很多場景中,可能會涉及到查詢與父表關(guān)聯(lián)的子表數(shù)據(jù),在這樣的場景中,會有一部分人先查出主表數(shù)據(jù),然后根據(jù)主表的主鍵再去查詢子表的數(shù)據(jù),筆者在使用ef初期也是這種處理方式的。但借助Include的方法可以讓我們更方便的解決父子表級聯(lián)查詢的問題。示例代碼如下:

var result = await _context.Blog.Include(b => b.Posts) .SingleOrDefaultAsync(x=>x.Id==157);

如果有更多的層級,可以借助ThenInclude進(jìn)行查詢。

有的時(shí)候,還有這樣的場景:我們不是簡單的查詢子表的數(shù)據(jù),而是需要查詢滿足指定條件的數(shù)據(jù),那就要求咱們在調(diào)用Include的方法時(shí)傳入?yún)?shù),示例代碼如下:

var filteredBlogs = await _context.Blogs.Include(blog => blog.Posts.Where(post => post.BlogId == 1).OrderByDescending(post => post.Title).Take(5)).ToListAsync();

注:以上方法僅在.net5中支持。所以,efcore也是在一個(gè)發(fā)展的過程中,隨著時(shí)間與版本的更新,功能也會漸漸趨于完善。相關(guān)內(nèi)容請參考:https://docs.microsoft.com/zh-cn/ef/core/querying/related-data

7. 改

使用過EF的應(yīng)該都了解查詢的跟蹤與非跟蹤的概念吧(納尼?你沒聽說過,老衲給您指條明路吧:https://docs.microsoft.com/zh-cn/ef/core/querying/tracking)。

通常來講,更新的流程大概是這樣:查詢出數(shù)據(jù),修改某些字段的值,調(diào)用Update方法,然后調(diào)用SaveChange方法。看上去毫無破綻,但如果你仔細(xì)觀察過生成的sql腳本的話,或許你就應(yīng)該有更好的方法,咱們先來看看示例代碼:

var school = await _context.Schools.FirstAsync(x => x.Id > 0); school.Name = "6666"; _context.Schools.Update(school); await _context.SaveChangesAsync();

如下圖所示的是執(zhí)行以上代碼生成的update的sql語句,我們發(fā)現(xiàn)明明代碼中只對Name重新賦了值,但生成的腳本卻將此記錄的所有字段進(jìn)行了更新,顯然這不是我們想要的結(jié)果。

其實(shí),如果實(shí)體是通過跟蹤查詢得到的,則可直接調(diào)用SaveChage方法,而不用多余調(diào)用Update方法,此時(shí),EF內(nèi)部會自動判斷哪些字段進(jìn)行了更新,從而只生成值改變了的sql語句。

結(jié)論:當(dāng)要更新的實(shí)體開啟了跟蹤,則更新時(shí),無需調(diào)用Update方法, 直接調(diào)用SaveChange方法,此時(shí)之后更新值發(fā)生改變的字段。如果先調(diào)用Update則SaveChange,則不管實(shí)體的字段有沒有更新,生成的sql腳本依舊會更新所有的字段,犧牲了性能。假如你的實(shí)體不是通過數(shù)據(jù)庫的跟蹤查詢獲取的,則在調(diào)用時(shí)才需要調(diào)用Update方法。


福祿ICH.架構(gòu)出品

作者:福爾斯

2020年8月

總結(jié)

以上是生活随笔為你收集整理的efcore技巧贴-也许有你不知道的使用技巧的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。