本章内容包括 |
持久化修改的对象到数据库 |
持久化复杂对象图到数据库 |
带有外键和独立关联的持久化 |
本章,我们讨论如何在连接和断开连接的情况下插入、更新和删除实体。包括单个对象的更新,例如一个customer和复杂关系图的更新,例如一个order和它的details。本章结束的时候,你就可以使用EF处理更新了。
让我们开始讨论持久化过程是如何工作的吧。
7.1 使用SaveChanges持久化实体
实体持久化是存储实体数据到数据库的过程。触发这个过程很简单,只需要调用ObjectContext类的SaveChanges方法即可。
下面的代码片段显示了如何使用SaveChanges持久化修改的customer:
var customer = (Customer)(from c in ctx.Companies where c.CompanyId == 1 select c);customer.Name = "New Name";ctx.SaveChanges();
使用SaveChanges很简单。但它被调用时,在内部遍历所有处于Modified,Deleted和Added状态的实体,生成合适的SQL语句并在数据库里执行。
SaveChanges执行持久化实体所需的所有管道。不仅仅同步state manager和实体,还检测dirty实体(处于Added,Modified和Deleted状态的实体),启动与数据库的连接和事务,生成正确的SQL,以及提交或者回滚操作。最终,它移除deleted的实体,设置added和modified的实体为Unchanged。最后,这是一个复杂的过程,需要使用数据库,state manager和SQL完成任务,如下图所示:
下面让我们看看每一步,从第一个开始。
7.1.1 检测dirty实体
Dirty实体是处于Added,Modified或者Deleted状态的实体。第六章已经知道了,实体和它们关联的实体在state manager中并不总是同步的。SaveChanges方法需要同步它们。
这一步是持久化过程中最简单的。SaveChanges方法调用DetectChanges方法(第六章介绍了)使实体和它们关联的实体同步。当DetectChanges结束工作,SaveChanges方法为处于Added,Modified和Deleted状态的实体查询state manager检索entry,以便可以持久化它们。
看下面的代码摘录:DetectChanges();var entries = ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Modified | EntityState.Deleted);
由查询state manager返回的entry首先包含added实体,其次是modified实体,最后是deleted实体。
7.1.2 启动数据库事务
持久化过程的这一步,SaveChanges打开数据库的连接,开启数据库事务。在下一阶段执行的所有命令都在事务的上下文中执行。
第8章学习如何自定义事务管理 |
这一阶段很简单。我们继续讨论最复杂的一个阶段。
7.1.3 SQL代码生成和执行
SQL代码生成和检测dirty实体或者开启事务比起来复杂的多。这里通过遍历调用GetObjectStateEntries方法返回的entry,为它里面的每一个实体生成合适的SQL代码。
当持久化Added状态的实体时,生成的SQL代码不难,只是一个简单的INSERT SQL语句。
持久化Modified状态的实体就会变得复杂了。因为state manager保持跟踪每个加载实体的原值和当前值,EF生成只更新修改的属性的UPDATE命令。这是一个伟大的优化,节省了很多工作。
最简单的SQL代码可能是为处于Deleted状态生成的DELETE命令。SQL代码使用key属性删除一个对象。
SQL的生成和执行是一个迭代过程,因为SQL语句经常需要使用前面的数据。例如,order和它的detail。插入每一个detail,EF需要之前保存的order的ID,所以只有order已经持久化后才生成order detail的SQL代码。
7.1.4 数据库事务提交或回滚
如果上一步的每个SQL命令都正确执行,数据库事务就会提交。但是在SQL语句执行时出现一个错误,事务就会被回滚。
错误出现的原因有很多。例如,数据库临时断开或者网络问题。这些都是常见的硬件问题,软件问题的处理方式完全相同。重复键或者不可空的列接受了一个null值都会导致执行流程中断,事务回滚。
这些错误都是有数据库引起的,即使数据库没有引发异常,但是还有其他类型的错误导致持久化过程的突然中止,:并发异常。这是软件异常,它由EF自身引发。我们在第8章讨论并发。
7.1.5 提交实体
默认情况下,如果事务正常结束,上下文调用AcceptAllChanges方法。在其调用之后,entry就和实体和数据库同步了,所以你可以认为这是一个实体提交。处于Added或者Modified状态的实体变成Unchanged,Deleted状态的实体从上下文中移除。
你可以使用SaveChanges的重载操作这个提交过程。SaveChanges的重载接收一个SaveOption(命名空间为:System.Data.Objects)类型的枚举。它有三个可能的值:
- None—DetectChanges和AcceptAllChanges两个方法都不调用。
- AcceptAllChangesAfterSave—调用AcceptAllChanges。
- DetectChangesBeforeSave—在saving changes之前调用DetectChanges。
这个枚举是一个标识语法,所以可以组合值。
没有参数传递给SaveChanges时,AcceptAllChangesAfterSave和DetectChangesBeforeSave自动传递。 |
在前面有提到,如果在持久化期间出现错误,事务就会回滚。这种情况下,AcceptAllChanges不会被触发,所以如果再次调用SaveChanges方法,上下文会再一次尝试持久化实体,当出现异常时会以同样的方式处理。
如果在持久化期间没有出现错误,AcceptAllChangesAfterSave不会传递给SaveChanges方法,state manager里的实体和entry保持不变,所以对SaveChanges的再次调用会再次触发持久化。
7.1.6 重写SaveChanges
SaveChanges是virtual的,可以在上下文中加入任何你需要的逻辑重写它,甚至完全重写持久化逻辑。重写SaveChanges对构建一个记录系统非常有用。它还可以用于调用实体上的方法,当更新、添加或删除时就通知它。这不能完成,尤其是这个方法通过接口或者基类公开。
如果不想完全重写SaveChanges逻辑,记住调用基本实现。 |
现在你已经掌握了EF如何持久化实体所有基础知识。下面一节,在实际中看看这些概念,看看它们是如何影响数据库的。