65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Visual Basic 在 LINQ to SQL 中进行级联删除

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.50/5 (9投票s)

2008 年 7 月 8 日

CPOL

6分钟阅读

viewsIcon

27406

本文将讨论使用 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 示例为例,如果尝试删除有相关订单的客户,将会发生异常。这实际上并不是一个问题,这是正常行为,否则外键关系就没有意义了。真正的问题在于确定您是否真的希望删除具有相关实体集的记录,如果希望,您希望如何处理它们——是想保留相关记录,还是与目标记录一起删除它们?

image001.png
图 1:Customers、Orders 和 Order Details – 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

这将导致错误,并且数据库不会发生任何更改。

image002.jpg
图 2:冲突错误消息

解决方案替代方法 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)即可。

image003.png
图 3. 设置删除规则

如果您构建了数据库图,设置删除规则的最简单方法是打开图,单击图中的外键关系,然后打开“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 内部实现此操作。

© . All rights reserved.