Entity Framework Core系列之DbContext(修改)
上一篇我们介绍了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'