【问题标题】:How do I delete records from a child collection in LINQ to SQL?如何从 LINQ to SQL 中的子集合中删除记录?
【发布时间】:2009-05-25 04:11:12
【问题描述】:

我的数据库中有两个通过外键连接的表:Page(PageId,其他数据)和 PageTag(PageId,Tag)。我使用 LINQ 为这些表生成类,页面作为父集合,标签作为子集合(一对多关系)。有没有办法在 Page 类中标记 PageTag 记录以从数据库中删除?

快速清关:

我希望在父 DataContext 调用 SubmitChanges() 时删除子对象,而不是之前。我希望 TagString 的行为与 Page 对象的任何其他属性完全相同。

我想启用如下代码:

Page page = mDataContext.Pages.Where(page => page.pageId = 1);
page.TagString = "new set of tags";

//Changes have not been written to the database at this point.

mDataContext.SubmitChanges();

//All changes should now be saved to the database.

这是我的详细情况:
为了更轻松地处理标签集合,我向 Page 对象添加了一个属性,将标签集合视为字符串:

public string TagString {
    get {
        StringBuilder output = new StringBuilder();
        foreach (PageTag tag in PageTags) {
            output.Append(tag.Tag + " ");
        }

        if (output.Length > 0) {
            output.Remove(output.Length - 1, 1);
        }

        return output.ToString();
    }
    set {
        string[] tags = value.Split(' ');
        PageTags.Clear();
        foreach (string tag in tags) {
            PageTag pageTag = new PageTag();
            pageTag.Tag = tag;
            PageTags.Add(pageTag);
        }
    }
}

基本上,这个想法是当一串标签被发送到这个属性时,对象的当前标签被删除,并在它们的位置生成一个新的集合。

我遇到的问题是这一行:

PageTags.Clear();

在提交更改时实际上不会从数据库中删除旧标签。

环顾四周,删除事物的“正确”方法似乎是调用数据上下文类的 DeleteOnSubmit 方法。但我似乎无法从 Page 类中访问 DataContext 类。

有谁知道在 Page 类中标记子元素以从数据库中删除的方法?

【问题讨论】:

    标签: linq linq-to-sql


    【解决方案1】:

    经过更多研究,我相信我已经设法找到了解决方案。从集合中删除对象时将其标记为删除由 Association 属性的 DeleteOnNull 参数控制。

    当两个表之间的关系用OnDelete Cascade标记时,该参数设置为true。

    不幸的是,无法从设计器中设置此属性,也无法从 *DataContext.cs 文件的部分类中设置它。在不启用级联删除的情况下进行设置的唯一方法是手动编辑 *DataContext.designer.cs 文件。

    在我的例子中,这意味着找到 Page 关联,并添加 DeleteOnNull 属性:

    [Association(Name="Page_PageTag", Storage="_Page", ThisKey="PageId", OtherKey="iPageId", IsForeignKey=true)]
    public Page Page
    {
        ...
    }
    

    并添加 DeleteOnNull 属性:

    [Association(Name="Page_PageTag", Storage="_Page", ThisKey="PageId", OtherKey="iPageId", IsForeignKey=true, DeleteOnNull = true)]
    public Page Page
    {
        ...
    }
    

    请注意,该属性需要添加到 PageTag 类的 Page 属性中,而不是相反。

    另请参阅:
    Beth Massi -- LINQ to SQL and One-To-Many Relationships
    Dave Brace -- LINQ to SQL: DeleteOnNull

    【讨论】:

    • 亚伦,感谢您的洞察力。但是在存储库中编写两行代码来标记要删除的记录不是更容易吗?
    • 它最终不会成为存储库中的两行代码。我可以在存储库中添加一个 UpdateTags(Page page, string tagString) 方法,但这不能与模型绑定一样干净。
    • 这也很容易设置,现在我知道它链接到 On Delete Cascade,并且可以避免手动编辑设计器文件。
    【解决方案2】:

    对不起,我的错。那是行不通的。

    看起来您确实需要在您的存储库中执行此操作,而不是在您的 Page 类中。在那里,您可以访问原始数据上下文。

    有一种方法可以“附加”原始数据上下文,但是当你这样做时,它已经变成了代码味道。

    【讨论】:

    • 感谢您的意见。看起来 LINQ 不具备这种结构的能力:/
    【解决方案3】:

    在 Linq to SQL 实体图中,您是否有关联 Page 和 PageTags 表的关系?如果您不这样做,这就是您无法从 Page 类中看到 PageTags 类的原因。

    如果 PageTags 数据库表中的外键设置为 Allow Nulls,则当您将表拖入设计器时,Linq to SQL 将不会创建链接,即使您在 SQL Server 上创建了关系也是如此。

    【讨论】:

    • 关联已到位,我可以通过编程方式访问子集合。问题是对集合所做的更改(特别是对象的删除)不会持久化到数据库中。
    • 愚蠢的问题,但您是否正在提交更改?
    • 是的。当我提交更改时,我收到重复键错误(因为新标签与旧标签重叠)。
    【解决方案4】:

    这是 OR 映射可能会让人毛骨悚然的领域之一。提供这个 TagString 属性会使事情变得更方便一些,但从长远来看,当有人使用 TagString 属性时,它会混淆实际发生的情况。通过隐藏您执行数据修改的事实,某人可以很容易地出现并设置 TagString,而无需在 DataContext 范围内使用您的 Page 实体,这可能会导致一些难以发现的错误。

    更好的解决方案是使用 L2S 模型设计器在 Page 类上添加一个 Tags 属性,并要求在 DataContext 范围内直接在 Tags 属性上编辑 PageTag。将 TagString 属性设置为只读,以便对其进行分类(并且仍然提供一些便利),但消除了设置该属性时的混乱和困难。这种更改明确了意图,并明确了正在发生的事情以及 Page 对象的消费者需要什么来实现它。

    由于 Tags 是 Page 对象的属性,只要它附加到 DataContext,对该集合的任何更改都会正确触发数据库中的删除或插入,以响应 Remove 或 Add 调用。

    【讨论】:

    • 我不希望在从集合中删除 PageTag 后立即删除它。我希望在父 DataContext 调用 SubmitChanges() 时将其删除。 PageTagString 属性的更新语义不会与对象的任何其他数据属性不同。
    【解决方案5】:

    亚伦,

    显然,您必须遍历您的 PageTag 记录,为每个记录调用 DeleteOnSubmit。当您调用 SubmitChanges 时,Linq to SQL 应该创建一个聚合查询来一次删除所有记录,因此开销应该是最小的。

    替换

    PageTags.Clear();
    

    foreach (PageTag tag in PageTags)
        myDataContext.DeleteOnSubmit(tag);
    

    【讨论】:

    • 如何从我的 Page 类中访问 DataContext?
    • 将 DataContext 成员添加到您的 PageTag 部分类。部分类 PageTag { DataClassesDataContext myDataContext = new DataClassesDataContext();公共字符串 TagString { ..etc.
    【解决方案6】:

    亚伦:

    将 DataContext 成员添加到您的 PageTag 分部类。

    partial class PageTag 
    { 
        DataClassesDataContext myDataContext = new DataClassesDataContext(); 
    
        public string TagString { 
    

    ..等等。

    【讨论】:

    • 这不是用于检索 PageTag 的父 DataContext 的独立 DataContext 吗?我认为当我在 parent DataContext 上调用 SubmitChanges 时不会发生删除。
    • 它会起作用的。将 DataContext 视为与数据库的另一个连接。这看起来很奇怪的原因是,从架构的角度来看,您可能应该在 PageTag 部分类之外而不是在它内部操作 PageTag 对象。
    • 我收到异常“无法删除尚未附加的实体”。当我尝试从第二个 DataConnection 中删除它们时。这看起来不能满足将更改推迟到原始 DataContext 提交的要求。
    • 作为此问题的答案发布的代码示例。
    • Aaron,您应该能够通过您的对象引用原始数据上下文,例如:tag.dataContext(或您的原始数据上下文变量的名称)。
    【解决方案7】:

    应 Robert Harvey 的要求发布了更大的代码示例:

    DataContext.cs 文件:

    namespace MyProject.Library.Model
    {
        using Tome.Library.Parsing;
        using System.Text;
    
        partial class Page
        {
            //Part of Robert Harvey's proposed solution.
            MyDataContext mDataContext = new TomeDataContext();
    
            public string TagString {
                get {
                    StringBuilder output = new StringBuilder();
                    foreach (PageTag tag in PageTags) {
                        output.Append(tag.Tag + " ");
                    }
    
                    if (output.Length > 0) {
                        output.Remove(output.Length - 1, 1);
                    }
    
                    return output.ToString();
                }
                set {
                    string[] tags = value.Split(' ');
                    //Original code, fails to mark for deletion.
                    //PageTags.Clear();
    
                    //Robert Harvey's suggestion, thorws exception "Cannot remove an entity that has not been attached."
                    foreach (PageTag tag in PageTags) {
                        mDataContext.PageTags.DeleteOnSubmit(tag);
                    }
    
                    foreach (string tag in tags) {
                        PageTag PageTag = new PageTag();
                        PageTag.Tag = tag;
                        PageTags.Add(PageTag);
                    }
                }
            }
    
            private bool mIsNew;
            public bool IsNew {
                get {
                    return mIsNew;
                }
            }
    
            partial void OnCreated() {
                mIsNew = true;
            }
    
            partial void OnLoaded() {
                mIsNew = false;
            }
        }
    }
    

    存储库方法:

    public void Save() {
        mDataContext.SubmitChanges();
    }
    
    public Page GetPage(string pageName) {
        Page page =
            (from p in mDataContext.Pages
            where p.FileName == pageName
            select p).SingleOrDefault();
    
        return page;
    }
    

    用法:

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Edit(string pageName, FormCollection formValues) {
        Page updatedPage = mRepository.GetPage(pageName);
    
        //TagString is a Form value, and is set via UpdateModel.
        UpdateModel(updatedPage, formValues.ToValueProvider());
        updatedPage.FileName = pageName;
    
        //At this point NO changes should have been written to the database.
    
        mRepository.Save();
    
        //All changes should NOW be saved to the database.
    
        return RedirectToAction("Index", "Pages", new { PageName = pageName });
    }
    

    【讨论】:

      猜你喜欢
      • 2010-11-02
      • 1970-01-01
      • 1970-01-01
      • 2011-12-24
      • 1970-01-01
      • 1970-01-01
      • 2012-03-13
      • 1970-01-01
      • 2013-12-25
      相关资源
      最近更新 更多