上一篇我們介紹了Entity Framework Core系列之DbContext(添加),這一篇我們介紹下修改數據
修改實體的方法取決於context是否正在跟蹤需要修改的實體。
下面的示例中實體由context獲得,所以context會開始追蹤這個實體。當我們更改這個實體的屬性值時,context會將實體的EntityState更改為已修改,ChangeTracker將記錄舊屬性值和新屬性值。當調用SaveChanges時,會生成update語句並執行。
var author = context.Authors.First(a => a.AuthorId == 1); author.FirstName = "Bill"; context.SaveChanges();
由於ChangeTracker記錄哪些屬性已被修改,Context將發出一條SQL語句,該語句只更新已修改的屬性:
exec sp_executesql N'SET NOCOUNT ON; UPDATE [Authors] SET [FirstName] = @p0 WHERE [AuthorId] = @p1; SELECT @@ROWCOUNT; ',N'@p1 int,@p0 nvarchar(4000)',@p1=1,@p0=N'Bill'
斷開連接的場景
在一個斷開連接的場景如ASP.NET應用程序中,在controller或者服務方法中可能會發生更改現有實體屬性值。在這種情況下,需要告知Context實體處於修改狀態, 有幾種方法: 顯式地為實體設置EntityState; 用DbContext.Update
方法(EF Core新內容); 使用DbContext.Attach方法,然后“遍歷對象圖”,以顯式地設置圖中各個屬性的狀態.
設置EntityState
通過EntityEntry設置實體的EntityState
public void Save(Author author) { using (var context = new EFCoreContext()) { context.Entry(author).State = EntityState.Modified; context.SaveChanges(); } }
這種方法只會導致Author實體被設置成修改后的狀態。不會設置任何相關對象。由於ChangeTracker不知道修改了哪些屬性,因此Context生成一條SQL語句來更新所有屬性值(除了主鍵值之外)。
DbContext Update
DbContext類提供用於處理單個或多個實體的Update和UpdateRange方法。
public void Save(Author author) { using (var context = new EFCoreContext()) { context.Update(author); context.SaveChanges(); } }
與設置EntityState方法一樣,這個方法也會將Context追蹤的實體設置成修改狀態。同樣,Context沒有任何方法來識別哪些屬性值已經更改,所以生成SQL來更新所有屬性。與顯示設置設置EntityState不同的是,Context也會修改相關實體(如本例中的Books)的狀態為已修改,從而會為每個實體生成update語句。如果相關實體沒有對應的鍵值,就會標記為add,生成一條Insert語句。
DbContext Attach
當在實體上使用Attach方法時,它的狀態將被設置為unchanged,這將導致根本不會生成任何sql語句,所有定義了鍵值的其他可訪問實體也將被設置為unchanged。那些沒有鍵值的將被標記為Added。但是,現在該實體正在被Context跟蹤,我們可以通知上下文哪些屬性被修改,生成正確的UPDATE SQL:
using (var context = new EFCoreContext()) { var author = new Author { AuthorId = 1, FirstName = "yixuan", LastName = "han" }; author.Books.Add(new Book { BookId = 1, Title = "Othello" }); context.Attach(author); context.Entry(author).Property("FirstName").IsModified = true; context.SaveChanges(); }
上面的代碼會將Author實體設置成被修改,生成的sql語句也之后更新FirstName。
exec sp_executesql N'SET NOCOUNT ON; UPDATE [Authors] SET [FirstName] = @p0 WHERE [AuthorId] = @p1; SELECT @@ROWCOUNT; ',N'@p1 int,@p0 nvarchar(4000)',@p1=1,@p0=N'William'
TrackGraph
TrackGraph API提供了對對象圖中各個實體的訪問,並允許您對每個實體分別執行定制代碼。這在處理這種由不同對象的相關實體的復雜對象圖的場景中非常有用。下面的示例復制了一個場景,其中對象圖是在Context之外構造的。然后用TrackGraph方法“走圖”:
var author = new Author { AuthorId = 1, FirstName = "yixuan", LastName = "han" }; author.Books.Add(new Book { AuthorId = 1, BookId = 1, Title = "Hamlet", Isbn = "1234" }); author.Books.Add(new Book { AuthorId = 1, BookId = 2, Title = "Othello", Isbn = "4321" }); author.Books.Add(new Book { AuthorId = 1, BookId = 3, Title = "MacBeth", Isbn = "5678" }); using (var context = new EFCoreContext()) { context.ChangeTracker.TrackGraph(author, e => { if ((e.Entry.Entity as Author) != null) { e.Entry.State = EntityState.Unchanged; } else { e.Entry.State = EntityState.Modified; } }); context.SaveChanges(); }
在這個場景中,假定Author實體沒有被修改,但是Book可能被編輯過。TrackGraph方法將根實體作為參數,並使用lambda指定要執行的操作。在這種情況下,根實體(Author)將其EntityState設置為不變。Context開始跟蹤實體,設置EntityState,只有這樣才能發現和它相關的實體。Books的EntityState設置為Modified,與前面的示例一樣,這將導致生成SQL更新實體上的所有屬性:
exec sp_executesql N'SET NOCOUNT ON; UPDATE [Books] SET [AuthorId] = @p0, [Isbn] = @p1, [Title] = @p2 WHERE [BookId] = @p3; SELECT @@ROWCOUNT; UPDATE [Books] SET [AuthorId] = @p4, [Isbn] = @p5, [Title] = @p6 WHERE [BookId] = @p7; SELECT @@ROWCOUNT; UPDATE [Books] SET [AuthorId] = @p8, [Isbn] = @p9, [Title] = @p10 WHERE [BookId] = @p11; SELECT @@ROWCOUNT; ',N'@p3 int,@p0 int,@p1 nvarchar(4000),@p2 nvarchar(150),@p7 int,@p4 int,@p5 nvarchar(4000), @p6 nvarchar(150),@p11 int,@p8 int,@p9 nvarchar(4000),@p10 nvarchar(150)', @p3=1,@p0=1,@p1=N'1234',@p2=N'Hamlet', @p7=2,@p4=1,@p5=N'4321',@p6=N'Othello', @p113,@p8=1,@p9=N'5678',@p10=N'MacBeth'
因為SQL更新了所有屬性,所以它們都需要顯示並分配一個有效值,否則它們將被更新為默認值。在下一個示例中,對象圖再次在Context之外構造,但只修改了books的Isbn屬性。因此,其他屬性(除了實體鍵)被省略:
var author = new Author { AuthorId = 1, FirstName = "William", LastName = "Shakespeare" }; author.Books.Add(new Book { BookId = 1, Isbn = "1234" }); author.Books.Add(new Book { BookId = 2, Isbn = "4321" }); author.Books.Add(new Book { BookId = 3, Isbn = "5678" }); using (var context = new EFCoreContext()) { context.ChangeTracker.TrackGraph(author, e => { e.Entry.State = EntityState.Unchanged; //starts tracking if ((e.Entry.Entity as Book) != null) { context.Entry(e.Entry.Entity as Book).Property("Isbn").IsModified = true; } }); }
這一次,lambda的方法主體確保所有實體都被設置成Unchanged,然后指示Isbn屬性被修改。這導致生成的SQL只更新Isbn屬性值:
exec sp_executesql N'SET NOCOUNT ON; UPDATE [Books] SET [Isbn] = @p0 WHERE [BookId] = @p1; SELECT @@ROWCOUNT; UPDATE [Books] SET [Isbn] = @p2 WHERE [BookId] = @p3; SELECT @@ROWCOUNT; UPDATE [Books] SET [Isbn] = @p4 WHERE [BookId] = @p5; SELECT @@ROWCOUNT; ',N'@p1 int,@p0 nvarchar(4000),@p3 int,@p2 nvarchar(4000),@p5 int,@p4 nvarchar(4000)', @p1=1,@p0=N'1234', @p3=2,@p2=N'4321', @p5=3,@p4=N'5678'