[英]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
。 当您仅从“ Person
和Category
表中加载没有相关数据的“平面”数据时,就不会加载该表中的任何列。 加载那些可以告诉EF哪个Person
属于哪个Categories
(反之亦然)的数据后,内存中根本没有任何信息。
要在内存中创建正确的关系,您必须将链接表加载为第三个表。
var linkRecords = context.People.SelectMany(p => p.Categories.Select(c => new
{
PersonId = p.Id,
CategoryId = c.Id
}))
.ToList();
(我相信这是一个相对便宜的SQL查询,仅从链接表中获取数据,而没有任何连接)
...,然后根据linkRecords
和已加载的Person
和Category
实体的PK在内存中手动构建导航集合。 EF在这里无济于事,因为链接表记录不是实体。 linkRecords
只是内存中具有一对密钥的对象的“临时”集合,EF没有关于该集合的基础类型的任何元数据。
对于不太大的表,整个过程可能会更高效-否则可能不会。 没有测量,我真的无法分辨。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.