LINQ to SQL系列Part 4 - Updating our Database
本文转载自:
http://www.cnblogs.com/hanxianlong/archive/2007/11/19/962696.html
英文原贴链接:
http://weblogs.asp.net/scottgu/archive/2007/07/11/linq-to-sql-part-4-updating-our-database.aspx
上个月我开始了一个讲解LINQ to SQL的帖子系列。LINQ to SQL是集成在.NET Framework3.5中的O/RM(对象关系映射)的实现,它让你非常容易地用.NET类来生成关系型数据库的模型。然后你可以用LINQ 表达式对它来进行查询,更新,插入删除。
在今天的帖子中,我将会讲解一下如何使用我们在前几篇帖子中生成数据模型,并且用它来更新、插入、删除数据。我还会展示一下如何将我们的业务规则和自定义的验证与我们的数据模型进行集成。
用LINQ to SQL建成的Northwind数据库模型
在本系列的第二部分(Part 2)中我讲解了如何用内置在VS2008中的LINQ to SQL设计器来生成一个LINQ to SQL类模型。下边是用LINQ to SQL设计器生成的一个Northwind事例的模型,在本篇博客中将会用到它。
在用LINQ to SQL数据设计器设计数据模型时,我们定义了一个数据模型类:Product,Category,Customer,Order和OrderDetail。类的属性映射到数据库相应的数据表的列上。每个类的实体代表了数据表的一条记录。
当我们在定义数据模型时,LINQ to SQL设计器也自动生成了一个DataContext类,该类提供了我们查询数据库和提交更新及变更的一个主要通道。在我们上面定义的数据模型事例中,这个类的名字叫"NorthwindDataContext",NorthwindDataContext类含有代表了在数据库中我们建立模型的表的属性(具体来说就是:Product,Categories,Customers,Orders,OrderDetails)。
在本系列的第3部分(Part 3)中我讲过,我们可以用这个NorthwindDataContext类通过方便地写LINQ语法表达式来对数据库进行查询和检索数据库。LINQ to SQL将会在运行时自动地将LINQ 表达式转换为适当的SQL语句。
例如,我可以写如下的LINQ 表达式来通过Product名称对产品查询出一个Product对象:
我可以写如下的LINQ 查询表达式来完成满足以下条件的查询:没有订单的,单价超过100美元的。
注意上面我是如何用每个产品的"OrderDetails"这个关联来查询的一部分,并通过它来查出那些尚未有订单的产品记录。
变更跟踪和DataContext.SubmitChanges()方法
当查询并检索出像上边的Product实例的对象时,LINQ to SQL将会默认地保持着对该对象任何的我们对这些对象所做的变化或更新的跟踪。通过LINQ to SQL的DataContext我们可以写许许多多的查询和更新,这些变化将会被保存在一起。
注意:LINQ to SQL的变化追踪只在调用端被保存――而不是在数据库端。这意味着在使用它时,你既不需要任何的数据库资源,也不需要在数据库中更改/安装任何的东西。
在对我们从LINQ to SQL中查询出来的对象进行了变化之后,我们可以选择调用DataContext的SubmitChanges()方法来将更新返回给数据库。这将会导致LINQ to SQL动态地生成并执行适当的SQL语句来更新数据库。
例如,我可以写如下的代码来更新数据库中的产品名称为"Chai"的产品的价格和存储数量:
在调用Northwindo.SubmitChanges()时,LINQ to SQL将会动态地构建LINQ to SQL语句并执行一个SQL语句中的"UPDATE"声明,该声明将会更新我们上面修改的两个属性。
然后我可以写如下的代码来通过遍历不受欢迎的,贵的产品,将它们的"ReorderLevel"属性设置为0:
当我调用northwind.SubmitChange()方法时,LINQ to SQL将会生成并执行的将ReorderLevel属性变化的产品记录的相应字段更新的语句。
注意,如果一个产品的属性值没有被上边的属性声明给更改,那个这个对象将不会被认为被更改过了,LINQ to SQL也因此不会为那条产品到数据库中执行一条Update语句。例如,如果"Chai"产品 的单价已经是2美元,并且库存数量也是4,那么调用SubmitChanges()方法将不会执行任何的Update语句。同样的,第2个事例中,只有那些ReorderLevel还不是0的产品才会在调用SubmitChange()方法时被更新。
插入和删除事例
除了允许你更新在数据库已有的数据记录之后,LINQ to SQL显然也允许你向数据库中插入和删除数据库。你可以通过在DataContext的表集合中添加/删除对象,然后调用SubmitChanges()方法来完成对数据插入和删除。LINQ to SQL将会跟踪这些添加和删除的动作,并且当SubmiChanges()被触发时执行适当的Insert或Delete的SQL语句。
插入一条新产品
我可以通过如下方式来向数据库中添加一条新的产品记录:生成一个新的"Product"类的实例,设置其属性,然后将它添加到DataContext的"Product"集合中:
然后调用上面的SubmitChanges()方法时,一条新记录将会添加到Products数据表中。
删除产品记录
就像上面我可以通过将一个Product对象添加到DataContext的Products集合中来实现添加一条记录到数据库中那样,我同样也可以通过从DataContext的产品集合中移除一个Product对象来完成对数据库中记录的删除:
注意上面我用LINQ查询来检索出一个不再出售的,订单数量为0的产品结果集,然后将它传到DataContext的Products集合的RemoveAll()方法中。当我们调用 SubmitChanges()访求时,所有的这些记录将会从数据库中删除。
通过关系进行更新
使得O/R映射器,比如LINQ to SQL,特别灵活的是它们允许我们非常方便地通过我们的数据模型来对交叉的表间关系进行建模。例如,我可以将每个Product放到一个Category中,每条Order可以包含OrderDetails,将每个OrderDetail的数据项跟产品关联起来,每个有一套订单。在本系列的第二部分(Part 2)我讲解了如何组织并生成这些关系。
LINQ to SQL使得我在查询和更新我的数据时均可以使用这些关系。例如,我可以写如下的代码来生成一条新产品记录,并将它与在数据库中已经存在的"Beverages"类别关联起来:
注意上面我如何将一个Product对象添加到类别的Products集合中。这表明了在两个对象之间存在着关系,并且使得在调用SubmitChanges()方法时LINQ to SQL自动地维护外键/主键关系。
另外一个关于LINQ to SQL如何帮助我们管理表间交叉的关系并且使得我们的代码更清洁的例子,让我们看一下下面的一个为已经存在的客户生成一条订单的事例。在设置了请求的时间和订单的运费之后,我生成了两个指向客户订下的产品的Order对象。然后将这个订单和客户关联起来,并且将所有的这些变更更新至数据库。
正如你看到的,实现所有这些工作的编程模型特别的简单,并且是面向对象的。
事务
事务是数据库(或者其他的资源管理器)提供的一种服务,该服务能保证一系列单独的行为自动的触发――也就是要么它们全部成功,要么就不成功,如果不成功的话,它们将会在允许做其他事情之前自动地回滚。
当你调用DataContext的SubmictChanges()方法时,更新部分总是会被包装在一个事务中。这意味着如果你执行了多步变更的话,你的数据库永远不会处于不一致的情况――要么将你所有对DataContext做的变化保存至数据库,要么全不保存。
如果没有已经排除的事务,LINQ to SQL DataContext将会在调用SubmitChanges()方法时自动地开启一个事务来进行更新。另外,LINQ to SQL也允许你显示地声明并使用你自己的事务处理对象(在.Net2.0中介绍的一个特性)。这使得将LINQ to SQL代码和现有的数据访问代码集成变得更加容易。也意味着你可以将一个非数据库资源添加到数据库中――例如,你可以发送一条MSMQ消息,更新文件系统(用新的事务文件系统支持)等――并且将所有的这些工作放到同你用LINQ to SQL更新你数据库的一个事务中。
验证和业务逻辑
开发者在跟数据打交道时需要考虑的一个重要的事情就是如何将验证和业务逻辑结合起来。幸运的是,LINQ to SQL支持开发者清晰地将它们集成在数据模型中的各种各样的方法。
LINQ to SQL使得你添加一次这些验证逻辑――然后你就可以不用管你生成的数据模型如何被使用,也不用管在何处被使用。这避免了你在我处来重复你的验证规则,而且使得数据模型更具有可维护性和简洁性。
架构验证的支持
当使用VS2008中的LINQ to SQL设计器来定义数据模型为时,它们会被默认地加上从数据表中反映出来的验证规则。
数据模型类中的属性的数据类型要和数据库中的数据项的数据类型相一致。这意味着如果你试图将一个boolean类型赋值给一个decimal类型的值,或者错误的隐示地转换一个数据类型时,你就会得到一个编译错误。
如果数据库中的一列被标志为可空,那么数据模型类相应的属性将是一个可空的类型。如果你试图对没有被标为可空的列赋空值,将会自动抛出异常。 LINQ to SQL 将同样确保标识/唯一列被正确的赋值。
如果你乐意的话,你可以使用LINQ to SQL 设计器覆盖默认的框架验证设置。但是在默认情况下,这是自动的并不需要额外的步骤来启用它。LINQ to SQL 也会为你自动管理SQL语句-所以在使用时你不必担心SQL注入。
自定义属性验证支持
数据库框架验证作为第一步非常有用,但是在实际的场景中并不足够。
例如考虑这一场景,"Customer"类有一个"phone"的属性,被定义为NVarchar类型,开发者编写如下代码,用一个合法的电话号码来对它进行更新:
然而,这种更新在程序中是行得能的,从SQL语句上来说,如下代码依然是合法的(因为它依然是一个字符串,虽然不是一个电话号码)
为避免错误的号码插入到数据库中,我们可以向Customer数据类型类中加入自定义的属性验证规则。用这个特性来添加一条对电话号码的验证规则确实非常简单。我们所需要做的是在项目中加一个新的部分类来定义以下方法。:
以上代码利用了LINQ to SQL 两个特性:
1) LINQ to SQL 设计器生成的类都被声明为"局部"类 -这意味着开发者可以向这些总局类中很方便的增加方法,属性,及事件。因此验证规则和用户自定义的辅助方法可以很方便的加入数据模型类和DataContext类中。不需要设置或者代码绑定。
2) LINQ to SQL 揭示了数据模型类及DataContext类中很多用户可扩展的地方,在这些地方可以添加验证逻辑。可扩展的地方利用了一个新的语言特性,称之为“部分方法”,这是VS2008Beta2 VB和C#引入的。Wes Dyer 在他的日志中对“部分方法”是如何工作的有很好的解释(here.)。
在以上验证的示例中,我使用 OnPhoneChanging 部分方法,此方法将在程序设置“Customer”对象的“Phone”属性时执行。 如果我想的话我可以使用这个方法来验证输入—(在此使用正则表达式)如果一切正确,将从此方法返回而且LINQ to SQL 将认为该值合法。如果有什么错误发生,在验证方法中抛出一个异常-这将阻止赋值。
自定义实体对象验证支持
以上场景中的属性级验证对验证数据模型类的彼此独立的属性非常有用。有时,你需要验证互相作用的多个属性的值。
例如,设置订单对象的 "OrderDate" 和 "RequiredDate" 属性:
上面代码对于SQL数据库来说是合法的- 尽管将传递的时间设置为订单时间的昨天是没有任何意义的。
幸好,在LINQ to SQLBeta2 版本中,通过增加自定义实体级验证规则可以防止此类错误的发生。我们可以给“Order”实体增加一个部分类并且实现OnValidate()的部分方法, 此方法将在实体值被保存在数据库中之前调用。在此验证方法中我们可以访问和验证数据模型类所有的属性。
在验证方法中可以检查实体的属性值(甚至只读访问其关联的对象),如果值不合法的话将抛出异常。任何抛出的异常将中止保存到数据库,并且回滚同一事务中其他的改动。
自定义实体插入/更新/删除方法验证
经常你需要的验证逻辑不单单是在独立的插入/更新/删除操作中。LINQ to SQL Beta2允许你将“局部方法”添加到DataContext类中来为你的添加更新和删除逻辑添加自定义的验证。这些方法在调用SubmitChanges()时将会被自动调用。
在这些方法中,你可以加入适当的验证逻辑。 - 如果验证逻辑通过,它将通知 LINQ to SQL保存相关的改动到数据库。 (通过调用DataContext的"ExecuteDynamicXYZ" 方法):
一个比较好的地方是,以上方法在数据对象创建/更新/删除时自动匹配执行。例如,如下示例, 我们创建一个新的定单关联到一个存在的客户。
在我们调用Northwind.SubmitChanges() 时,, LINQ to SQL 将决定保存新的定单对象, "InsertOrder"部分方法将被自动调用。
高级:事务中全部的发动列表
经常你需要的验证逻辑不单单是在独立的插入/更新/删除操作中。反而,你需要检查在同一事务中发生的所有改动。
从 .NET 3.5 Beta2,LINQ to SQL开始,你可以通过调用DataContext.GetChangeList() 访问所有的改变列表。此方法返回一个ChangeList 对象,该对象包含了增加,删除及修改的集合。
在更高级的场景中,你可以继承DataContext类,重写SubmitChange()方法。在保存到数据库之前,通过获取更新操作的ChangeList()进行自定义的验证
以上是一个高级的场景--但是最好你要在需要的时候 ,你可以实现并能利用它。
乐观并发控制
开发者需要关心的一点是,在多用户的数据库系统中,如何处理同一数据同时更新。例如,假定两个用在以各应用中获取一个产品对象,其中一个用户设置RecorderLevel为0,而另外一个用户设置为1。如果两个用户试图将此产品保存到数据库,开发者需要决定如何处理这个改动的冲突。
一种解决方法是 "最后写入者赢" - 这意味着第一位用户没有的到提示的情况下,其提交的值将被丢掉。通常这被认为是一个不好(不正确)的应用经验。
另外一个方法是 LINQ to SQL 支持乐观并发模型。 - 在保存新值之前,LINQ to SQL 将自动探测原值已被其他人修改. LINQ to SQL 然后可以提供一个改变值得冲突列表,这样开发者或者协调数值之间的差距,或者在UI中提示用户要如何处理。
在以后的帖子中我将讲解如何用LINQ to SQL处理乐观并发冲突。
用存储过程或者自定义的SQL语句来插入/更新/删除记录的情景
那些过去用SQL语句写自定义的存储过程(尤其DBA们)在第一次见到LINQ to SQL时经常问的一个问题是—"可是我如何完全地控制它执行的SQL语句?"
有一个好的消息,就是LINQ to SQL有一个非常之灵活的模型,该模型允许开发者们重写LINQ to SQL动态生成的SQL语句,用他们自定义的插入/更新/删除的存储过程来替代那些生成的SQL语句.
真正好的一点是你可以通过定义你的数据模型来让LINQ to SQL自动的处理插入/更新/删除的SQL逻辑。你可以为更新定义一个你自己的存储过程或者SQL语句-这样在以后更新应用程序时你可以既不必去更改应用了你的数据模型的应用程序的逻辑,又不必去更改任何的验证或者业务逻辑(所有的这些使用原有的即可)。这在你如何构建应用程序上提供了很大的灵活性。
在接下来的一篇帖子中我将会讲解一下如何自定义你的数据模型来使用存储过程或者自定义的SQL语句。
总结
希望上面这篇帖子给你提供了如何用LINQ to SQL方便地更新你的数据库的,如何清晰地将你的验证及业务逻辑和你的数据模型结合起来的一个好的概述。我想你会发现,在和数据打交道时,LINQ to SQL会极大地提高你的生成力,并且能够让你写出非常整洁的面向对象的数据访问的代码。
在本系列的下面的帖子中,我将讲述一下在.Net3.5中的新增的<asp:linqdatasource>控件,并且讲一下在使用LINQ to SQL的数据模型的情况下在ASP.NET中建立一个数据的UI是多么地方便!我会更近一步地讲术一下LINQ to SQL的编程思想,包括优化并发冲突,延迟和提前加载,表映射的继承,自定义的存储过程的使用等。
希望这些能对你有所帮助
Scott