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

歡迎訪問(wèn) 生活随笔!

生活随笔

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

编程问答

Entity Framework Core 5中实现批量更新、删除

發(fā)布時(shí)間:2023/12/4 编程问答 59 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Entity Framework Core 5中实现批量更新、删除 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

本文介紹了一個(gè)在EntityFramework Core 5中不需要預(yù)先加載數(shù)據(jù)而使用一句SQL語(yǔ)句批量更新、刪除數(shù)據(jù)的開(kāi)發(fā)包,并且分析了其實(shí)現(xiàn)原理,并且與其他實(shí)現(xiàn)方案做了比較。

一、背景

隨著微軟全面擁抱開(kāi)源,.Net開(kāi)源社區(qū)百花開(kāi)放,涌現(xiàn)了非常多優(yōu)秀的開(kāi)源,ORM項(xiàng)目就有Dapper、SqlSugar、PetaPoco、FreeSQL等。作為微軟官方提供的ORM框架,Entity Framework Core(以下簡(jiǎn)稱(chēng)EF Core)顯然是被關(guān)注最多的。EF Core非常優(yōu)秀而且功能豐富,但是EF Core有一個(gè)一直被人詬病的地方就是它并不能很好支持?jǐn)?shù)據(jù)的批量更新和批量刪除。在EF Core中批量更新和刪除數(shù)據(jù)都要先把數(shù)據(jù)加載到內(nèi)存中,然后再對(duì)數(shù)據(jù)操作,最后再SaveChanges,比如下面的代碼用于把所有Id大于2或者AuthorName中含有”zack”的價(jià)格增加3:

var books2 = ctx.Books.Where(b => b.Id >2||b.AuthorName.Contains("zack"));

foreach(var b in books2)

{

??? b.Price =b.Price + 3;

}

ctx.SaveChanges();

?

讓我們查看上面的程序幕后執(zhí)行的SQL語(yǔ)句:

?

可以看到,EF Core先把數(shù)據(jù)用Select查詢出來(lái),然后在內(nèi)存中逐個(gè)修改,最后再把被修改對(duì)象每個(gè)都執(zhí)行一次Update語(yǔ)句去更新。

?

再比如,如下的代碼用于刪除Price大于5元的記錄:

var books1 = ctx.Books.Where(b => b.Price > 5);

ctx.RemoveRange(books1);

ctx.SaveChanges();

?

讓我們查看上面的程序運(yùn)行幕后執(zhí)行的SQL語(yǔ)句:

?

?

可以看到,EF Core先把數(shù)據(jù)用Select查詢出來(lái),然后再對(duì)每條記錄都執(zhí)行Delete語(yǔ)句去刪除。

很顯然,如果批量更新或者刪除的數(shù)據(jù)量比較大,這樣的操作性能是非常低的。

因此,我們需要一種在EF Core中使用一條SQL語(yǔ)句就高性能地刪除或者更新數(shù)據(jù)的方法。

?

二、為什么微軟不提供這樣的方法

盡管用戶的要求強(qiáng)烈,但是微軟一直沒(méi)有提供高效的批量刪除和更新的方式。在EF Core Github的issue中?[1],微軟給出的理由是:這樣做會(huì)導(dǎo)致EF Core的對(duì)象狀態(tài)跟蹤混亂,比如對(duì)于同一個(gè)DbContext,如果用批量刪除的方法刪除了數(shù)據(jù),那么在被刪除之前查詢出來(lái)的數(shù)據(jù)狀態(tài)就混亂了,因此需要重構(gòu)EF Core的代碼,工作量比較大。

作為一個(gè)成熟的框架,考慮這些邏輯問(wèn)題以避免潛在的風(fēng)險(xiǎn)是有必要的,是可以理解的。但是作為實(shí)際的開(kāi)發(fā)者,我們是有辦法規(guī)避這些問(wèn)題的。比如一般的Web應(yīng)用中,刪除操作都是在一個(gè)單獨(dú)的Http請(qǐng)求進(jìn)行中的,因此不涉及到微軟擔(dān)心的問(wèn)題。即使在有的場(chǎng)景下,涉及到在通過(guò)同一個(gè)DbContext在數(shù)據(jù)刪除之前就把數(shù)據(jù)查詢出來(lái)的場(chǎng)景,那么也完全可以通過(guò)在刪除之后再查一次的方式來(lái)規(guī)避這個(gè)問(wèn)題。

根據(jù)github上那個(gè)issue的回復(fù),微軟有考慮在EF Core 6.0中加入高效地批量刪除和更新數(shù)據(jù)的方式,但是僅僅是“考慮”,并不確定。我們作為普通開(kāi)發(fā)者可等不及了,因此要自己去解決。

三、已有解決方法

有如下三種已有的解決方法:

  • 執(zhí)行原生SQL語(yǔ)句。在EF Core中提供了ctx.Database.ExecuteSqlRaw()等方法可以用來(lái)執(zhí)行原生SQL語(yǔ)句,因此我們可以直接編寫(xiě)Delete、Update語(yǔ)句來(lái)刪除或者更新數(shù)據(jù)。這種方式比較直接,缺點(diǎn)就是這樣代碼中直接操作數(shù)據(jù)表的方式不太符合模型驅(qū)動(dòng)、分層隔離等思想,程序員直接面對(duì)數(shù)據(jù)庫(kù)表,無(wú)法利用EF Core強(qiáng)類(lèi)型的特性,如果模型發(fā)生改變,必須手動(dòng)變更SQL語(yǔ)句;而且如果調(diào)用了一些DBMS特有的語(yǔ)法、函數(shù),一旦程序遷移到其他DBMS,就可能要重新編寫(xiě)SQL語(yǔ)句,而無(wú)法利用EF Core強(qiáng)大的SQL翻譯機(jī)制來(lái)屏蔽不同底層數(shù)據(jù)庫(kù)的差異。

  • 使用其他ORM。FreeSQL等ORM中提供了批量Delete、Update語(yǔ)句的方法,使用也非常簡(jiǎn)單。這種方式的缺點(diǎn)是項(xiàng)目中必須引入第三方的ORM,無(wú)法復(fù)用EF Core的代碼。

  • 使用已有的EF Core擴(kuò)展。EF Plus、EFCore.BulkExtensions等開(kāi)源庫(kù)中都提供了在EF Core框架下進(jìn)行批量操作的方法。實(shí)現(xiàn)這個(gè)的核心就是要獲得EF Core生成的SQL語(yǔ)句以及SelectExpression。由于EF Core 5.0之前的版本中沒(méi)有提供公開(kāi)的API用于獲取一個(gè)LINQ操作對(duì)應(yīng)的SQL語(yǔ)句,所以這些開(kāi)源庫(kù)都是通過(guò)訪問(wèn)EF Core框架中一些類(lèi)的私有成員來(lái)完成的獲取LINQ對(duì)應(yīng)的SQL語(yǔ)句以及SelectExpression的方法?[2]。由于用的是訪問(wèn)私有成員這樣不符合面向?qū)ο笤瓌t的方式,所以一旦EF Core框架代碼發(fā)生改變,代碼就可能會(huì)失敗,之前就發(fā)生過(guò)EF Core新版本發(fā)布造成這些開(kāi)源庫(kù)無(wú)法工作的情況。而且,在撰寫(xiě)這篇文章的時(shí)候,這些開(kāi)源庫(kù)還沒(méi)有適配.Net 5。

  • ?

    四、我的實(shí)現(xiàn)Zack.EFCore.Batch

    我開(kāi)發(fā)了一個(gè)EntityFramework Core的擴(kuò)展庫(kù),讓開(kāi)發(fā)者在Entity Framework Core中可以用一句SQL進(jìn)行數(shù)據(jù)的刪除或者更新。由于開(kāi)發(fā)中用到了Entity Framework Core5的API,所以這個(gè)庫(kù)要求Entity FrameworkCore 5及以上版本,也就是.Net 5及以上版本。

    ?

    下面介紹一下使用方法:

    第一步,通過(guò)Nuget安裝Install-Package Zack.EFCore.Batch

    第二步,把如下代碼添加到你的DbContext類(lèi)的OnConfiguring方法中:

    optionsBuilder.UseBatchEF();

    第三步: 使用DbContext的擴(kuò)展方法DeleteRangeAsync()來(lái)刪除一批數(shù)據(jù). DeleteRangeAsync()的參數(shù)就是過(guò)濾條件的lambda表達(dá)式。

    批量刪除的例子代碼如下:

    ?

    await ctx.DeleteRangeAsync<Book>(b =>b.Price > n || b.AuthorName == "zack yang");

    ?

    上面的代碼將會(huì)在數(shù)據(jù)庫(kù)中執(zhí)行如下SQL語(yǔ)句:

    Delete FROM [T_Books] WHERE ([Price] > @__p_0) OR([AuthorName] = @__s_1)

    ?

    DeleteRange()方法是DeleteRangeAsync()的同步方法版本。

    使用DbContext的擴(kuò)展方法BatchUpdate()來(lái)創(chuàng)建一個(gè)BatchUpdateBuilder對(duì)象。BatchUpdateBuilder類(lèi)有如下四個(gè)方法:

    • Set()方法用于給一個(gè)屬性賦值。方法的第一個(gè)參數(shù)是屬性的lambda表達(dá)式,第二個(gè)參數(shù)是值的lambda表達(dá)式。

    • Where() 是過(guò)濾條件

    • ExecuteAsync()使用用于執(zhí)行BatchUpdateBuilder的異步方法

    • Execute()是ExecuteAsync()的同步方法版本。

    ?

    例子代碼:

    await ctx.BatchUpdate<Book>()

    ?? .Set(b =>b.Price, b => b.Price + 3)

    ?? .Set(b =>b.Title, b => s)

    ??.Set(b=>b.AuthorName,b=>b.Title.Substring(3,2)+b.AuthorName.ToUpper())

    ?? .Set(b =>b.PubTime, b => DateTime.Now)

    ?? .Where(b=> b.Id > n || b.AuthorName.StartsWith("Zack"))

    ??.ExecuteAsync();

    ?

    上面的代碼將會(huì)在SQLServer數(shù)據(jù)庫(kù)中執(zhí)行如下SQL語(yǔ)句:

    Update [T_Books] SET [Price] = [Price] + 3.0E0,[Title] = @__s_1, [AuthorName] = COALESCE(SUBSTRING([Title], 3 + 1, 2), N'') +COALESCE(UPPER([AuthorName]), N''), [PubTime] = GETDATE()

    WHERE ([Id] > @__p_0) OR ([AuthorName] IS NOT NULLAND ([AuthorName] LIKE N'Zack%'))

    ?

    這個(gè)開(kāi)發(fā)包使用EFCore實(shí)現(xiàn)的lambda表達(dá)式到SQL語(yǔ)句的翻譯,所以幾乎所有EF Core支持的lambda表達(dá)式寫(xiě)法都被支持。

    ?

    項(xiàng)目的GitHub地址:https://github.com/yangzhongke/Zack.EFCore.Batch

    五、實(shí)現(xiàn)原理分析

    其實(shí)要把lambda表達(dá)式轉(zhuǎn)換為SQL語(yǔ)句并不難,只要對(duì)表達(dá)式樹(shù)進(jìn)行解析就可以生成SQL語(yǔ)句,但是最難的部分是對(duì)于.Net函數(shù)到SQL片段的翻譯,因?yàn)橄嗤?Net函數(shù)在不同DBMS中等效的SQL片段是不同的,如果我自己實(shí)現(xiàn)這個(gè)是很麻煩的,因此我想到了直接借用EF Core的表達(dá)式樹(shù)到SQL語(yǔ)句的翻譯引擎來(lái)實(shí)現(xiàn)是最佳的方法。

    不幸的是,在.NetCore 3.x及之前,是無(wú)法直接獲取一個(gè)Linq查詢翻譯后的SQL語(yǔ)句的。.Net Core中可以通過(guò)日志等方式獲取翻譯后的SQL語(yǔ)句,但是這些都是Linq執(zhí)行后才能獲得的,而且是無(wú)法在拿到一個(gè)Lambda表達(dá)式或者IQueryable的時(shí)候立即獲得SQL的。經(jīng)過(guò)詢問(wèn).Net Core開(kāi)發(fā)團(tuán)隊(duì)得知,在.Net Core 3.X及之前,也是沒(méi)有公開(kāi)的API可以完成表達(dá)式樹(shù)到SQL片段翻譯的功能。

    ?

    從.Net 5開(kāi)始,Entity Framework Core 中提供了不用執(zhí)行查詢,就可以直接獲取Linq查詢對(duì)應(yīng)的SQL語(yǔ)句的方法,那就是調(diào)用IQueryable的ToQueryString()方法?[3]。

    ?

    因此我就想通過(guò)這個(gè)ToQueryString()方法拿到的SQL語(yǔ)句來(lái)入手來(lái)實(shí)現(xiàn)這個(gè)功能。可以把用到的Lambda表達(dá)式片段、過(guò)濾表達(dá)式拼接到一個(gè)查詢表達(dá)式中,然后調(diào)用ToQueryString()方法獲取翻譯后的SQL語(yǔ)句,然后編寫(xiě)詞法分析器和語(yǔ)法分析器對(duì)SQL語(yǔ)句進(jìn)行分析,提取出Where子句以及Select列中的表達(dá)式片段,然后再把這些片段重新組合成Update、Delete的SQL語(yǔ)句即可。

    不過(guò),由于不同DBMS的語(yǔ)法不同,編寫(xiě)這樣的詞法及語(yǔ)法分析器是很麻煩的,我就想能否研究ToQueryString()的實(shí)現(xiàn)原理,然后直接拿到解析過(guò)程中的SQL片段,這樣就避免了生成SQL后再去解析的工作。

    雖然EF Core是開(kāi)源的,不過(guò)由于關(guān)于EF Core的源代碼并沒(méi)有一個(gè)全面介紹的文檔,而EF Core的代碼又是非常復(fù)雜的,所以研究EF Core的源代碼是非常耗時(shí)的。研究過(guò)程中,我?guī)状味枷胍艞?#xff0c;最后終于把功能實(shí)現(xiàn)了,通過(guò)開(kāi)發(fā)這個(gè)庫(kù),我也對(duì)于EF Core的內(nèi)部原理,特別是從Lambda表達(dá)式到SQL的翻譯的整個(gè)過(guò)程了解的非常透徹。我這里不對(duì)研究的過(guò)程去回顧,而是直接為大家講解一下EFCore的原理,然后再講解一下我這個(gè)Zack.EFCore.Batch的實(shí)現(xiàn)原理。

    1.? EF Core的SQL翻譯原理

    EF Core中有很多的服務(wù),比如對(duì)于IQueryable進(jìn)行預(yù)處理的QueryTranslationPreprocessor、從查詢中提取查詢參數(shù)的RelationalParameterBasedSqlProcessor、把表達(dá)式樹(shù)翻譯為SQL語(yǔ)句的QuerySqlGenerator等。這些服務(wù)一般都是通過(guò)IXXX Factory這樣的工廠類(lèi)的Create()方法創(chuàng)建的,比如QueryTranslationPreprocessor對(duì)應(yīng)的IQueryTranslationPreprocessorFactory、QuerySqlGenerator對(duì)應(yīng)的IQuerySqlGeneratorFactory。而這些工廠類(lèi)的對(duì)象則是通過(guò)dbContext.GetService<XXX>()來(lái)從DbContext中獲得的。當(dāng)然,也有的服務(wù)是不需要通過(guò)工廠直接獲得的,比如Lambda編譯器服務(wù)IQueryCompiler就可以直接通過(guò)ctx.GetService<IQueryCompiler>()獲取。

    ?

    因此,如果你想使用EF Core中其他的服務(wù),都可以嘗試把對(duì)應(yīng)的服務(wù)接口類(lèi)型或者工廠類(lèi)型放到GetService()中查詢一下試試。

    EF Core中還允許調(diào)用DbContextOptionsBuilder的ReplaceService()方法把EF Core中的默認(rèn)服務(wù)替換為自定義實(shí)現(xiàn)類(lèi)。

    ?

    EF Core中把一個(gè)IQueryable對(duì)象翻譯為SQL語(yǔ)句的代碼分散在各個(gè)類(lèi)中,我經(jīng)過(guò)努力,把它們整合為一段可以運(yùn)行的代碼,如下:

    ?

    Expression query = queryable.Expression;

    var databaseDependencies =ctx.GetService<DatabaseDependencies>();

    IQueryTranslationPreprocessorFactory_queryTranslationPreprocessorFactory = ctx.GetService<IQueryTranslationPreprocessorFactory>();

    IQueryableMethodTranslatingExpressionVisitorFactory_queryableMethodTranslatingExpressionVisitorFactory =ctx.GetService<IQueryableMethodTranslatingExpressionVisitorFactory>();

    IQueryTranslationPostprocessorFactory_queryTranslationPostprocessorFactory =ctx.GetService<IQueryTranslationPostprocessorFactory>();

    QueryCompilationContext queryCompilationContext =databaseDependencies.QueryCompilationContextFactory.Create(true);

    ?

    IDiagnosticsLogger<DbLoggerCategory.Query>logger = ctx.GetService<IDiagnosticsLogger<DbLoggerCategory.Query>>();

    QueryContext queryContext =ctx.GetService<IQueryContextFactory>().Create();

    QueryCompiler queryComipler =ctx.GetService<IQueryCompiler>() as QueryCompiler;

    //parameterize determines if it will use "Declare"or not

    MethodCallExpression methodCallExpr1 =queryComipler.ExtractParameters(query, queryContext, logger, parameterize:true) as MethodCallExpression;

    QueryTranslationPreprocessorqueryTranslationPreprocessor = _queryTranslationPreprocessorFactory.Create(queryCompilationContext);

    MethodCallExpression methodCallExpr2 =queryTranslationPreprocessor.Process(methodCallExpr1) as MethodCallExpression;

    QueryableMethodTranslatingExpressionVisitorqueryableMethodTranslatingExpressionVisitor =

    ?????? _queryableMethodTranslatingExpressionVisitorFactory.Create(queryCompilationContext);

    ShapedQueryExpression shapedQueryExpression1 =queryableMethodTranslatingExpressionVisitor.Visit(methodCallExpr2) asShapedQueryExpression;

    QueryTranslationPostprocessor queryTranslationPostprocessor=_queryTranslationPostprocessorFactory.Create(queryCompilationContext);

    ShapedQueryExpression shapedQueryExpression2 =queryTranslationPostprocessor.Process(shapedQueryExpression1) asShapedQueryExpression;

    ?

    IRelationalParameterBasedSqlProcessorFactory_relationalParameterBasedSqlProcessorFactory =

    ?????? ctx.GetService<IRelationalParameterBasedSqlProcessorFactory>();

    RelationalParameterBasedSqlProcessor_relationalParameterBasedSqlProcessor =_relationalParameterBasedSqlProcessorFactory.Create(true);

    ?

    SelectExpression selectExpression =(SelectExpression)shapedQueryExpression2.QueryExpression;

    selectExpression =_relationalParameterBasedSqlProcessor.Optimize(selectExpression,queryContext.ParameterValues, out bool canCache);

    IQuerySqlGeneratorFactory querySqlGeneratorFactory =ctx.GetService<IQuerySqlGeneratorFactory>();

    QuerySqlGenerator querySqlGenerator =querySqlGeneratorFactory.Create();

    var cmd =querySqlGenerator.GetCommand(selectExpression);

    string sql = cmd.CommandText;

    ?

    大致解釋一下上面的代碼:

    queryable是一個(gè)待轉(zhuǎn)換的IQueryable對(duì)象,ctx是一個(gè)DbContext對(duì)象。QueryCompilationContext是Lambda到SQL翻譯這個(gè)“編譯”過(guò)程的上下文,很多工廠類(lèi)的Create方法都要用它做參數(shù)。QueryContext是查詢語(yǔ)句的上下文。SelectExpression是Linq查詢的表達(dá)式樹(shù)翻譯為強(qiáng)類(lèi)型的抽象語(yǔ)法樹(shù)的樹(shù)根。QuerySqlGenerator的GetCommand()方法用于遍歷SelectExpression生成目標(biāo)SQL語(yǔ)句。

    QuerySqlGenerator的GetCommand方法最終會(huì)調(diào)用VisitSelect(SelectExpressionselectExpression)來(lái)拼接生成SQL語(yǔ)句,其中會(huì)調(diào)用VisitSqlBinary(SqlBinaryExpression sqlBinaryExpression)、VisitFromSql(FromSqlExpression fromSqlExpression)、VisitLike(LikeExpression likeExpression)等方法來(lái)把運(yùn)算表達(dá)式、From、Like等翻譯成對(duì)應(yīng)的SQL片段。由于不同DBMS中一些函數(shù)等實(shí)現(xiàn)不同,而SelectExpression、LikeExpression等都是一個(gè)抽象節(jié)點(diǎn),是獨(dú)立于具體DBMS的抽象模型,因此各個(gè)DBMS的EF Provider只要負(fù)責(zé)編寫(xiě)代碼把這些XXExpression翻譯為各自的SQL片段即可,不同DBMS的EF Core中的代碼大部分都是各種XXTranslatorProvider。

    2.? Zack.EFCore.Batch的實(shí)現(xiàn)原理

    這個(gè)庫(kù)最核心的代碼就是ZackQuerySqlGenerator,它是一個(gè)繼承自QuerySqlGenerator的類(lèi)。它通過(guò)override父類(lèi)的VisitSelect方法,然后把父類(lèi)的VisitSelect方法的代碼全部拷過(guò)來(lái)。這樣的目的就是在VisitSelect拼接SQL語(yǔ)句的過(guò)程中把各個(gè)SQL片段截獲到。以下面的代碼為例:

    if (selectExpression.Predicate != null)

    {

    ?????? Sql.AppendLine().Append("WHERE");

    ?????? varoldSQL = Sql.Build().CommandText;//zack's code

    ?????? Visit(selectExpression.Predicate);

    ?????? this.PredicateSQL= Diff(oldSQL, this.Sql.Build().CommandText); //zack's code

    }

    這里就是首先把拼接Where條件之前的SQL語(yǔ)句保存到oldSQL變量中,再把拼接Where條件之后的SQL語(yǔ)句和oldSQL求一個(gè)差運(yùn)算,就得到了Where語(yǔ)句的SQL片段。

    ?

    然后通過(guò)optBuilder.ReplaceService<IQuerySqlGeneratorFactory,ZackQuerySqlGeneratorFactory>();把ZackQuerySqlGenerator對(duì)應(yīng)的ZackQuerySqlGeneratorFactory替換為IQuerySqlGeneratorFactory的默認(rèn)實(shí)現(xiàn)。這樣EF Core再完成從SelectExpression到SQL語(yǔ)句的翻譯,就會(huì)使用ZackQuerySqlGenerator類(lèi),這樣我們就可以截獲翻譯生成的SQL片段了。

    ?

    再解釋一下批量更新數(shù)據(jù)庫(kù)的BatchUpdateBuilder類(lèi)的主要代碼。代碼主要就是把Age=Age+1,Name=AuthorName.Trim()這樣的賦值表達(dá)式重新生成Select(new{b.Age,b.Age+1,b.Name,b.AuthorName.Trime()})這樣的表達(dá)式,這樣就把N個(gè)賦值表達(dá)式重新拼接為2*N個(gè)查詢表達(dá)式,再把查詢條件拼接形成一個(gè)IQueryable對(duì)象,再調(diào)用ZackQuerySqlGenerator翻譯IQueryable獲取到Where的SQL片段以及各個(gè)列的SQL片段,最后重新拼接成一個(gè)Update的SQL語(yǔ)句。

    ?

    六、局限性

    Zack.EFCore.Batch有如下局限性:

  • 由于Zack.EFCore.Batch用到了EF Core 5.0的新API,所以暫不支持EF Core 3.X及以下版本。

  • 由于Zack.EFCore.Batch是直接操作數(shù)據(jù)庫(kù),所以更新、刪除后,會(huì)存在微軟擔(dān)心的同一個(gè)DbContext中已經(jīng)查詢出來(lái)的對(duì)象跟蹤狀態(tài)和數(shù)據(jù)庫(kù)不一致的情況。在同一個(gè)DbContext實(shí)例中,如果需要在批量刪除或者更新之后操作同一個(gè)DbContex中之前查詢出來(lái)的數(shù)據(jù),建議再執(zhí)行一遍查詢操作。

  • 代碼中使用了一個(gè)內(nèi)部API QueryCompiler,這是不推薦的做法。

  • 創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來(lái)咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)

    總結(jié)

    以上是生活随笔為你收集整理的Entity Framework Core 5中实现批量更新、删除的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

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