本主题介绍如何在 SaveChanges 期间添加实体并将其附加到上下文以及 Entity Framework 如何处理这些实体。 在连接到上下文时,实体框架负责跟踪实体的状态,但在断开连接或 N-Tier 环境中,您可以告诉 EF 您的实体应处于什么状态。 本主题中显示的技术同样适用于使用 Code First 和 EF Designer 创建的模型。
实体状态和 SaveChanges
实体可以是 EntityState 枚举定义的五种状态之一。 这些状态为:
- 添加说明:实体正在被上下文跟踪,但尚未存在于数据库中。
- 未更改:实体正被上下文跟踪并存在于数据库中,并且其属性值未从数据库中的值更改
- 已修改:实体正被上下文跟踪并存在于数据库中,并且已修改其部分或全部属性值
- 已删除:该实体正被上下文跟踪并存在于数据库中,但在下次调用 SaveChanges 时,该实体已被标记为要从数据库中删除
- 分离:上下文未跟踪实体
SaveChanges 对处于不同状态的实体执行不同的操作:
- SaveChanges 不会触摸未更改的实体。 更新不会发送到处于“未更改”状态的实体的数据库。
- 添加的实体将插入到数据库中,然后在 SaveChanges 返回时变为“未更改”。
- 修改后的实体在数据库中更新,然后在 SaveChanges 返回时变为“未更改”。
- 已删除的实体将从数据库中删除,然后从上下文中分离。
以下示例演示了实体或实体图的状态可以更改的方式。
将新实体添加到上下文
可以通过调用 DbSet 上的 Add 方法,将新实体添加到上下文中。 这会将实体置于“已添加”状态,这意味着它将在下一次调用 SaveChanges 时插入到数据库中。 例如:
using (var context = new BloggingContext())
{
var blog = new Blog { Name = "ADO.NET Blog" };
context.Blogs.Add(blog);
context.SaveChanges();
}
向上下文添加新实体的另一种方法是将其状态更改为“已添加”。 例如:
using (var context = new BloggingContext())
{
var blog = new Blog { Name = "ADO.NET Blog" };
context.Entry(blog).State = EntityState.Added;
context.SaveChanges();
}
最后,您可以通过将新实体连接到已跟踪的另一个实体,将其添加到上下文中。 这可以通过将新实体添加到另一个实体的集合导航属性,或者通过将另一个实体的引用导航属性设置为指向新实体来执行此操作。 例如:
using (var context = new BloggingContext())
{
// Add a new User by setting a reference from a tracked Blog
var blog = context.Blogs.Find(1);
blog.Owner = new User { UserName = "johndoe1987" };
// Add a new Post by adding to the collection of a tracked Blog
blog.Posts.Add(new Post { Name = "How to Add Entities" });
context.SaveChanges();
}
请注意,对于所有这些示例,如果添加的实体具有对尚未跟踪的其他实体的引用,则这些新实体也将添加到上下文中,并在下次调用 SaveChanges 时插入数据库中。
将现有实体附加到上下文
如果数据库中已存在已知实体,但上下文当前未跟踪该实体,则可以指示上下文使用 DbSet 上的 Attach 方法跟踪实体。 实体将在上下文中处于“未更改”状态。 例如:
var existingBlog = new Blog { BlogId = 1, Name = "ADO.NET Blog" };
using (var context = new BloggingContext())
{
context.Blogs.Attach(existingBlog);
// Do some more work...
context.SaveChanges();
}
请注意,如果在不对附加实体执行任何其他操作的情况下调用 SaveChanges,则不会对数据库进行任何更改。 这是因为实体处于“未更改”状态。
将现有实体附加到上下文的另一种方法是将其状态更改为“未更改”。 例如:
var existingBlog = new Blog { BlogId = 1, Name = "ADO.NET Blog" };
using (var context = new BloggingContext())
{
context.Entry(existingBlog).State = EntityState.Unchanged;
// Do some more work...
context.SaveChanges();
}
请注意,对于这两个示例,如果附加的实体引用了尚未跟踪的其他实体,则这些新实体也会附加到处于“未更改”状态的上下文中。
将现有但已修改的实体附加到上下文
如果你知道某个实体已存在于数据库中,但可能进行了更改,那么你可以通知上下文环境附加该实体,并将其状态设置为“已修改”。 例如:
var existingBlog = new Blog { BlogId = 1, Name = "ADO.NET Blog" };
using (var context = new BloggingContext())
{
context.Entry(existingBlog).State = EntityState.Modified;
// Do some more work...
context.SaveChanges();
}
将状态更改为“修改”时,实体的所有属性将被标记为已修改,调用 SaveChanges 时,所有属性值都将发送到数据库。
请注意,如果附加的实体引用了尚未跟踪的其他实体,则这些新实体将附加到处于“未更改”状态的上下文中,它们不会自动进行修改。 如果有多个实体需要标记为“修改”,则应为每个实体单独设置状态。
更改跟踪实体的状态
可以通过在其条目上设置 State 属性来更改已跟踪的实体的状态。 例如:
var existingBlog = new Blog { BlogId = 1, Name = "ADO.NET Blog" };
using (var context = new BloggingContext())
{
context.Blogs.Attach(existingBlog);
context.Entry(existingBlog).State = EntityState.Unchanged;
// Do some more work...
context.SaveChanges();
}
请注意,为已跟踪的实体调用“添加”或“附加”也可用于更改实体状态。 例如,为当前处于“已添加”状态的实体调用 Attach 会将其状态更改为“未更改”。
插入或更新模式
某些应用程序的常见模式是将实体添加为新实体(导致数据库插入),或将实体附加为现有实体,并将其标记为已修改(导致数据库更新),具体取决于主键的值。 例如,使用数据库生成的整数主键时,通常将具有零键的实体视为新实体,将非零键视为现有实体。 可以通过基于主键值的检查设置实体状态来实现此模式。 例如:
public void InsertOrUpdate(Blog blog)
{
using (var context = new BloggingContext())
{
context.Entry(blog).State = blog.BlogId == 0 ?
EntityState.Added :
EntityState.Modified;
context.SaveChanges();
}
}
请注意,将状态更改为“修改”时,实体的所有属性都将标记为已修改,调用 SaveChanges 时,所有属性值都将发送到数据库。