Entity Framework 4 in Action读书笔记——第七章:持久化对象到数据库:持久化修改的实体到数据库

7.2 持久化修改的实体到数据库

持久化单个实体到数据库有三种方式:

  • 持久化为一个新行。
  • 使用属性更新一个存在的行。
  • 使用键值属性(key properties)删除一个存在的行。

7.2.1 持久化为一个新行

首先,添加客户。非常简单:使用AddObject方法,传递一个Customer实例,然后调用SaveChanges。

var cust = new Customer
{
    Name = "Stefano Mostarda",
    BillingAddress = new AddressInfo()
    {
        Address = "5th street",
        City = "New York",
        Country = "USA",
        ZipCode = "0000000"
    },
    ShippingAddress = new AddressInfo()
    {
        Address = "5th street",
        City = "New York",
        Country = "USA",
        ZipCode = "0000000"
    },
    WSEnabled = true,
    WSUserName = "user1",
    WSPassword = "user1pwd"
};
ctx.Companies.AddObject(cust);
ctx.SaveChanges();

因为主键属性是identity,所以没有必要设置它的值;如果设置了,值也会被忽略。

注意 倘若key不是identity,就需要设置它,否则在运行时会得到InvalidOperationException异常。

持久化一个新的实体并不难处理。即使持久化过程涉及复杂类型,上下文都能很好的处理。

7.2.2 持久化对现有实体的修改

连接情况下的持久化

连接情况是指在同一个上下文中修改从数据库检索出的实体。在这种情况下,可以修改属性,然后调用SaveChanges方法。

var cust = ctx.Companies.OfType<Customer>()
            .First(c => c.CompanyId == 1);
cust.Name = "Stefano Mostarda";
ctx.SaveChanges();

因为state manager跟踪实体属性的原值和当前值,所以SQL只影响修改了的属性映射的列。当修改复杂类型中的标量属性时,复杂类型的所有属性都会持久化。举个例子,如果修改shipping address,SQL会更新shipping city,country和ZIP code,即使它们没有被修改。

断开连接情况下的持久化

假设有一个方法,它接收一个Customer实例作为参数。你创建一个新的上下文,附加customer,然后持久化它。问题出来了,当你附加实体到上下文时,它是Unchanged状态,SaveChanges不会持久化任何东西。

有两个方法解决这个问题:

  • 附加传入的实体,使用ChangeObjectState方法修改它的状态为Modified。
  • 查询数据库检索实体,然后使用ApplyCurrentValues方法用传入实体的属性重写数据库实体的属性。

第一种方法最简单也最常用。

void UpdateCustomer(Customer cust)
{
    using (var ctx = new OrderITEntities())
    {
        ctx.Companies.Attach(cust);
        ctx.ObjectStateManager.ChangeObjectState(cust, EntityState.Modified);
        ctx.SaveChanges();
    }
}

这个方法的缺点是ChangeObjectState标记实体以及它的所有属性都为Modified。结果,UPDATE命令修改所有的映射列,即使它们没有被修改。如果一个属性没有设置值,数据库中的值就会被重写,造成数据丢失。

还是看个例子。假设有一个ID为1的customer。你创建了一个Customer的实例,并设置CompanyId为1和Name为NewName。然后将它附加到上下文,标记为Modified并调用SaveChanges。生成的UPDATE命令会更新Name列并且其他列也会使用它们的默认值更新(字符串为null,整数为0,等等)。也就是说,数据库中customer之前的数据就丢失了。下图说明了这个问题:

image

第二种方法即使比第一种方法稍微复杂,也是很简单的。

对于第二种方法,查询数据库检索出需要修改的实体,然后使用ApplyCurrentValues方法应用修改实体的值。最后,调用SaveChanges。当修改被持久化时,生成最优化的SQL语句,因为state manager知道哪个属性已经被修改了。

void UpdateCustomerWithApplyCurrentValues(Customer cust)
{
    using (var ctx = new OrderITEntities())
    {
        ctx.Companies.OfType<Customer>()
        .First(c => c.CompanyId == cust.CompanyId);
        ctx.Companies.ApplyCurrentValues(cust);
        ctx.SaveChanges();
    }
}

可惜,ApplyCurrentValues和ChangeObjectState有同样的问题,因为使用传入实体的值重写entry的当前值。如果传入实体的属性没有设置,结果一样会造成数据丢失。

ApplyCurrentValues还有另外一个问题。它涉及两个对数据库的往返过程:一个读取数据,另一个更新数据。这样会造成性能的损失。尤其是数据涉及到多个表时,这个问题会变得越来越严重。就拿OrderIT这个例子来说吧,查询需要关联多个表,根据什么属性改变了更新也影响到一个或多个表。如果对性能要求很高,你可能要选择ChangeObjectState方法,因为更新过程只需操作数据库一次。

最后,这两个方法都工作在一些情况下,但是如果滥用就会导致数据丢失。如果提前知道一些属性没有设置,可以有两个方法设置:

  1. 从数据库检索customer,根据传入实体的值一个一个的手动修改它的属性。这个方法行之有效,因为它跟连接情况相似。
  2. 附加传入实体,显示标记要更新的属性。这个方法消除了到数据库中检索实体以及数据丢失的危险。

第二个方法最好。它需要一些代码,但是很有效,因为它在避免数据丢失的同时又提供了最好的性能。

在断开连接的情况下选择要更新的属性

更新customer的name和address。为此,你可以创建一个附加了实体并且标记指定属性为Modified的方法。这个标记过程也自动地将实体设为Modified状态。当调用SaveChanges方法,只有指定的属性被持久化到数据库。

标记一个属性为Modified的方法是SetModifiedProperty。它只接收一个参数,表示要更新的属性名称。

var entry = osm.GetObjectStateEntry(customer);
entry.SetModifiedProperty("Name");
entry.SetModifiedProperty("ShippingAddress");
entry.SetModifiedProperty("BillingAddress");

持久化实体删除

删除一个实体非常简单。调用DeleteObject方法,传入一个实体来标记实体为Deleted。然后调用SaveChanges。记住,在被标记为Deleted之前,实体必须附加到上下文。

连接情况下的删除

var cust = ctx.Companies.OfType<Customer>().First();
ctx.DeleteObject(cust);
ctx.SaveChanges();

断开连接情况下的删除

var cust = new Customer() { CompanyId = id };
ctx.Companies.Attach(cust);
ctx.DeleteObject(cust);
ctx.SaveChanges();

现在可以按照需要修改customer的数据了,但这仅仅是一个开始,下面会涉及到关联实体的持久化。

posted @ 2011-11-09 14:47  BobTian  阅读(2589)  评论(7编辑  收藏  举报