在LINQ-TO-SQL中实现“级联删除”的方法

现在假定有两张表tb_Category和tb_Product(它们属于一对多关系),其中Product中的Cid是Category的外键。表结构如下:

转化成LINQ-TO-SQL之后模型类如下:

现在如果删除一个Category的话会抛出“外键冲突”等异常——究其原因,在于外键如果引用该主键,那么该主键不应该被删除。

怎么解决呢?LINQ-TO-SQL没有提供类似直接解决方案。我们可以这样做:

一、先从后主:

所谓“先从后主”就是说先把要删除的Category(假设就是第一个Category)下面的所有的Products给删除,最后把Category也删除。代码如下:

[C#]

using (MyDbContextDataContext dc = new MyDbContextDataContext())
           {
               var category = dc.tb_Categories.First();
               dc.tb_Products.DeleteAllOnSubmit(category.tb_Products);
               dc.tb_Categories.DeleteOnSubmit(category);
               dc.SubmitChanges();
           }

[VB.NET]

Using dc As New MyDbContextDataContext()
    Dim category = dc.tb_Categories.First()
    dc.tb_Products.DeleteAllOnSubmit(category.tb_Products)
    dc.tb_Categories.DeleteOnSubmit(category)
    dc.SubmitChanges()
End Using

原来打算用嵌套的foreach遍历Category实体下所有的product,然后批量DeleteOnSubmit。但是这个函数貌似有重载(允许批量删除,基于IEnumerable<T>的类型传参)。因此无需foreach而直接进行简化。不过这里要提醒大家注意一点——如果SubmitChanges不写是不会真正执行删除的。要记住:无论是传统的基于DataTable的ADO.NET,还是LINQ-TO-SQL,或者是EntityFramework,它们共同特点都是把真实数据表转化成模型存入缓存(内存)中,然后直接对内存的数据进行修改,同时内部通过某种设置维护其状态,最终进行批量删除。

二、拦截“删除”部分方法:

上面的方法是直白、明显地告知你“先删从表关联数据,最后删主表主记录”。下面的方法思路也如此。不过是通过重新定义部分类而实现的。我们首先查阅自生成的LINQ-TO-SQL模型类代码:

[C#]

public partial class MyDbContextDataContext : System.Data.Linq.DataContext
    {
        
        private static System.Data.Linq.Mapping.MappingSource mappingSource = new AttributeMappingSource();
        
    #region 可扩展性方法定义
    partial void OnCreated();
    partial void Inserttb_Category(tb_Category instance);
    partial void Updatetb_Category(tb_Category instance);
    partial void Deletetb_Category(tb_Category instance);
    partial void Inserttb_Product(tb_Product instance);
    partial void Updatetb_Product(tb_Product instance);
    partial void Deletetb_Product(tb_Product instance);
    #endregion
        
        public MyDbContextDataContext() : 
                base(global::CSharp.Properties.Settings.Default.MyTestConnectionString, mappingSource)
        {
            OnCreated();
        }
        
        public MyDbContextDataContext(string connection) : 
                base(connection, mappingSource)
        {
            OnCreated();
        }
        
        public MyDbContextDataContext(System.Data.IDbConnection connection) : 
                base(connection, mappingSource)
        {
            OnCreated();
        }
        
        public MyDbContextDataContext(string connection, System.Data.Linq.Mapping.MappingSource mappingSource) : 
                base(connection, mappingSource)
        {
            OnCreated();
        }
        
        public MyDbContextDataContext(System.Data.IDbConnection connection, System.Data.Linq.Mapping.MappingSource mappingSource) : 
                base(connection, mappingSource)
        {
            OnCreated();
        }
        
        public System.Data.Linq.Table<tb_Category> tb_Categories
        {
            get
            {
                return this.GetTable<tb_Category>();
            }
        }
        
        public System.Data.Linq.Table<tb_Product> tb_Products
        {
            get
            {
                return this.GetTable<tb_Product>();
            }
        }
    }

[VB.NET]

Public Partial Class MyDbContextDataContext
    Inherits System.Data.Linq.DataContext

    Private Shared mappingSource As System.Data.Linq.Mapping.MappingSource = New AttributeMappingSource()

    #Region "可扩展性方法定义"
    Private Partial Sub OnCreated()
    End Sub
    Private Partial Sub Inserttb_Category(instance As tb_Category)
    End Sub
    Private Partial Sub Updatetb_Category(instance As tb_Category)
    End Sub
    Private Partial Sub Deletetb_Category(instance As tb_Category)
    End Sub
    Private Partial Sub Inserttb_Product(instance As tb_Product)
    End Sub
    Private Partial Sub Updatetb_Product(instance As tb_Product)
    End Sub
    Private Partial Sub Deletetb_Product(instance As tb_Product)
    End Sub
    #End Region

    Public Sub New()
        MyBase.New(Global.CSharp.Properties.Settings.[Default].MyTestConnectionString, mappingSource)
        OnCreated()
    End Sub

    Public Sub New(connection As String)
        MyBase.New(connection, mappingSource)
        OnCreated()
    End Sub

    Public Sub New(connection As System.Data.IDbConnection)
        MyBase.New(connection, mappingSource)
        OnCreated()
    End Sub

    Public Sub New(connection As String, mappingSource As System.Data.Linq.Mapping.MappingSource)
        MyBase.New(connection, mappingSource)
        OnCreated()
    End Sub

    Public Sub New(connection As System.Data.IDbConnection, mappingSource As System.Data.Linq.Mapping.MappingSource)
        MyBase.New(connection, mappingSource)
        OnCreated()
    End Sub

    Public ReadOnly Property tb_Categories() As System.Data.Linq.Table(Of tb_Category)
        Get
            Return Me.GetTable(Of tb_Category)()
        End Get
    End Property

    Public ReadOnly Property tb_Products() As System.Data.Linq.Table(Of tb_Product)
        Get
            Return Me.GetTable(Of tb_Product)()
        End Get
    End Property
End Class

可以发现,其中有一个Delete_tbCategory的部分方法。如果我们重定义这个方法那么就可以拦截在对Category删除的同时先删除对应的Category下全部的Products,最后才是删除Category自身。
[C#]

namespace CSharp
{
    public partial class MyDbContextDataContext
    {
        partial void Deletetb_Category(tb_Category instance)
        {
            using (MyDbContextDataContext d = new MyDbContextDataContext())
            {
                var r = d.tb_Products.Where(p => p.cid == instance.Id);
                
                foreach (var item in r)
                {
                    d.tb_Products.DeleteOnSubmit(item);
                }
                d.SubmitChanges();
                ExecuteDynamicDelete(instance);
            }
        }
 
    }
 
    public class MainTest
    {
        static void Main(string[] args)
        {
            using (MyDbContextDataContext dc = new MyDbContextDataContext())
            {
                var result = dc.tb_Categories.First();
                dc.tb_Categories.DeleteOnSubmit(result);
                dc.SubmitChanges();
            }
        }
    }
}

[VB.NET]

Namespace CSharp
    Public Partial Class MyDbContextDataContext
        Private Partial Sub Deletetb_Category(instance As tb_Category)
            Using d As New MyDbContextDataContext()
                Dim r = d.tb_Products.Where(Function(p) p.cid = instance.Id)

                For Each item As var In r
                    d.tb_Products.DeleteOnSubmit(item)
                Next
                d.SubmitChanges()
                ExecuteDynamicDelete(instance)
            End Using
        End Sub

    End Class

    Public Class MainTest
        Private Shared Sub Main(args As String())
            Using dc As New MyDbContextDataContext()
                Dim result = dc.tb_Categories.First()
                dc.tb_Categories.DeleteOnSubmit(result)
                dc.SubmitChanges()
            End Using
        End Sub
    End Class
End Namespace

这里值得注意两点:
1)在Delete_tb_Category函数内部不能使用自身的DataContext进行对Product的删除操作。因为这个方法在SubmitChanges会被自动调用,而SubmitChanges会根据当前的DataContext类模型状态自动生成SQL语句并执行。如果此时对当前的DataContext进行操作,那么对于products的删除语句将无法自动生成。因此需要借助额外的DataContext来实现此操作。

2)在删除完全部的products之后,必须紧接着调用ExecuteDynamicDelete方法(这个方法是internal protected的)删除Category自身,否则是无法自动删除的。(具体可以参考:http://msdn.microsoft.com/zh-cn/library/bb546188(v=vs.90).aspx)。

至于说如果不重写为啥LINQ-TO-SQL默认的SubmitChanges会正常工作,还有生成的类是子类,那么父类的SubmitChanges又是怎么调用子类的partial方法我始终没有弄清楚,哪位好心人要是知道的话可以告知我,谢谢!

三、在SQL的关系图(第一个图)中设置级联删除。

posted @ 2012-11-23 13:51  Serviceboy  阅读(1213)  评论(0编辑  收藏  举报