12.4 操作对象
在上面两节阐述了如何使用LINQ to Entities与Entity SQL对概念模型执行查询并以对象的形式返回数据。下面阐述如何使用对象服务创建、更改和删除对象上下文中的对象,如何处理数据源中的并发以及如何将对象绑定到控件。
12.4.1 创建和添加对象
要在数据源中插入数据,必须创建实体类型的实例,并将该对象添加到对象上下文。若要将新对象保存到数据源中,必须先设置不支持null值的所有属性。使用实体框架生成的类时,可以考虑使用实体类型的静态Create方法创建实体类型的新实例。如下面的代码所示:
public static Employee CreateEmployee(
global:System.Decimal employeeid,
global:System.String employeename)
{
Employee employee=new Employee();
employee.employeeid=employeeid;
employee.employeename=employeename;
return employee;
}
实体数据模型工具生成实体类型时,会在每个类中包含此方法。该方法用于创建对象的实例并设置此类的不能为null的所有属性。它对于在CSDL文件中已应用Nullable="false"特性的每个属性都包含一个参数。使用静态Create方法创建对象的示例如下面的代码所示:
Employee newItem=Employee.CreateEmployee(20,"张云");
创建好对象之后,可以使用以下方法之一将新对象添加到对象上下文中:
1)ObjectSet的AddObject(UTP)方法。
2)ObjectContext的AddObject(String, Object)方法。
3)EntityCollection的Add(UTP)方法。
具体的添加示例如下面的代码所示:
protected void Page_Load(object sender, EventArgs e)
{
using(DataConnectString context=new DataConnectString())
{
Employee newItem=Employee.CreateEmployee(20,"张云");
context.Employee.AddObject(newItem);
context.SaveChanges();
}
}
当然,除了使用CreateEmployee方法来创建新对象之外,也可以这样来创建与添加新对象。如下面的代码所示:
using(DataConnectString context=new DataConnectString())
{
Employee newItem=new Employee();
newItem.employeeid=21;
newItem.employeename="zhanghua";
newItem.department="软件研发部";
newItem.email="zhanghua@163.com";
newItem.address="陕西西安";
context.Employee.AddObject(newItem);
context.SaveChanges();
}
在添加新对象时需要考虑下列注意事项:
1)在调用SaveChanges之前,ADO.NET实体框架会为每个新对象生成一个临时的键值。而调用SaveChanges之后,该键值会被插入新行时数据源所指定的标识值所取代。
2)如果数据源未生成实体的键值,应指定一个唯一值。如果两个对象具有相同的键值,则在调用SaveChanges时会发生InvalidOperationException。如果发生此问题,应指定唯一值并重试该操作。
12.4.2 修改对象
相对于创建和添加对象,修改对象变得非常容易。只需要查找出相关的对象,然后修改相应的属性值,最后调用SaveChanges方法。修改示例如下面的代码所示:
protected void Page_Load(object sender, EventArgs e)
{
using(DataConnectString context=new DataConnectString())
{
decimal employeeid=1;
Employee newItem=context.Employee.Where(
"it.employeeid=@id",
new ObjectParameter("id",employeeid)).First();
newItem.employeename="马伟(修改1)";
context.SaveChanges();
}
}
12.4.3 删除对象
对于删除对象操作,可以先通过调用ObjectSet的DeleteObject(UTP)或ObjectContext的DeleteObject(Object)方法标记要删除的指定对象。然后调用SaveChanges方法来从数据源中删除该行。如下面的示例代码所示:
protected void Page_Load(object sender, EventArgs e)
{
using(DataConnectString context=new DataConnectString())
{
decimal employeeid=11;
Employee emp=context.Employee.Where(
"it.employeeid=@id",
new ObjectParameter("id",employeeid)).First();
context.DeleteObject(emp);
context.SaveChanges();
}
}
需要说明的是,在实体框架中删除对象的行为有所不同,具体取决于对象所属的关系类型。在标识关系中,删除某个对象时还会删除相关的其他对象。删除父对象的同时也会删除所有子对象。如果与父对象没有既定关系,则依赖对象无法存在。
在表示为外键关联的非标识关系中,删除主体对象后,实体框架将依赖对象的可以为null的外键属性设置为null。
12.4.4 保存更改和管理并发
默认情况下,实体框架实现开放式并发模型。这意味着在查询数据与更新数据之间,不对数据源中的数据保留锁。实体框架将对象更改保存到数据库中,但不检查并发。对于可能出现高度并发的实体,建议为实体在概念层定义一个具有ConcurrencyMode="fixed"特性的属性,如下面的代码所示:
<Property Name="employeeid"Type="Decimal"Nullable="false"
Precision="18"Scale="0"ConcurrencyMode="Fixed"/>
在使用此特性时,实体框架会检查数据库中的更改,然后再将更改保存到数据库中。任何有冲突的更改都会引发OptimisticConcurrencyException异常。而定义使用存储过程更新数据源的实体数据模型时,也可能引发OptimisticConcurrencyException异常。在这种情况下,如果用于执行更新的存储过程报告更新了零行,则会引发该异常。
在高度并发情况下进行更新时,建议经常调用Refresh(RefreshMode, Object)方法,该方法可以按照指定模式刷新实体对象。调用方法如下面的示例代码所示:
try
{
int num=context.SaveChanges();
//处理代码
}
catch(OptimisticConcurrencyException)
{
context.Refresh(RefreshMode.ClientWins, emp);
context.SaveChanges();
//处理代码
}
在调用Refresh(RefreshMode, Object)方法时,RefreshMode将控制传播更改的方式。其中:
1)StoreWins选项将导致实体框架使用数据库中的相应值覆盖对象缓存中的所有数据。
2)ClientWins选项则将使用数据源中的最新值替换缓存中的原始值。
这样,通过消除缓存数据的更改与数据源中相应数据的更改之间的冲突,可以确保对象缓存中更改过的所有数据都可以成功地保存回数据源。
如果数据源更新可能会修改属于对象上下文中其他对象的数据,则应在调用SaveChanges方法后调用Refresh(RefreshMode, Object)方法。
实体框架跟踪已对缓存中的对象所做的更改。调用SaveChanges方法时,实体框架会尝试将更改合并回数据源。如果对象缓存中的数据更改与对象添加到缓存后或在缓存中刷新后数据源中发生的更改相冲突,则SaveChanges会失败,从而引发OptimisticConcurrencyException异常。这会导致整个事务回滚。如果发生OptimisticConcurrencyException,应通过调用Refresh(RefreshMode, Object)并指定是否解决冲突[通过将数据保存到对象数据((CientWins)或通过使用数据源数据更新对象缓存((SoreWins)]来处理该异常,示例如上面的代码所示。
如果不能够在数据源中成功创建添加到ObjectContext中的对象,则SaveChanges会引发异常UpdateException。如果具有关系所指定的外键的行已存在,则会出现这种情况。如果出现这种情况,就不能使用Refresh(RefreshMode, Object)更新对象上下文中的已添加对象,而使用MergeOption的OverwriteChanges值来重新加载该对象。