0

我有以下模型:

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; }
}

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

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

4

3 回答 3

0

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

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

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

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

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

...然后根据已加载和实体linkRecords的 PK在内存中手动构建导航集合。EF 在这里没有帮助,因为链接表记录不是实体。只是内存中包含键对的对象的“临时”集合,EF 没有关于该集合的底层类型的任何元数据。PersonCategorylinkRecords

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

于 2013-10-08T18:49:13.857 回答
0

EF 无法为您执行此操作。但它会期望/创建一个外键,如Person_Id表架构中的 Category 。如果将其添加到,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);
}
于 2013-10-08T17:29:21.597 回答
0

首先,我强烈建议在您的模型中包含外键。避免盲目导航是推荐的良好做法。您需要将相关实体包含PersonId在类中。Category

其次,EF 5.0(我不确定旧版本)支持DBSet<T>通过方法将 a 完全加载到上下文中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
于 2013-10-08T17:41:02.247 回答