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

LINQ to SQL 中的级联删除

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.71/5 (9投票s)

2008 年 7 月 7 日

CPOL

6分钟阅读

viewsIcon

88360

本文将讨论使用 LINQ to SQL 执行级联删除的替代方法。

引言

本文将讨论使用 LINQ to SQL 执行级联删除的替代方法。级联删除是指删除一个记录时,同时删除与该记录通过外键关联的其他记录的操作。LINQ to SQL 不会专门处理级联删除,开发者需要自行决定是否需要执行此操作,以及如何来实现级联删除。

问题

执行级联删除的问题并非 LINQ to SQL 才出现,在处理这类问题时,基本上有相同的替代方法。问题在于,当一个记录被标记为删除时,需要确定如何处理与之通过外键关联(特别是那些非可空字段)的其他表中的相关记录——是删除还是保留它们。

例如,考虑 Northwind 数据库中的 Customer 表。Customer 表与 Orders 表之间存在外键关系(Orders 表又与 Order_Details 表存在外键关系)。要删除一个有相关订单的客户,就需要处理 Orders 表和 Order_Details 表中的相关记录。在 LINQ to SQL 的术语中,相关的表被称为实体集。

LINQ to SQL 不会违反外键关系。如果应用程序尝试删除具有此类关系的记录,执行的代码将抛出异常。以 Northwind 数据库为例,如果尝试删除一个有相关订单的客户,就会发生异常。这并非问题,而是应有的行为,否则外键关系还有何意义?关键在于,你需要确定是否真的要删除包含相关实体集的记录,如果决定删除,又该如何处理——是保留相关记录,还是与目标记录一起删除它们?

图 1:Customers、Orders 和 Order Details – Northwind 数据库

解决方案

有几种可行的替代方案。你可以在代码中使用 LINQ to SQL 来处理级联删除,也可以在 SQL Server 中处理外键关系。

如果将此代码执行到 Northwind 数据库,它将创建一个带有相关订单和订单明细的客户。

try
{
    Customer c = 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";

    Order_Detail od = new Order_Detail();
    od.Discount = .25f;
    od.ProductID = 1;
    od.Quantity = 25;
    od.UnitPrice = 25.00M;

    Order o = new Order();
    o.Order_Details.Add(od);
    o.Freight = 25.50M;
    o.EmployeeID = 1;
    o.CustomerID = "AAAAA";

    c.Orders.Add(o);

    using (NWindDataContext dc = new NWindDataContext())
    {
        var table = dc.GetTable();
        table.InsertOnSubmit(c);
        dc.SubmitChanges();
    }
}
catch (Exception ex)
{
    MessageBox.Show(ex.Message);
}

但是,如果你尝试删除客户而不处理实体集,像这样:

using (NWindDataContext dc = new NWindDataContext())
{

    var q =
        (from c in dc.GetTable<Customer>()
        where c.CustomerID == "AAAAA"
    select c).Single<Customer>();

    dc.GetTable<Customer>().DeleteOnSubmit(q);
    dc.SubmitChanges();
}

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

图 2:冲突错误消息

解决方案替代方案 1 – 使用 LINQ to SQL 处理删除

你可以通过手动删除关联实体集中的所有相关实体来处理级联删除;这是一种简单的实现方式:

try
{
    using (NWindDataContext dc = new NWindDataContext())
    {

        var q =
            (from c in dc.GetTable<Customer>()
            where c.CustomerID == "AAAAA"
        select c).Single<Customer>();

        foreach (Order ord in q.Orders)
        {
            dc.GetTable<Order>().DeleteOnSubmit(ord);

            foreach (Order_Detail od in ord.Order_Details)
            {
                dc.GetTable<Order_Detail>().DeleteOnSubmit(od);
            }
        }

        dc.GetTable<Customer>().DeleteOnSubmit(q);
        dc.SubmitChanges();
    }

    UpdateDataGrid();
}
catch (Exception ex)
{
    MessageBox.Show(ex.Message);
}

从这个例子来看,要删除客户及其相关的订单和订单明细,代码首先根据客户 ID(主键)选择匹配的客户。一旦找到匹配项,代码会遍历与每个客户相关的订单,并使用 DeleteOnSubmit 调用将其标记为删除。

此外,由于订单和订单明细之间还存在另一个关系,代码会遍历与订单相关的所有订单明细,并将它们也标记为删除。最后,将客户本身标记为删除,然后对数据上下文调用 SubmitChanges。实体被标记为删除的顺序无关紧要,LINQ to SQL 会在执行 SubmitChanges 调用时根据外键的配置进行排序。

解决方案替代方案 2 – 在 SQL Server 中处理级联删除

可以在 SQL Server 中完全管理级联删除。为此,只需将外键关系删除规则设置为“级联”即可。

图 3. 设置删除规则

如果你已经构建了数据库图,最简单的设置删除规则的方法是打开图,点击图中的外键关系,然后打开 INSERT 和 UPDATE 属性以显示 Delete Rule 属性,然后将 Delete Rule 属性设置为 Cascade,如图 3 所示。

重复删除相关订单的客户的示例,如果我们为所有约束的删除规则都设置为级联,我们可以使用这段代码删除客户:

try
{
    using (NWindDataContext dc = new NWindDataContext())
    {

        var q =
            (from c in dc.GetTable<Customer>()
            where c.CustomerID == "AAAAA"
        select c).Single<Customer>();

        dc.GetTable<Customer>().DeleteOnSubmit(q);
        dc.SubmitChanges();
    }

    UpdateDataGrid();
}
catch (Exception ex)
{
    MessageBox.Show(ex.Message);
}

正如你在此示例代码中看到的,无需像上一个方案那样麻烦地标记实体集的每个成员为删除,因为在这种情况下,SQL Server 已被告知如何处理客户或订单记录的删除。因此,删除客户也会导致 Order 和 Order Details 表中包含的相关记录被删除。

解决方案替代方案 3 – 在 SQL Server 中处理级联删除

也可以将实体集中的外键字段设置为可空,然后将该字段的删除规则设置为“Set Null”。也可以为字段设置一个默认值,并将删除规则设置为“Set Default”。如果需要删除(在此示例中)一个客户记录但保留订单和订单明细记录,这两种方法都可能有用。这两种方法都可以以类似于前一个解决方案替代方案的方式处理。将外键值设置为可空可能不是最佳选择,但它是一个可行的替代方案。

解决方案替代方案 4 – 使用存储过程处理级联删除

可以创建或添加一个存储过程来完成级联删除,并使用 LINQ to SQL 调用该存储过程。添加到设计器中的存储过程可以直接从数据上下文调用,例如,如果我们有一个名为 DeleteCustomer 的存储过程,它接受客户 ID 作为参数并处理级联删除,我们可以这样做:

Using(NwindDataContext dc = new NwindDataContext())
{
    dc.DeleteCustomer("AAAAA");
}

摘要

级联删除对 LINQ to SQL 来说并非新问题,它一直是同样的问题。在本文中,我描述了几种在代码内部和 SQL Server 端处理级联删除的方法,但正如 .NET 中的许多事物一样,还有其他几种方法可以在 LINQ to SQL 中实现此操作。

© . All rights reserved.