繁体   English   中英

如何在EF中使用多对多关系有效地加载数据?

[英]how to efficiently load data with many-to-many relationships in EF?

我有以下模型:

public class Person
{
    public int Id { get; set; }
    public virtual ICollection<Category> Categories { get; set; }
}

public class Category
{
    public int Id { get; set; }
    public string Name { get; set; }
}

基本上一个人可以属于多个类别。 该代码导致EF创建了CategoryPerson { PersonId, CategoryID }表。 现在,我想在列表中显示所有类别的所有人。 天真的方法:

var people = context.People.ToList();
foreach (var p in people)
{
    Console.WriteLine("Person {0}, categories: {1}", p.Id, string.Join("|", p.Categories.Select(x => x.Name)));
}

导致对数据库的1 + N个请求。

如果我按如下方式使用“ 包含 ”:

var people = context.People.Include(x => x.Categories).ToList();
foreach (var p in people)
{
    Console.WriteLine("Person {0}, categories: {1}", p.Id, string.Join("|", p.Categories.Select(x => x.Name)));
}

我的确只有1个请求,但是它是2个表的联接,如果Person记录很重,并且有多个关联的类别,则相同的重数据将多次返回:

{ person1, category1 } 
{ person1, category2 } 
{ person1, category3 } 

等等

理想情况下,我希望对数据库发出2个请求-一个请求获取所有类别,另一个请求获取所有人。 然后,理想情况下,这两个数组应该连接到内存中-因此,当我枚举Person.Categories时,它不会进入数据库,而是会获取预加载的数据。 EF可以实现吗?

EF将无法为您执行此操作。 但是它将期望/在表的架构中的Category上创建一个类似于Person_Id的外键。 如果将其添加到“ Category则可以在内存中进行联接:

public class Person
{
    public int Id { get; set; }
    public virtual ICollection<Category> Categories { get; set; }
}

public class Category
{
    public int Id { get; set; }
    public int Person_Id { get; set; }
    public string Name { get; set; }
}

var people = context.People.ToList();
var categories = context.Categories.ToList();

foreach (var p in people)
{
    p.Categories = categories.Where(a => a.Person_Id == a.Id);
}

首先,我强烈建议您在模型中包含外键。 建议您避免盲目导航。 您需要在与实体相关的Category包括PersonId

其次,EF 5.0(我不确定较旧的版本)支持通过Load方法将DBSet<T>完全Load到上下文中。 填充DBSet之后,可以使用Local属性来指定所需的内存中实体。

context.People.Load();
context.Categories.Load();

var q = (from p in context.People.Local
        join c in context.Categories.Local
        on a.PersonId equals c.PersonId
        select p
        ).ToList(); //--> No round trip to DataBase

您的想法适用于一对多(或一对一)关系,因为它们在表之一中具有外键,并且EF会加载此FK(无论您是否将其公开为模型属性)。 然后,EF能够根据PK和加载的FK在内存中重建对象图(称为“关系修正”)。

但是,它对于多对多关系不起作用,因为Person表和Category表都没有另一个表的外键。 FK在链接表CategoryPerson 当您仅从“ PersonCategory表中加载没有相关数据的“平面”数据时,就不会加载该表中的任何列。 加载那些可以告诉EF哪个Person属于哪个Categories (反之亦然)的数据后,内存中根本没有任何信息。

要在内存中创建正确的关系,您必须将链接表加载为第三个表。

var linkRecords = context.People.SelectMany(p => p.Categories.Select(c => new
{
    PersonId = p.Id,
    CategoryId = c.Id
}))
.ToList();

(我相信这是一个相对便宜的SQL查询,仅从链接表中获取数据,而没有任何连接)

...,然后根据linkRecords和已加载的PersonCategory实体的PK在内存中手动构建导航集合。 EF在这里无济于事,因为链接表记录不是实体。 linkRecords只是内存中具有一对密钥的对象的“临时”集合,EF没有关于该集合的基础类型的任何元数据。

对于不太大的表,整个过程可能会更高效-否则可能不会。 没有测量,我真的无法分辨。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM