在这之前一起学过LINQ to SQL设计器的使用,下面就使用如下的数据模型:
当使用LINQ to SQL设计器设计以上定义的五个类(Product,Category,Customer,Order和OrderDetail)的时候,每个类中的属性都映射了相应数据库中表的列,每个类的实例则代表了数据库表中的一条记录。另外,当定义数据模型时,LINQ to SQL设计器同样会创建一个自定义DataContext类,来作为数据库查询和应用更新/变化的主要渠道。以上数据模型中定义的DataContext类命名为“NorthwindDataContext”。该类中包含了代表每个建模数据库表的属性。
使用LINQ语法表达式可以十分简单的使用NorthwindDataContext类来查询和检索数据库中的数据。LINQ to SQL会在运行时自动的转换LINQ表达式到适当的SQL代码来执行。例如,编写以下LINQ表达式来根据Product Name检索单个Product对象:
还可以使用LINQ表达式来检索所有不存在于Order Details中的,并且UnitPrice大于100的所以Product:
变化跟踪和DataContext.SubmitChanges()
当执行查询和检索像Product实例这样的对象时,LINQ to SQL会自动保持对这些对象任何变化或更新的跟踪。我们可以进行任意次数的查询,以及使用LINQ to SQL的DataContext类作出更新,而这些变化都会被全部跟踪。
注意:LINQ to SQL的变化跟踪发生于调用者端——而不是在数据库中。这就意味着使用跟踪不会销耗任何数据库资源,也不需要在数据库中改变/安装任何组件模块。
当对从LINQ to SQL中检索的对象作出更改之后,我们可以选择调用DataContext上的SubmitChange()方法来应用变化返回到数据库。这将会导致LINQ to SQL动态计算并执行适当的SQL代码来更新数据库。例如,编写以下代码更新数据库中Product Name为“Chai”的Product上的UnitPrice和UnitsInStock:
当在以上代码中调用northwind.SubmitChanges()方法时,LINQ to SQL会动态构建并执行一个更新这两个Product属性值的SQL“UPDATE”代码模块。
在下面代码中我们来遍历不流行的,昂贵的Product,并把它们的ReorderLevel属性设为0:
当在以上代码中调用northwind.SubmitChanges()方法时,LINQ to SQL会计算并执行一组适当的UPDATE代码模块来修改RecorderLevel属性已变化的Product。
注意,如果一个Product的属性没有通过属性指定而发生变化,则该对象不会被认为是发生变化的,并且LINQ to SQL也不会对于该对象执行更新回数据库的操作。例如,如果“Chai”对象的UnitPrice仍旧是$2,UnitsInStock仍旧是4,当调用SubmitChange()时不会导致任何数据库UPDATE代码模块的执行。相似的,在第二个例子中的那些符合条件的Product中只有RecorderLevel原来不是0的才会在SubmitChange()被调用时更新。
插入和删除示例
除了更新数据库中已存在的行之外,LINQ to SQL同样支持插入和删除数据。可以通过从DataContext的表集合中添加/移除数据对象,并调用SubmitChange()方法来实现数据库中的插入和删除操作。LINQ to SQL也会对添加/移除操作保持跟踪,并当SubmitChange()被调用时自动执行适当的SQL中的INSERT或DELETE代码模块。
插入Product
可以通过创建一个新Product实例添加一个新的Product到数据库,设置它的属性,并添加它到DataContext类中的Product集合中:
当在以上代码中调用northwind.SubmitChanges()方法时,在数据库的Product表中会有一条新的记录被创建。
删除Product
与添加相似,可以通过从DataContext类中的Product集合中移除某一产品来表达想要从数据库中删除相应的记录:
在以上代码中先使用LINQ查询检索一系列不会被订购的Product,然后将其传入DataContext类中Product集合上的RemoveAll()方法。当在以上代码中调用northwind.SubmitChanges()方法时,这些Product记录就会从数据库中的Product表中删除。
通过关系关联更新
像LINQ to SQL这样十分灵活的O/R映射工具,可以让我们很简单的通过表之间的关系关联来对数据模型建模。例如,可以把每个Product建模到一个Category中,每个Order包含多条OrderDetail明细,每条OrderDetail明细都关联着一个Product,并且每个Customer拥有一组相关联的Order。
LINQ to SQL能够让我们不论是在查询还是更新数据中都可以利用这些关系关联。例如,编写以下代码来创建一个新Product,并关联到一个数据库中已存在的“Beverages”Category上:
注意如何添加Product对象到该Category的Product集合中。这样就会指明这两个对象之间存在关系关联,并会导致LINQ to SQL在调用SubmitChange()时,自动维护两者之间的主/外键关系关联。
另一个LINQ to SQL有助于管理交叉表关系关联的例子,让我们看一下如何对一个现有的Customer创建一个新的Order。在设置Order的OrderDate和RequireDate和Freight后,然后创建两个Customer订购的Product相关的OrderDetail,并添加到Order中,随后关联Order到Customer上,最后更新所有变化回数据库:
正如所看到,执行所以这些工作的编程模型是十分清晰并且是面向对象的。
事务
事务是一种通过数据库(或其他资源管理器)提供的服务,来保证了一系列的单独的操作是原子性发生的——这就意味着它们要么全部成功,要么全部失败,并且当这一系列操作都自动执行完毕之前,不会有任何更改会发生。当调用DataContext上的SumbitChange()方法时,这些更新操作会包装到一个事务中。这就意味着当执行多个变化更新时,数据库永远不会陷入不一致的状态——这些变化要么一起被保存,要么任何变化都不保存。
验证和业务逻辑
对于处理数据,开发需要考虑的一件十分重要的事就是如何融合数据验证和业务规则逻辑。LINQ to SQL为开发者提供了多种途径,可以十分简洁地把这些整合到数据模型中。LINQ to SQL会确保一旦添加数据验证逻辑,就可以无论何时何地在都应用于数据模型上。这就避免了在多处的重复定义,保证了数据模型的可维护性和代码整洁。
构架验证支持
当在Visual Studio 2008中使用LINQ to SQL设计器定义数据模型类时,会被默认附加一些根据数据库表构架推断出的验证规则。数据模型类中的属性的数据类型会与数据库构架的数据类型相匹配。这就意味着如果试图指定一个boolean值到decimal,或试图隐式转换numeric类型都会导致编译错误。如果数据库中的列可以为null,则通过LINQ to SQL设计器创建的在数据模型中的相应的属性会是一个Nullable类型。如果试图给未标记为Nullable的属性指定null值,会自动引发异常。相似地,LINQ to SQL也会确保数据库中的identity/unique列值的正确验证。
当然也可以使用LINQ to SQL设计器来覆写这些默认的构架驱动的验证设置——但是通过默认我们可以自动获得它们,而且不需要进行额外的工作。LINQ to SQL同样会自动处理转义的SQL值,这样就不必担心SQL注入攻击了。
自定义属性验证支持
构架驱动的数据验证仅仅是应用的第一步,但对于实际应用来说还是不够的。考虑Northwind中的一种情况——Customer类上的Phone属性,在数据库中被定义为NVARCHAR类型。开发者可以使用LINQ to SQL编写如下代码更新一个有效的电话号码:
但在应用中会碰到这样的疑问,如对于下面的代码,从单纯的SQL构架方面来说是合法的:
为防止虚假的电话号码被添加进数据库,我们可以添加一个自定义属性验证规则到Customer数据模型类中。使用局部类的特性,可以十分简单的添加规则来验证电话号码,只需要添加一个新的包含如下方法定义的局部类到我们的项目中:
以上代码利用了LINQ to SQL的两个特性:
1.所以通过LINQ to SQL设计器创建的类都被声明为局部类——这就意味着开发者可以十分方便地为这些类添加额外的方法,属性和事件。这样对通过LINQ to SQL设计器创建的数据模型类和DataContext类,可以很简单的扩充自已定义验证规则和额外的自定义辅助方法。没有任何设置或后续代码要求。
2.LINQ to SQL会在数据模型类和DataContext类中暴露了一些自定义的可扩展点,用来在事件发生之前和之后添加验证逻辑。其中许多的扩展点利用了称为“partial methods(局部方法)”的新语言特性。
在上面的验证示例中,我们使用了在任何设置Customer类上Phone属性时都会被执行的OnPhoneChanging方法。使用该方法来验证输入,如果验证成功,则从该方法中返回并且LINQ to SQL会认定该值是有效的,否则就会在该验证方法中产生一个异常——防止发生赋值操作。
自定义实体对象验证支持
属性级别的验证对于验证数据模型类上独立的属性是十分有效的。但有时却需要同时验证一个对象上的多个属性。考虑这样一个场景,同时设置Order对象上的OrderDate和RequiredDate属性:
以上的代码从单纯的SQL构架方面来说是合法的——即使规定交货日期是订单下达日期的前一天是毫无意义的。好消息是现在在Beta2(.NET Framework 3.5)中LINQ to SQL可以十分简单地添加自定义实体级别的验证规则来防止类似的错误发生。可以为Order实体类添加一个局部类,并实现OnValidate()局部方法,该方法会在实体的值将要记录进数据库的时候被调用。使用该验证方法可以访问和验证所有的数据模型类属性:
使用该验证方法我们可以检查实体类上的任何属性值(即使对于它的管理对象只获得只读访问),如果验证的值不正确就可以产生一个异常。从OnValidate()方法中产生的任何异常都导致从数据库更新操作中退出,并且回滚该操作所在事务中的所以更改。
自定义实体插入/更新/删除方法验证
在实际应用中,有时我们会需要对于特定的插入,更新或删除场景来添加验证逻辑。在Beta2(.NET Framework 3.5)中LINQ to SQL可以通过添加局部类型来扩展DataContext类,然后实现相应的局部方法来为数据模型实体自定义插入,更新和删除逻辑。当调用DataContext类上的SubmitChanges()方法时,这些方法会被自动调用。
使用这些方法添加适当的验证逻辑后,如果验证通过就会通知LINQ to SQL继续进行数据库更新操作(通过调用DataContext类中的“ExecuteDynamicXYZ”方法):
适当地添加以上方法后,对于数据对象的任何创建/更新/删除操作都会自动调用这些方法。例如,考虑这样一个场景,创建一个新的Order并关联到一个已存在的Customer上:
当在上面的示例代码中调用northwind.SubmitChanges()时,LINQ to SQL会确认是否需要插入一个新的Order对象,并自动调用InsertOrder局部方法。
高级:为事务察看实体变化
在实际应用中,有时不能单纯地通过察看单独的插入/更新/插入操作来添加验证逻辑,而是应察看一个以事务发生的实体操作变化列表。从.NET Framework 3.5 Beta2开始,我们可以通过调用公共的DataContext.GetChangeSet()方法来访问这个变化列表。该方法会返回一个暴露每个已作出的添加,修改和移除操作的ChangeSet对象。
还有一种可供选择的方式是编写DataContext的子类并覆写SubmitChanges()方法。然后就可以为更新操作检索ChangeSet,并执行任何自定义的数据验证:
使用乐观并发执行处理同时发生的变化
在多用户数据库系统中开发者需要考虑的事情之一,就是如何处理对于数据库中数据同时发生的更新。例如,假设两个用户在同一应用程序中检索同样的Product对象,一个用户更新RecorderLevel为0,而另一个更新为1。如果两个用户都要试图保存Product的更新到数据库,那么开发者就需要决定如何处理变化冲突。
一种方式是“让后来的更新者获胜”——这就意味者先来的用户提交的值会在重大用户没有察觉的情况下丢失。这通常被认为是一种缺乏应用程序开发经验的一种表现。
另一种而且是LINQ to SQL支持的方式是使用乐观并发执行模型——如何数据库中的原始值已经有其他更新操作事先要执行,LINQ to SQL会自动检测发生地点。LINQ to SQL可以提供一个更改值的冲突列表给开发者,并且能够调和分歧,或者是通过UI通知应用程序终端用户来让他们决定如何处理。
为插入/更新/删除场景使用存储过程或自定义SQL逻辑
对于数据库开发者来说,好消息是LINQ to SQL提供了相当灵活的开发模型,可以让开发者覆写通过LINQ to SQL自动执行的动态SQL,并替代为调用自定义的插入,更新,删除用的存储过程。
一开始的时候,我们可以定义数据模型并使用LINQ to SQL自动处理插入,更新,删除等逻辑。在稍后还可以自定义用于更新数据模型的存储过程或SQL——不需要对使用数据模型的应用程序逻辑作出任何改变,也不需要对相应的数据验证或业务规则逻辑作出任何改变。这就为应用程序的构建提供了大量的灵活性。
总结
除了一般的插入,更新,删除操作,以及数据验证外,通过这篇Post我们还应注意以下一些要点:
1.善于利用表之间的关系关联进行数据更新
2.数据验证和业务逻辑验证的应用级别包括:构架级,实体级,属性级,更新操作时验证和以事务为单位的验证
3.LINQ to SQL还可以有效地处理SQL注入攻击
4.LINQ to SQL还可以自动处理事务
5.LINQ to SQL使用乐观并发执行模型处理更新冲突