使用 Visual Basic 在 LINQ to SQL 中进行级联删除
本文将讨论使用 LINQ to SQL 执行级联删除的替代方法。
引言
本文将讨论使用 LINQ to SQL 执行级联删除的替代方法。级联删除是指在删除操作的目标记录时,删除与该记录通过外键关系相关联的记录的操作。LINQ to SQL 本身不直接处理级联删除,是否需要进行此操作以及如何实现级联删除,都由开发人员自行决定。
问题
执行级联删除的问题并非 LINQ to SQL 所特有,基本上可以使用与以前相同的替代方法来执行此类删除。问题在于确定如何处理与目标删除记录相关联的记录的删除或保留,其中目标记录与数据库中其他表内的记录维护着外键关系,尤其是在外键字段不可为空的情况下。
例如,考虑 Northwind 数据库中的 Customers 表。Customers 表与 Orders 表之间存在外键关系(而 Orders 表又与 Order_Details 表之间存在外键关系)。要删除有相关订单的客户,需要处理(删除或采取其他措施)Orders 表和 Order_Details 表中的相关记录。在 LINQ to SQL 的术语中,相关表被称为实体集(entity sets)。
LINQ to SQL 不会违反外键关系,如果应用程序尝试删除具有此类关系的记录,执行的代码将抛出异常。以 Northwind 示例为例,如果尝试删除有相关订单的客户,将会发生异常。这实际上并不是一个问题,这是正常行为,否则外键关系就没有意义了。真正的问题在于确定您是否真的希望删除具有相关实体集的记录,如果希望,您希望如何处理它们——是想保留相关记录,还是与目标记录一起删除它们?

解决方案
您可以采用几种可能的替代方法。您可以在代码中使用 LINQ to SQL 来处理级联删除,也可以在 SQL Server 中处理外键关系。
如果对 Northwind 数据库执行此代码,它将创建一个具有相关订单和订单明细的客户。
Try
Dim c As New Customer()
c.CustomerID = "AAAAA"
c.Address = "554 Westwind Avenue"
c.City = "Wichita"
c.CompanyName = "Holy Toledo"
c.ContactName = "Frederick Flintstone"
c.ContactTitle = "Boss"
c.Country = "USA"
c.Fax = "316-335-5933"
c.Phone = "316-225-4934"
c.PostalCode = "67214"
c.Region = "EA"
Dim od As New Order_Detail()
od.Discount = 0.25F
od.ProductID = 1
od.Quantity = 25
od.UnitPrice = 25.0
Dim o As New Order()
o.Order_Details.Add(od)
o.Freight = 25.5
o.EmployeeID = 1
o.CustomerID = "AAAAA"
c.Orders.Add(o)
Dim dc As New NWindDataContext()
dc.Customers.InsertOnSubmit(c)
dc.SubmitChanges()
UpdateDataGrid()
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
但是,如果您随后尝试在不处理实体集的情况下删除客户,例如使用以下代码:
Try
Dim dc = New NWindDataContext()
Dim q = _
(From c In dc.GetTable(Of Customer)() _
Where c.CustomerID = "AAAAA" _
Select c).SingleOrDefault()
dc.Customers.DeleteOnSubmit(q)
dc.SubmitChanges()
UpdateDataGrid()
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
这将导致错误,并且数据库不会发生任何更改。

解决方案替代方法 1 – 使用 LINQ to SQL 处理删除
您可以通过手动删除相关实体集中的所有相关实体来处理级联删除;以下是一种简单的实现方法:
Try
Dim dc = New NWindDataContext()
Dim q = _
(From c In dc.GetTable(Of Customer)() _
Where c.CustomerID = "AAAAA" _
Select c).SingleOrDefault()
Dim ord As New Order()
For Each ord In q.Orders
dc.Orders.DeleteOnSubmit(ord)
Dim od As Order_Detail
For Each od In ord.Order_Details
dc.Order_Details.DeleteOnSubmit(od)
Next
Next
dc.Customers.DeleteOnSubmit(q)
dc.SubmitChanges()
UpdateDataGrid()
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
从该示例来看,要同时删除客户以及相关的订单和订单明细,代码首先按客户 ID 字段(主键)选择匹配的客户。找到匹配项后,代码会遍历与每个客户相关的订单,并使用 DeleteOnSubmit
调用将它们标记为删除。
此外,由于订单和订单明细之间还存在其他关系,代码会遍历与订单相关的所有订单明细,并将它们也标记为删除。最后,客户本身也会被标记为删除,然后对数据上下文调用 Submit Changes。实体被标记为删除的顺序并不重要,LINQ to SQL 会在执行 Submit Changes 调用期间根据外键的配置来处理顺序。
解决方案替代方法 2 – 在 SQL Server 中处理级联删除
可以完全在 SQL Server 中管理级联删除。为此,只需将外键关系的外键删除规则设置为“级联”(Cascade)即可。

如果您构建了数据库图,设置删除规则的最简单方法是打开图,单击图中的外键关系,然后打开“INSERT and UPDATE”属性以显示“Delete Rule”属性,然后将“Delete Rule”属性设置为“Cascade”,如 图 3 所示。
重复删除相关订单的客户示例,如果我们将所有约束的删除规则都设置为“级联”,我们可以使用以下代码删除客户:
Try
Dim dc = New NWindDataContext()
Dim q = _
(From c In dc.GetTable(Of Customer)() _
Where c.CustomerID = "AAAAA" _
Select c).SingleOrDefault()
dc.Customers.DeleteOnSubmit(q)
dc.SubmitChanges()
UpdateDataGrid()
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
正如您在此示例代码中看到的,无需像之前那样费心标记实体集的每个成员进行删除,因为在这种情况下,SQL Server 已被指示如何处理客户或订单记录的删除。因此,删除客户也会导致删除 Order 和 Order Details 表中包含的关系记录。
解决方案替代方法 3 – 在 SQL Server 中处理级联删除
也可以将实体集中的外键字段设置为可空(nullable),然后将该字段的删除规则设置为“Set Null”(设为 Null)。也可以为字段设置一个默认值,并将删除规则设置为“Set Default”(设为默认值)。如果需要删除(在此示例中)客户记录但保留订单和订单明细记录,这两种方法都可能很有用。这两种方法都可以用类似于上一解决方案替代方法的方式来处理。将外键值设置为可空可能不推荐,但它是一种可行的替代方法。
解决方案替代方法 4 – 使用存储过程处理级联删除
可以创建或添加一个执行级联删除的存储过程,并使用 LINQ to SQL 调用该存储过程。添加到设计器中的存储过程可以直接从数据上下文调用,例如,如果我们有一个名为 DeleteCustomer
的存储过程,它接受客户 ID 作为参数并处理级联删除,我们可以这样做:
Dim dc as new NwindDataContext()
dc.DeleteCustomer(“AAAAA”)
摘要
级联删除对于 LINQ to SQL 来说并非新问题,它一直是老问题。在本文中,我描述了几种在代码内部和 SQL Server 方面处理级联删除的方法,但正如 .NET 中的许多事情一样,还有其他几种方法可以在 LINQ to SQL 内部实现此操作。