基本保存
- 添加數(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递礼。
- 默認(rèn)情況 ( 不配置級聯(lián)刪除的情況下 )
級聯(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ā)器嵌套;