[英]EF Core: how to load releated data with only having a FK of the related data
[英]EF core load data from child object using FK
我有一个 class 的代理
public class Agent
{
public string Id { get; set; }
public string Name { get; set; }
public List<AgentUser> Users { get; set; }
}
public class User
{
public string Id { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class AgentUser
{
public string Id { get; set; }
public string AgentId { get; set; }
public Agent Agent { get; set; }
public string UserId { get; set; }
public User User { get; set; }
}
关系是一对多。 在 AgentUsers 表上,它只包含 UserId。 我想知道在调用 Agents 表时如何加载用户详细信息(电子邮件、名字、姓氏)。
这是我的查询,但它只包含 UserId。
var result= await _context.Agents
.Include(au => au.Users)
.Where(x => x.Id.Equals(request.Id))
.AsNoTracking()
.FirstOrDefaultAsync();
是的,我可以进行 JOIN,但是如何仅使用 FK 以最有效的方式从 User 表中获取详细信息? 谢谢!
如果您想像您的示例一样在关系中使用连接实体,那么读取数据时的另一个选项是使用投影而不是加载整个实体图:
var result= await _context.Agents
.Where(a => a.Id.Equals(request.Id))
.Select(a => new AgentSummaryViewModel
{
AgentId = a.Id,
// other details from agent...
Users = a.Users.Select(u => new UserSummaryViewModel
{
UserId = u.User.Id,
Email = u.User.EMail,
// ...
}).ToList()
})
.SingleOrDefaultAsync();
一旦映射器配置了如何构建代理和用户 ViewModel,就可以使用 Automapper 及其针对 EF IQueryable
的ProjectTo()
方法进行简化。 投影具有额外的优势,即从实体图中加载与您的消费者(视图等)需求一样多的数据,构建高效的查询。 由于实体本身没有被加载,因此无需急切加载相关实体或担心跟踪引用。
如果您想加载该代理及其用户以进行编辑,则:
var agent = await _context.Agents
.Include(a => a.Users)
.ThenInclude(u => u.User)
.Where(a => a.Id.Equals(request.Id))
.SingleOrDefaultAsync();
或者,对于多对多关系,如果连接表可以仅用两个 FK 表示为复合 PK,则 EF 可以按约定 map 此表,而无需将 AgentUser 声明为实体。 如果您想要记录和访问有关该关系的其他详细信息,您只需要定义 AgentUser。 因此,例如,如果您想使用带有 IsActive 标志等的软删除系统,并且删除代理和用户之间的关联导致该记录变为非活动而不是删除,您将需要一个 AgentUser 实体。
因此,如果您可以将 AgentUser 表简化为:
AgentId [PK, FK(Agents)]
UserId [PK, FK(Users)]
...然后您的实体 model 可以简化为:
public class Agent
{
public string Id { get; set; }
public string Name { get; set; }
public List<User> Users { get; set; }
}
实体映射需要显式添加,例如使用 OnModelCreating 或通过注册的 IEntityTypeConfiguration 实现,以便:
modelBuilder.Entity<Agent>()
.HasMany(x => x.Users)
.WithMany(x => Agents);
不幸的是,对于 EF Core,以这种方式映射的多对多关系需要双方都有导航属性。 (代理有用户,用户有代理)
EF 应该能够按照惯例计算出链接表,但是如果您想更好地控制表名等,您可以明确告诉它要使用什么。 这是它可能会变得有点棘手的地方,但您可以通过定义一个连接实体来完成它,或者没有一个,使用“影子”实体。 (本质上是一本字典)
modelBuilder.Entity<Agent>()
.HasMany(x => x.Users)
.WithMany(x => Agents);
.UsingEntity<Dictionary<string, object>>(
"AgentUsers",
l => l.HasOne<Agent>().WithMany().HasForeignKey("AgentId"),
r => r.HasOne<User>().WithMany().HasForeignKey("UserId"),
j =>
{
j.HasKey("AgentId", "UserId");
j.ToTable("AgentUsers"); // Or "UserAgents", or "AgentUserLinks" etc.
});
编辑:一个使用 Automapper 的简单示例...
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<Agent, AgentSummaryViewModel>();
cfg.CreateMap<User, UserSummaryViewModel>();
});
var result= await _context.Agents
.Where(a => a.Id.Equals(request.Id))
.ProjectTo<AgentSummaryViewModel>()
.SingleOrDefaultAsync();
上面的“config”可以从具有所有映射器配置的全局映射器设置中提取,根据需要临时创建,或者我通常做的是从视图模型本身中构建为 static 方法的映射配置表达式派生:
var config = new MapperConfiguration(AgentSummaryViewModel.BuildMapperConfig());
...然后在 AgentSummaryViewModel 中:
public static MapperConfigurationExpression BuildConfig(MapperConfigurationExpression config = null)
{
if (config == null)
config = new MapperConfigurationExpression();
config = UserSummaryViewModel.BuildMapperConfig(config);
config.CreateMap<Agent, AgentSummaryViewModel>()
.ForMember(x => x.Users, opt => opt.MapFrom(src => src.Users.Select(u => u.User));
// append any other custom mappings such as renames or flatting from related entities, etc.
return config;
}
UserSummaryViewModel 将具有类似的 BuildMapperConfig() 方法来设置将用户转换为 UserSummaryViewModel。 生成的映射器配置表达式可以根据需要链接尽可能多的相关视图模型,位于它们各自的视图模型下。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.