c# - 使用 EF Core 5 使用存储在不同表中的信息填充一个实体中的列表
问题描述
概括
我有两个表,一个用户表和一个赞助表。Sponsorships 表有两个指向 Users 表的外键。这里的关系是一个用户可以被几个不同的用户赞助,一个用户可以赞助几个不同的用户。我想使用此信息来填写每个用户的赞助商列表。
语境
我目前正在处理一个项目,我必须创建一个 API,该 API 使用在不同项目中使用的现有数据库,并且已决定将使用 .NET 5。已经存在的项目有成千上万的用户,所以我无法对数据库进行任何更改。为简洁起见,我删除了与问题无关的代码。
数据库
我连接的数据库有很多表,但其中只有两个与我的问题相关,用户和赞助。下面我说明了这些表的外观和它们的关系,删除了与此问题无关的列。这些列不以任何方式提供用户表和赞助表之间的关系。
用户
用户身份 |
---|
1 |
2 |
3 |
4 |
5 |
赞助
用户身份 | 赞助商用户 ID |
---|---|
1 | 3 |
1 | 4 |
1 | 5 |
2 | 3 |
2 | 4 |
Sponsorships 中的两列是用户表中 UserId 的主键和外键,因此我们正在处理复合键。这个表中还有一列也是主键和外键,称为RolesId。它是不同表的外键,但这不应该是相关的,因为它与用户表没有关系。此外,Sponsorships 表没有自动递增的 Id 列,只有这三个主键和一个 DateTime 类型的不相关列。我决定不编辑对 RolesId 的引用,以使相关代码尽可能接近原始代码。
一个用户可以由多个不同的用户赞助,也可以由多个不同的用户赞助。使用此信息,应该可以为每个用户创建两个列表,一个列表包含该用户赞助的所有用户,另一个列表包含该用户赞助的所有用户。
我的代码
到目前为止,我的代码包含一个继承 DbContext 的类、两个实体类,一个用于 User 和 Sponsorship,它有两个实现 IEntityTypeConfiguration 接口的相应配置类。一个使用在继承 DbContext 的类中定义的 DbSet 访问数据库的存储库。还有一个控制器可以让消费者可以使用这些信息。您可以在下面找到这些类,为简洁起见进行了编辑,以使我的解决方案更加清晰。一些类名和属性已被编辑以避免复制粘贴专有代码。
public class MyContext: DbContext, IMyContext
{
public DbSet<User> Users { get; set; }
public DbSet<Sponsorship> Sponsorships { get; set; }
public MyContext(DbContextOptions<MyContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new SponsorshipConfiguration());
modelBuilder.ApplyConfiguration(new UserConfiguration());
}
}
public class User
{
public int Id { get; set; }
public int UserId { get; set; }
public List<Sponsorship> AffiliateRelations { get; set; }
public List<Sponsorship> SponsorRelations { get; set; }
public List<User> Sponsors { get; set; }
}
public class Sponsorship
{
public int RoleId { get; set; }
public int UserId { get; set; }
public int SponsorUserId { get; set; }
public User User { get; set; }
public User Sponsor { get; set; }
}
public class SponsorshipConfiguration : IEntityTypeConfiguration<Sponsorship>
{
public void Configure(EntityTypeBuilder<Sponsorship> builder)
{
builder.ToTable("Sponsorships");
builder.HasKey(s => new { s.RoleId, s.UserId, s.SponsorUserId });
builder.Property(s => s.RoleId).HasColumnName("Roles_Id");
builder.HasOne(s => s.Sponsor)
.WithMany(u => u.SponsorRelations)
.HasForeignKey(s => s.SponsorUserId);
builder.HasOne(s => s.User)
.WithMany(u => u.AffiliateRelations)
.HasForeignKey(s => s.UserId);
}
}
public class UserConfiguration : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> builder)
{
builder.ToTable("Users");
builder.HasKey("Id");
}
}
public class UserRepository: BaseRepository, IUserRepository
{
public UserRepository(IMyContext context) : base(context)
{
}
public async Task<List<Sponsorship>> GetSponsorships()
{
return await Context.Sponsorships.Include(s => s.Sponsor).Include(s => s.User).OrderBy( s => s.UserId).ToListAsync();
}
public async Task<List<User>> GetUsersAsync()
{
return await Context.Users.ToListAsync();
}
public async Task<List<User>> GetUsersWithSponsors()
{
return await Context.Users.Include(u => u.Sponsors).ToListAsync()
}
}
如有必要,我还可以包含控制器代码,但这只是调用 UserRepository 中声明的各种方法,稍后我将添加一个控制器可以使用的 UserService。
问题
GetUsersWithSponsors() 确实执行成功,但在 Sponsors 列表中缺少信息。这是使用上面定义的数据的示例。UserId = 1 的用户对象有一个赞助商列表,它只包含一个元素,而它应该有 3。我通过查询数据库确认了这一点,并确保在选择所有行时赞助表确实有 3 行。用户身份。
我在 Stack Overflow 上查看了很多不同的问题,这些问题似乎解决了一个非常相似的问题,但他们的解决方案对我不起作用。例如Entity Framework Code First - 来自同一个表的两个外键我只能猜测我在两个配置类中使用 Fluent API 定义关系的方式存在缺陷和/或缺少信息。我将非常感谢一些帮助。谢谢你。
附带说明一下,我知道微软建议在 Fluent API 之前使用数据注释。我不知道这是否属实,但我会研究一下并考虑转换 Fluent API 代码。但是,如果这个问题的解决方案是使用 Fluent API 提供的,或者如果需要更改或添加配置代码,我将不胜感激。
解决方案
User
除了类的这个属性之外的所有东西
public List<User> Sponsors { get; set; }
是具有显式连接实体的经典多对多自我关系(在您的情况下,Sponsorship
它是具有附加字段的连接实体),由其他两个集合导航属性表示。
这个属性虽然需要注意。如果没有额外的流畅配置,它将被视为单独的一对多自我关系(很可能将User.UserId
在呈现的模型中看起来很奇怪的属性视为 FK 并加入它),这不是您想要的。相反,它必须配置为跳过导航(EF Core 5.0 中的一个新概念,用于通过隐式连接实体实现多对多)并绑定到现有的“常规”关系,如连接Sponsorship
中所述(连同示例) EF Core 文档的实体类型配置部分。
但为了做到这一点,您需要定义另一个集合导航属性(所讨论的“逆”属性),因为这是多对多映射的当前要求。
所以让我们添加一个 - 名称无关紧要,只是类型 -IEnumerable<User>
或派生:
public List<User> Sponsoring { get; set; }
并在内部使用以下流利UserConfiguration
的配置(从中删除关系配置代码,SponsorshipConfiguration
因为它们必须/将被配置为User
下面的关系配置的一部分):
builder
.HasMany(e => e.Sponsors)
.WithMany(e => e.Sponsoring)
.UsingEntity<Sponsorship>(
j => j
.HasOne(e => e.Sponsor)
.WithMany(e => e.SponsorRelations)
.HasForeignKey(e => e.SponsorUserId),
j => j
.HasOne(e => e.User)
.WithMany(e => e.AffiliateRelations)
.HasForeignKey(e => e.UserId)
);
就这样。现在两者
Context.Users.Include(u => u.Sponsors)
和
Context.Users.Include(u => u.Sponsoring)
而且当然
Context.Users.Include(u => u.Sponsors).Include(u => u.Sponsoring)
可用于获取(正确!)您需要的信息。
推荐阅读
- python - ARIMA模型的Python多处理
- php - 管理繁重文件夹的文件传输的最佳方法
- javascript - 映射对象,在单个循环中删除元素
- java - Netty 框架 - 多端口监听
- angular - 如何在 Angular 2 中管理用户级别的访问?
- spring - Spring Boot + Liquibase - 启动期间出现 NotWritablePropertyException
- oracle - IMPDP:如何只导入表数据
- php - 使用 Rest Api 从 Outlook 365 Exchange 读取电子邮件帐户
- c# - 发生一个或多个错误。(无法反序列化当前的 JSON 对象(例如))
- javascript - 嵌套列表的Javascript长度