2020-05-24 - EFCore保存數(shù)據(jù)提綱

基本保存

  • 添加數(shù)據(jù)
  • 更新數(shù)據(jù)
  • 刪除數(shù)據(jù)
  • 單個 SaveChanges中的多個操作(事務(wù)性)

相關(guān)數(shù)據(jù)

  • 添加新實體和相關(guān)實體
  • 往導(dǎo)航屬性中添加相關(guān)實體
  • 新建主實體并修改已存在子實體的導(dǎo)航屬性
  • 刪除關(guān)系
    通過將引用導(dǎo)航設(shè)置為 null 或從集合導(dǎo)航中刪除相關(guān)實體來刪除關(guān)系
    • 默認(rèn)情況 ( 不配置級聯(lián)刪除的情況下 )
      對于必選關(guān)系峦剔,將配置級聯(lián)刪除行為,并將從數(shù)據(jù)庫中刪除子實體/依賴實體角钩。
      對于可選關(guān)系吝沫,默認(rèn)情況下不會配置級聯(lián)刪除,但會將外鍵屬性設(shè)置為 null递礼。

級聯(lián)刪除

描述一種允許在刪除某行時自動觸發(fā)刪除相關(guān)行的特性

  • 刪除行為 DeleteBehavior 枚舉類型, 傳遞到 OnDelete FluentAPI 來控制是主體/父實體的刪除還是依賴實體/子實體關(guān)系的斷開會對依賴實體/子實體產(chǎn)生副作用
    刪除主體/父實體或斷開與子實體的關(guān)系時有三個 EF 可執(zhí)行的操作:
    a. 可以刪除子項/依賴項
    b. 子項的外鍵值可以設(shè)置為 null (外鍵不可以為null時, savechanges將拋出異常)
    c. 子項保持不變

#刪除主實體的代碼

var blog = context.Blogs.Include(b => b.Posts).First();
context.Remove(blog);
context.SaveChanges();

1. (可選關(guān)系) 外鍵可為null時的級聯(lián)刪除

a. Cascade -- 刪除實體和子實體
b. ClientSetNull(默認(rèn))

內(nèi)存中相關(guān)實體外鍵屬性變?yōu)閚ull, SaveChanges 時會變更數(shù)據(jù)庫;
但如果存在未加載的關(guān)聯(lián)子實體,可能會拋出異常,因為引用的主實體不存在

c. SetNull, 外鍵屬性設(shè)置為 null

內(nèi)存中相關(guān)實體外鍵屬性變?yōu)閚ull, SaveChanges 時內(nèi)存中關(guān)聯(lián)實體將設(shè)置外鍵為 null;
如果存在未加載的關(guān)聯(lián)子實體,數(shù)據(jù)庫支持時會設(shè)置關(guān)聯(lián)子實體的外鍵為null,數(shù)據(jù)庫不支持時引發(fā)異常

d. Restrict, 不進(jìn)行任何改變

EFCore 拋出異常, 子實體的外鍵不發(fā)生改變, 引用了刪除的實體

2. 必選關(guān)系 ( 外鍵不可為null時) 的級聯(lián)刪除

DeleteBehavior 在 OnDelete 中的值
a. Cascade(默認(rèn)) 刪除相關(guān)實體
b. ClientSetNull,
SQL拋出異常, 不可以設(shè)置外鍵字段為 null
c. SetNull, ,
SQL拋出異常, 不可以設(shè)置外鍵字段為 null
d. Restrict, 不進(jìn)行任何改變
EFCore 拋出異常, 子實體的外鍵不發(fā)生改變, 引用了刪除的實體

刪除孤立項代碼

var blog = context.Blogs.Include(b => b.Posts).First();
blog.Posts.Clear();
context.SaveChanges();

a. DeleteBehavior.Cascade ( 無論是可選還是必選)
子實體從數(shù)據(jù)庫刪除
b. 具有必選關(guān)系的 DeleteBehavior.ClientSetNull 或 DeleteBehavior.SetNull
SQL執(zhí)行異常,無法設(shè)置 BlogId字段為 null
c. 具有可選關(guān)系的 DeleteBehavior.ClientSetNull 或 DeleteBehavior.SetNull
Post 的 BlogId 字段設(shè)置為 null
d. 具有必選或可選關(guān)系的 DeleteBehavior.Restrict
EFCore 拋出異常, Post有外鍵,但是未指向內(nèi)存中的 Blog 對象
Blog '1' is in state Unchanged with 2 posts referenced.
Post '1' is in state Modified with FK '1' and no reference to a blog.

并發(fā)沖突

  • 有三組值可用于幫助解決并發(fā)沖突:

“當(dāng)前值” 是應(yīng)用程序嘗試寫入數(shù)據(jù)庫的值惨险。
“原始值” 是在進(jìn)行任何編輯之前最初從數(shù)據(jù)庫中檢索的值。
“數(shù)據(jù)庫值” 是當(dāng)前存儲在數(shù)據(jù)庫中的值脊髓。

  • 處理并發(fā)沖突的常規(guī)方法是:

在 SaveChanges 期間捕獲 DbUpdateConcurrencyException辫愉。
使用 DbUpdateConcurrencyException.Entries 為受影響的實體準(zhǔn)備一組新更改。
刷新并發(fā)令牌的原始值以反映數(shù)據(jù)庫中的當(dāng)前值将硝。
重試該過程恭朗,直到不發(fā)生任何沖突。

事務(wù)

  • 默認(rèn)事務(wù)行為
  • 控制事務(wù)
  • 共享事務(wù) --
    要共享事務(wù)依疼,上下文必須共享 DbConnection 和 DbTransaction
var connectionString = @"Server=(localdb)\mssqllocaldb;Database=EFSaving.Transactions;Trusted_Connection=True;ConnectRetryCount=0";
var options = new DbContextOptionsBuilder<BloggingContext>()
                .UseSqlServer(new SqlConnection(connectionString))
                .Options;   
using (var context1 = new BloggingContext(options))
{
        using (var transaction = context1.Database.BeginTransaction())
        {
           using (var context2 = new BloggingContext(options))
           {
                context2.Database.UseTransaction(transaction.GetDbTransaction());
          }  
      }
}
public class BloggingContext : DbContext
{
            public BloggingContext(DbContextOptions<BloggingContext> options)
                : base(options)
            { }
            ....
}
  • 使用外部 DbTransactions(僅限關(guān)系數(shù)據(jù)庫)
using (var connection = new SqlConnection(connectionString))
{
    connection.Open();
    using (var transaction = connection.BeginTransaction())
    {
           var options = new DbContextOptionsBuilder<BloggingContext>()
                .UseSqlServer(connection)
                .Options;
          using (var context = new BloggingContext(options))
            {
                context.Database.UseTransaction(transaction);  //使用外部的事務(wù)
            }
            transaction.Commit();
    }
}
  • 使用 System.Transactions
    a. 環(huán)境事務(wù)
    TransactionScope
using (var scope = new TransactionScope(
    TransactionScopeOption.Required,
    new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
{
     using (var connection = new SqlConnection(connectionString))
     {
           connection.Open();
           var options = new DbContextOptionsBuilder<BloggingContext>()
                .UseSqlServer(connection)
                .Options;
          using (var context = new BloggingContext(options))
            {
                context.Database.UseTransaction(transaction);  //使用外部的事務(wù)
            }
            scope.Complete();
     }
}

b. 在顯式事務(wù)中登記
context.Database.EnlistTransaction(transaction)

using (var transaction = new CommittableTransaction(
    new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
{
         var connection = new SqlConnection(connectionString);
        var options = new DbContextOptionsBuilder<BloggingContext>()
            .UseSqlServer(connection)
            .Options;
        using (var context = new BloggingContext(options))
        {
            context.Database.OpenConnection();
            context.Database.EnlistTransaction(transaction);
        }
        transaction.Commit();
}

異步保存

必須調(diào)用 await 否則, DbContext可能會被dispose

using (var context = new BloggingContext())
    {
        var blog = new Blog { Url = url };
        context.Blogs.Add(blog);
        await context.SaveChangesAsync();
    }

斷開連接的實體

實例不是由 DbContext 從數(shù)據(jù)庫查詢而來,但是需要保存到數(shù)據(jù)庫
DbContext實例需要知道實體是新實體(應(yīng)插入)還是現(xiàn)有實體(應(yīng)更新)

  • 標(biāo)識新實體
    a. 客戶端標(biāo)識新實體
    b. 使用自動生成的鍵來判斷是否是新實體
    b1. public static bool IsItNew(Blog blog) => blog.BlogId == 0;
    b2. public static bool IsItNew(DbContext context, object entity) => !context.Entry(entity).IsKeySet;
    c. 從數(shù)據(jù)庫查詢
    public static bool IsItNew(BloggingContext context, Blog blog) => context.Blogs.Find(blog.BlogId) == null;
  • 保存單個實體
    知道是需要插入還是需要更新痰腮,則可以相應(yīng)地使用 Add 或 Update
    Update 方法通常將實體標(biāo)記為更新,而不是插入律罢。 但是膀值,如果實體具有自動生成的鍵且未設(shè)置任何鍵值,則實體會自動標(biāo)記為插入误辑。
public static void InsertOrUpdate(BloggingContext context, Blog blog)
{
    var existingBlog = context.Blogs.Find(blog.BlogId);
    if (existingBlog == null)
    {
        context.Add(blog);
    }
    else
    {
        context.Entry(existingBlog).CurrentValues.SetValues(blog);
    }
    context.SaveChanges();
}

SetValues 僅將與跟蹤實體中的屬性具有不同值的屬性標(biāo)記為“已修改”沧踏。這意味著當(dāng)發(fā)送更新時,只會更新實際發(fā)生更改的列稀余。 (如果未發(fā)生更改悦冀,則根本不會發(fā)送任何更新。)

設(shè)置已生成屬性的顯式值

  • 設(shè)置屬性的數(shù)據(jù)庫默認(rèn)值
    modelBuilder.Entity<Employee>() .Property(b => b.EmploymentStarted) .HasDefaultValueSql("CONVERT(date, GETDATE())");
  • 顯式值插入 SQL Server IDENTITY 列
    只能插入,不能更新
    context.Database.ExecuteSqlRaw("SET IDENTITY_INSERT dbo.Employees ON");
    context.SaveChanges();
    context.Database.ExecuteSqlRaw("SET IDENTITY_INSERT dbo.Employees OFF");
  • 在更新期間設(shè)置顯式值
    modelBuilder.Entity<Employee>() .Property(b => b.LastPayRaise) .ValueGeneratedOnAddOrUpdate();
    默認(rèn)情況下睛琳,如果嘗試保存配置為在更新期間生成的屬性的顯式值盒蟆,EF Core 將引發(fā)異常踏烙。 若要避免此問題,必須下拉到較低級別的元數(shù)據(jù) API 并設(shè)置 AfterSaveBehavior(如上所示)历等。
    即默認(rèn)不能在客戶端設(shè)置LastPayRaise屬性的值,除非像下面這樣設(shè)置 AfterSaveBehavior
    modelBuilder.Entity<Employee>() .Property(b => b.LastPayRaise) .Metadata.SetAfterSaveBehavior(PropertySaveBehavior.Ignore);
CREATE TRIGGER [dbo].[Employees_UPDATE] ON [dbo].[Employees]
    AFTER UPDATE
AS
BEGIN
    SET NOCOUNT ON;
    -- 避免被下面的 update 循環(huán)執(zhí)行此觸發(fā)器;              
    IF ((SELECT TRIGGER_NESTLEVEL()) > 1) RETURN;
                  
    IF UPDATE(Salary) AND NOT Update(LastPayRaise) -- 是否更新了 Salary 字段
    BEGIN
        DECLARE @Id INT
        DECLARE @OldSalary INT
        DECLARE @NewSalary INT
          
        SELECT @Id = INSERTED.EmployeeId, @NewSalary = Salary        
        FROM INSERTED
          
        SELECT @OldSalary = Salary        
        FROM deleted
          
        IF @NewSalary > @OldSalary
        BEGIN
            UPDATE dbo.Employees
            SET LastPayRaise = CONVERT(date, GETDATE())
            WHERE EmployeeId = @Id
        END
    END
END

TRIGGER_NESTLEVEL --
When no parameters are specified, TRIGGER_NESTLEVEL returns the total number of triggers on the call stack. This includes itself.
無參數(shù)時,返回此語句執(zhí)行時在調(diào)用堆棧中的觸發(fā)器的數(shù)量;
SQLSERVER最大支持 32層觸發(fā)器嵌套;

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末讨惩,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子寒屯,更是在濱河造成了極大的恐慌荐捻,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件寡夹,死亡現(xiàn)場離奇詭異处面,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)菩掏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門魂角,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人智绸,你說我怎么就攤上這事野揪。” “怎么了瞧栗?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵匙瘪,是天一觀的道長血柳。 經(jīng)常有香客問我驳棱,道長塘安,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任系草,我火速辦了婚禮通熄,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘找都。我一直安慰自己唇辨,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布能耻。 她就那樣靜靜地躺著赏枚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪晓猛。 梳的紋絲不亂的頭發(fā)上饿幅,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機(jī)與錄音戒职,去河邊找鬼栗恩。 笑死,一個胖子當(dāng)著我的面吹牛洪燥,可吹牛的內(nèi)容都是我干的磕秤。 我是一名探鬼主播乳乌,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼市咆!你這毒婦竟也來了汉操?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤蒙兰,失蹤者是張志新(化名)和其女友劉穎磷瘤,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體搜变,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡采缚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了挠他。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片仰担。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖绩社,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情赂苗,我是刑警寧澤愉耙,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站拌滋,受9級特大地震影響朴沿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜败砂,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一赌渣、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧昌犹,春花似錦坚芜、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至铸敏,卻和暖如春缚忧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背杈笔。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工闪水, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蒙具。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓球榆,卻偏偏與公主長得像朽肥,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子芜果,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353