Loading

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实现预先加载多级关联。查询角色对应的菜单也是类似的方式。

posted @ 2022-04-06 10:57  guwei4037  阅读(328)  评论(0编辑  收藏  举报