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

时间:2013-10-08 17:12:24

标签: c# performance entity-framework

我有以下型号:

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个请求。

如果我按如下方式使用 Include

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实现吗?

3 个答案:

答案 0 :(得分:0)

EF将无法为您执行此操作。但是它会在表的模式中期望/创建类似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);
}

答案 1 :(得分:0)

首先,我强烈建议在模型中包含外键。避免 Blind 导航是一种推荐和良好的做法。您需要在PersonId与实体相关的课程中加入Category

其次,EF 5.0(我不确定旧版本)支持通过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

答案 2 :(得分:0)

你的想法适用于一对多(或一对一)关系,因为它们在其中一个表中有一个外键,EF会加载这个FK(无论你是将它暴露为模型属性还是不)。然后,EF可以根据PK和加载的FK(称为“关系修正”)在内存中重建对象图。

但是它不适用于多对多关系,因为PersonCategory表都没有其他表的外键。 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没有关于此集合的基础类型的任何元数据。

对于不太大的表,整个过程可能更有效 - 或者它可能不会。没有测量,我真的说不出来。