EF Core中通过Fluent API配置多对多关系
EF Core与EF不是完全一样的,官方文档列出了详细的差异比较,可以查阅:https://docs.microsoft.com/zh-cn/ef/efcore-and-ef6/。
EF Core支持Code First模式生成数据库。这里以权限管理中的用户-角色-菜单的关系为例,演示一下EF Core中如何通过手动编写Fluent API来配置多对多的关系。
测试环境:VS2022 / .NET 6.0 / EF Core 6.0
假设我们已经设计好了数据库,并建立了主外键关联。如下图所示,一个用户可以对应多个角色,一个角色可以对应多个菜单。
我们首先要准备好上面的表对应的类:
用户类:
/// <summary> /// 用户 /// </summary> public class User { /// <summary> /// GUID /// </summary> [Key] public string? No { get; set; } /// <summary> /// 用户Id /// </summary> public string? Id { get; set; } /// <summary> /// 用户名 /// </summary> public string? UserName { get; set; } /// <summary> /// 密码 /// </summary> public string? PassWord { get; set; } /// <summary> /// 姓名 /// </summary> public string? Name { get; set; } /// <summary> /// 性别 /// </summary> public string? Sex { get; set; } /// <summary> /// 部门 /// </summary> public string? DeptId { get; set; } /// <summary> /// 工号 /// </summary> public string? JobNumber { get; set; } /// <summary> /// 职位 /// </summary> public string? Position { get; set; } /// <summary> /// 标识 /// </summary> public int Flag { get; set; } /// <summary> /// 最后登录时间 /// </summary> public DateTime LastLoginTime { get; set; } /// <summary> /// 备注 /// </summary> public string? Remark { get; set; } /// <summary> /// 一个用户可以对应多个角色 /// </summary> public virtual ICollection<Role> Role { get; set; } }
角色类:
/// <summary> /// 角色 /// </summary> public class Role { /// <summary> /// GUID /// </summary> [Key] public string? No { get; set; } /// <summary> /// 角色Id /// </summary> public string? Id { get; set; } /// <summary> /// 角色名 /// </summary> public string? Name { get; set; } /// <summary> /// 状态标识 /// </summary> public int Flag { get; set; } /// <summary> /// 角色备注 /// </summary> public string? Remark { get; set; } /// <summary> /// 一个角色对应多个菜单 /// </summary> public virtual ICollection<Function>? Function { get; set; } /// <summary> /// 一个角色对应多个用户 /// </summary> public virtual ICollection<User>? User { get; set; } }
菜单类:
/// <summary> /// 功能菜单 /// </summary> public class Function { /// <summary> /// 功能编号 /// </summary> public string? Id { get; set; } /// <summary> /// 功能名称 /// </summary> public string? Name { get; set; } /// <summary> /// 父级编号 /// </summary> public string? ParentId { get; set; } /// <summary> /// 链接地址 /// </summary> public string? Url { get; set; } /// <summary> /// 链接顺序 /// </summary> public int Order { get; set; } /// <summary> /// 菜单级别 /// </summary> public int Type { get; set; } /// <summary> /// 菜单标志 /// </summary> public int Flag { get; set; } /// <summary> /// 菜单说明 /// </summary> public string? Remark { get; set; } /// <summary> /// 一个菜单可以被多个角色引用 /// </summary> public virtual ICollection<Role>? Role { get; set; } }
然后需要创建一个EF上下文来操作数据库:
public class MyDbContext : DbContext { public MyDbContext() { } public MyDbContext(DbContextOptions<MyDbContext> options) : base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); //modelBuilder.Entity<User>() // .HasMany(o => o.Role) // .WithMany(o => o.User) // .UsingEntity<UserRole>( // o => o.HasOne(o => o.Role).WithMany().HasForeignKey(o => o.Role_No), // o => o.HasOne(o => o.User).WithMany().HasForeignKey(o => o.User_No), // o => o.HasKey(t => new { t.User_No, t.Role_No }) // ); modelBuilder.Entity<User>() .HasMany(o => o.Role) .WithMany(o => o.User) .UsingEntity<Dictionary<string, object>>("UserRole", o => o.HasOne<Role>().WithMany().HasForeignKey("Role_No"), o => o.HasOne<User>().WithMany().HasForeignKey("User_No"), o => o.ToTable("UserRole")); modelBuilder.Entity<Role>() .HasMany(o => o.Function) .WithMany(o => o.Role) .UsingEntity<Dictionary<string, object>>("RoleFunction", o => o.HasOne<Function>().WithMany().HasForeignKey("Function_Id"), o => o.HasOne<Role>().WithMany().HasForeignKey("Role_No"), o => o.ToTable("RoleFunction")); } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); optionsBuilder.UseSqlServer("server=xxx.xxx.xxx.xxx;database=数据库名称;uid=数据库账号;pwd=数据库密码;"); } public DbSet<User> User { get; set; } public DbSet<Role> Role { get; set; } public DbSet<Function> Function { get; set; } }
上述代码中的注释部分需要创建一个UserRole中间表对应的类:
[Table("UserRole")] public class UserRole { public string User_No { get; set; } public string Role_No { get; set; } [NotMapped] public User User { get; set; } [NotMapped] public Role Role { get; set; } }
而下面的Dictionary的方式则不需要创建这个类。
Fluent API方式手动配置好关系之后,就可以使用了:
using (var db = new MyDbContext()) { //当前用户对应多个角色 List<Role> roles = db.User .Include(o => o.Role) .ThenInclude(o => o.Function) .FirstOrDefault(t => t.UserName.Equals("admin")) .Role .ToList(); }
通过Include来预先加载关联的Role,再通过ThenInclude实现预先加载多级关联。查询角色对应的菜单也是类似的方式。