使用实体状态

本主题介绍如何将实体添加和附加到上下文中,以及实体框架如何在 SaveChanges 期间处理这些实体。 当实体连接到上下文时,实体框架负责跟踪实体状态,但在断开连接或 N 层方案中,你可以让 EF 知道实体应该处于什么状态。 本主题所介绍的方法同样适用于查询使用 Code First 和 EF 设计器创建的模型。

实体状态和 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();
}

请注意,还可通过为已跟踪的实体调用 Add 或 Attach 来更改实体状态。 例如,为当前处于“已添加”状态的实体调用 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 时,所有属性值都将发送到数据库。