我可以在不加载整个集合的情况下删除单个子实体吗?

Can I delete a single child entity without loading the entire collection?

我有 2 个 类,如下所示。

他们可以拥有非常大的集合 - 一个网站可能有 2,000 多个网站页面,反之亦然。

  class WebsitePage 
  {
    public int ID {get;set;}
    public string Title {get;set;}
    public List<Website> Websites {get;set;}
  }

  class Website 
  {
    public int ID {get;set;}
    public string Title {get;set;}
    public List<WebsitePage> WebsitePages {get;set;}
  }

我在从网站中删除 WebsitePage 时遇到问题。特别是从多个网站中删除 WebsitePage 时。

例如,我可能有这样的代码:

var pageToRemove = db.WebsitePages.FirstOrDefault();
var websites = db.Websites.Include(i => i.WebsitePages).ToList();
foreach(var website in websites)
{
    website.WebsitePages.Remove(pageToRemove)
}

如果每个网站 Include() 2k 页,您可以想象加载第二行需要很长时间。

但是,如果我在获取网站时不 Include() WebsitePages,则没有加载的子集合供我从中删除。

我尝试只 Include() 我需要删除的页面,但当然在保存时会给我一个空的集合。

是否有推荐的或更好的方法来解决这个问题?

我正在使用现有的 MVC 站点,除非绝对必要,否则我宁愿不必 create an entity class for the join table

不,你不能......通常。

多对多关系(带有隐藏连接 table)只能受嵌套集合中的 adding/removing 项影响。为此,必须加载集合。

但还有一些选择。

选项 1。

通过原始 SQL 从联结点 table 中删除数据。基本上这看起来像

context.Database.ExecuteSqlCommand(
    "DELETE FROM WebsiteWebsitePage WHERE WebsiteID = x AND WebsitePageID = y"));

(不使用参数)。

选项 2。

将连接点包含到 class 模型中,即将连接点 table 映射到 class WebsiteWebsitePageWebsiteWebsitePage 现在都有

public ICollection<WebsiteWebsitePage> WebsiteWebsitePages { get; set; }

WebsiteWebsitePage 将具有对 WebsiteWebsitePage 的引用属性。现在您可以直接通过 class 模型操纵连接点。

我认为这是最好的选择,因为一切都发生在与具有验证和跟踪的实体一起工作的标准方式。此外,迟早您可能需要一个显式连接 class,因为您将要向其中添加更多数据。

选项 3.

魔术盒。

我试图通过从集合中删除一个 存根实体 来做到这一点。在您的情况下:创建一个具有有效主键值的 WebsitePage 对象,并在不加载集合的情况下将其从 Website.WebsitePages 中删除。但是 EF 没有注意到变化,因为它没有跟踪 Website.WebsitePages,并且该项目不在开始的集合中。

但这让我意识到我必须让 EF 跟踪一个包含 1 个项目的 Website.WebsitePages 集合,然后删除该项目。我通过首先构建 Website 项目然后将其附加到新的上下文来实现这一点。我将展示我使用的代码(标准 Product - Category 模型)以防止拼写错误。

Product prd;

// Step 1: build an object with 1 item in its collection
Category cat = new Category { Id = 3 }; // Stub entity
using(var db = new ProdCatContext())
{
    db.Configuration.LazyLoadingEnabled = false;
    prd = db.Products.First();
    prd.Categories.Add(cat);
}

// Step 2: attach to a new context and remove the category.
using(var db = new ProdCatContext())
{
    db.Configuration.LazyLoadingEnabled = false;
    db.Products.Attach(prd);

    prd.Categories.Remove(cat);

    db.SaveChanges(); // Deletes the junction record.
}

延迟加载已禁用,否则在解决 prd.Categories 时仍会加载类别。

我对这里发生的事情的解释是:在第二步中,EF 不仅会在您附加产品时开始跟踪它,还会跟踪它的关联,因为它 'knows' 您无法自己加载这些关联在多对多的关系中。但是,当您在第一步中添加类别时,它不会执行此操作。