管理实体状态
上下文仅仅自动处理Unchanged状态到Modified状态的转变。其他的状态转变必须使用适当的方法显示处理:
AddObject——在Added状态时给上下文添加一个实体。
Attach——在Unchanged状态时附加一个实体到上下文。
ApplyCurrentValues和ApplyOriginalValues——改变状态为Modified,将追踪的实体与另一个比较。
DeleteObject——标记一个实体为Deleted。
AcceptAllChanges——标记所有的实体为Unchanged。
ChangeState和ChangeObjectState——改变一个实体从一个状态到另一个状态没有任何限制(Detached除外)
Detach——从上下文移除一个实体。
这些方法在ObjectContext和ObjectSet<T>类中公开,AttachTo和ChangeState除外。ObjectSet<T>方法在内部调用上下文的方法,所以两者没有什么区别。
下面我们详细看一下每一个方法。
AddObject方法
AddObject允许在Added状态时给上下文添加一个实体。当实体被添加到上下文,为了修改它会被添加到上下文追踪。当持久化过程被触发,上下文使用INSERT命令保存对象为表中的一个新行。在OrderIT例子中,持久化一个order会引起对Order表的INSERT,然而持久化一个shirt会引起对Product和Shirt表的INSERT。
AddObject上下文方法接受两个参数,实体集的名称和实体。
public void AddObject(string entitySetName, object entity)
在这段代码中至少有两个缺点。首先,实体集的名字是以字符串传递的。如果输入错误,会在运行时得到异常。第二,Object类型的实体参数,意味着你传递任意CLR类型,如果这个对象不正确只有在运行时才会得到异常。
在强类型时代,这样一个API是难以忍受的。为了克服这个糟糕的设计,EF团队在实体集接口中引进了一个等价的API。它只接受需要添加的对象:
public void AddObject(TEntity entity)
TEntity是由实体集维持的实体类型(记住一个实体集是实现IObjectSet<T>接口类型的实例)。因为强类型,你无法传递一个不正确的对象给方法。此外,实体集知道它的名字,没有必要指定它。
Attach方法
在Unchanged状态时,Attach方法将对象附加到上下文。当实体被附加到上下文,它就由上下文追踪对标量属性的修改。
现实世界中的应用程序,一个实体需要被附加有很多情形。Web应用程序和Web服务就是典型的例子。回到前面的例子,在检索客户的方法中,创建一个上下文,执行查询,返回对象给客户端,然后释放上下文。在更新的方法中,创建一个新的上下文,然后将customer附加给它。最后,持久化customer。
回到Attach方法,Attach上下文方法接收两个参数:实体集的名称和实体。这个方法遭受AddObject同样的限制,所以已经过时了。取而代之,可以使用Attach实体集方法,它只需附加对象,看下面的清单:
var c = new Customer { CompanyId = 1 }; ... ctx.Companies.Attach(c);
你附加一个对象给上下文,因为你想它的数据在数据库中被更新。当然,对象必须在数据库中有对应的行,这个对应由主键属性和列标识的。
当附加一个对象给上下文时,主键列必须设置,否则会在运行时得到一个InvalidOperationException。而且,在持久性阶段,如果UPDATE命令不能作用于任何行,上下文会抛出异常。
因为附加的实体要离开Unchanged状态,你必须找到一种方式标记它为Modified在数据库里持久化它。下一个要讨论的方法完成这个工作。
ApplyCurrentValues和ApplyOriginalValues方法
在我们的客户web服务例子中,当已经附加了对象,就需要持久化它。问题是,它被附加后还是Unchanged状态,所以需要找到一种方式改变它为Modified状态。最简单的方式是在数据库中查询最新的数据并将它与输入的实体比较。
你知道对象上下文保持有一个对每个实体的引用,这个实体或者是从数据中检索出的或者是通过AddObject或者Attach方法附加的。我们没有提到的是,当实体被绑定到上下文时,标量属性的原值(original values)和当前值(current values)保存在内存中。
ApplyOriginalValues方法将实体作为输入(来自数据库)。然后这个方法从上下文的内存中检索一个相同类型以及具有相同键的对象作为输入实体。最后,该方法复制输入实体的标量属性的值给上下文实体的标量属性的原值。目前,保存在上下文中标量属性的原值包含来自数据库的数据,然而保存在上下文中的标量属性的当前值包含来自web服务的实体的值。如果原值不同于当前值,实体就被设置为Modified状态;否则它仍然是Unchanged。
也可以按照相反的路径。查询数据库并且从web服务的实体应用修改代替附加实体并且查询数据库。这是ApplyCurrentValues方法所做的事情。它将一个实体作为输入(来自web服务)。然后该方法在上下文内存中检索一个相同类型以及具有相同键的对象作为输入实体。最后,该方法复制输入实体的标量属性值到上下文实体的标量属性的当前值。目前,保存在上下文的当前值包含来自web服务实体的数据,并且原值是来自数据库的值。如果它们不同,实体就被设置为Modified状态,否则,它仍然是Unchanged。
当持久化被触发,如果实体是Modified状态,它就用UPDATE命令持久化。
如我们前边讨论的方法,ApplyOriginalValues和ApplyCurrentValues方法属于ObjectContext和ObjectSet<T>类,我们建议使用后者公开的方法,如下:
var entityFromDb = GetEntityFromDb(entityFromService.CompanyId); ctx.Companies.Attach(entityFromService); ctx.Companies.ApplyOriginalValues(entityFromDb); ctx.Companies.First(c => c.CompanyId == entityFromService.CompanyId); ctx.Companies.ApplyCurrentValues(entityFromService);
这里你必须意识到有一点点的陷阱。两个方法仅仅关心输入实体的标量和复杂属性。如果一个关联实体的标量属性改变,或者在关联集合中一个新行被添加,移除或者修改,它不会被检测到。
DeleteObject方法
DeleteObject方法标记一个实体为Deleted。唯一需要注意的是你必须牢记传递到该方法的实体必须附加到上下文。该对象必须来自查询或者已经使用Attach方法附加到了上下文。如果在上下文中没有找到该对象,就会抛出一个InvalidOperationException异常,附带一条信息:The object cannot be deleted because it was not found in the ObjectStateManager。
下面的清单显示了使用由ObjectSet<T>类公开的DeleteObject方法。
var c = ctx.Companies.OfType<Customer>().Where(w => w.CompanyId == 1); ctx.Companies.DeleteObject(c); var c = new Customer { ... }; ctx.Companies.Attach(c); ctx.Companies.DeleteObject(c);
当DeleteObject被调用,实体没有从上下文删除;它被标记为deleted。当持久化被触发,实体从上下文移除并且执行DELETE命令从数据库删除它。
AcceptAllChanges方法
AcceptAllChanges方法接受所有Added和Modified状态的实体并标记它们为Unchanged。然后分离所有Deleted状态的实体,最后更新ObjectStateManager条目。
AcceptAllChanges由ObjectContext公开,在ObjectSet<T>类中没有对用的方法。这就是为什么需要使用下面的代码:
ctx.AcceptAllChanges();
ChangeState和ChangeObjectState方法
ChangeState和ChangeObjectState方法是灵活的方法。它们允许改变一个实体的状态到任何其他可能的状态(Detached除外)。当使用一个实体时,这些方法非常有用。不过当处理复杂的对象图时,它们的重要性也增加,这在本章后面讨论。
ChangeState由ObjectStateEntry类公开,而ChangeObjectState由ObjectStateManager类公开。ChangeState只需要新的状态,因为ObjectStateEntry的实例已经指的是一个实体。ChangeObjectState接受实体和新的状态作为参数。两个方法如下面的清单所示:
var osm = ctx.ObjectStateManager; osm.ChangeObjectState(entity, EntityState.Unchanged); osm.GetObjectStateEntry(entity).ChangeState(EntityState.Unchanged);
这些方法并不总是物理的更改实体状态;有时使用先前的方法。例如,改变一个实体的状态为Unchanged意味着调用ObjectStateEntry类的AcceptChanges方法。相反,改变一个实体的状态从Unchanged到Added意味着改变状态。
有时并不需要实体被持久化或者由上下文追踪修改。如果那样,可以将实体从上下文移除。
Detach方法
Detach方法从上下文追踪的实体的列表移除实体。不管实体处于什么状态,它都会变成Detached,但是由分离的(detached)实体引用的实体不能分离(detached)。
调用该方法非常简单,如下面的清单,因为它只接受必要分离的实体。
ctx.Companies.Detach(c);
成功分离的前提条件是实体已经被附加到了上下文。如果没有,就会得到一个InvalidOperationException异常,附带一条信息The object cannot be detached because it is not attached to the Object-
StateManager。