zoukankan      html  css  js  c++  java
  • ASP.NET Core搭建多层网站架构【5-网站数据库实体设计及映射配置】

    2020/01/29, ASP.NET Core 3.1, VS2019, EntityFrameworkCore 3.1.1, Microsoft.Extensions.Logging.Console 3.1.1, Microsoft.Extensions.Logging.Debug 3.1.1

    摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站架构【5-网站数据库实体设计及映射配置】
    网站数据库实体设计,使用EntityFrameworkCore 3.1 FluentAPI映射配置实体,网站启动时创建数据库并添加种子数据,开发调试时可以看到执行的具体sql语句

    文章目录

    此分支项目代码

    本章节介绍后台管理的网站数据库实体设计,使用FluentAPI方式配置数据库字段映射,网站启动时创建数据库并添加种子数据

    需求分析

    首先要实现的功能有用户登录、角色管理、日志记录
    大概有四张表:用户表、密码表、角色表、日志表
    日志表:

    用户表:

    密码表:

    角色表:

    好像博客园md不支持表格功能?所以只能截图展示,excel表格上传至项目docs文件夹中

    字段设计说明

    • 日志表主键Id是数据库自增的,也就是在向数据库插入日志时,不用管Id,往里写入就行
    • 用户表、角色表的Id都是long类型的,也就是使用雪花算法生成的Id
    • 密码表的主键是Account,UserId是用户表外键
    • 用户表和角色表拥有StatusCode、Creator、CreateTime、Modifier、ModifyTime,标明该记录的状态、创建时间等信息

    创建实体类

    MS.Entities类库中添加Core文件夹,在Core文件夹中添加IEntity.cs类:

    using System;
    
    namespace MS.Entities.Core
    {
        //没有Id主键的实体继承这个
        public interface IEntity
        {
        }
        //有Id主键的实体继承这个
        public abstract class BaseEntity : IEntity
        {
            public long Id { get; set; }
            public StatusCode StatusCode { get; set; }
            public long? Creator { get; set; }
            public DateTime? CreateTime { get; set; }
            public long? Modifier { get; set; }
            public DateTime? ModifyTime { get; set; }
        }
    }
    

    在Core中新建StatusCode.cs枚举:

    using System.ComponentModel;
    
    namespace MS.Entities.Core
    {
        public enum StatusCode
        {
            [Description("已删除")]
            Deleted = -1,//软删除,已删除的无法恢复,无法看见,暂未使用
            [Description("生效")]
            Enable = 0,
            [Description("失效")]
            Disable = 1//失效的还可以改为生效
        }
    }
    

    日志表

    MS.Entities类库中添加Logrecord.cs类:

    using MS.Entities.Core;
    using System;
    
    namespace MS.Entities
    {
        public class Logrecord : IEntity
        {
            public int Id { get; set; }
            public DateTime LogDate { get; set; }
            public string LogLevel { get; set; }
            public string Logger { get; set; }
            public string Message { get; set; }
            public string Exception { get; set; }
            public string MachineName { get; set; }
            public string MachineIp { get; set; }
            public string NetRequestMethod { get; set; }
            public string NetRequestUrl { get; set; }
            public string NetUserIsauthenticated { get; set; }
            public string NetUserAuthtype { get; set; }
            public string NetUserIdentity { get; set; }
        }
    }
    

    角色表

    MS.Entities类库中添加Role.cs类:

    using MS.Entities.Core;
    
    namespace MS.Entities
    {
        public class Role : BaseEntity
        {
            public string Name { get; set; }
            public string DisplayName { get; set; }
            public string Remark { get; set; } 
        }
    }
    

    用户表

    MS.Entities类库中添加User.cs类:

    using MS.Entities.Core;
    
    namespace MS.Entities
    {
        public class User : BaseEntity
        {
            public string Account { get; set; }
            public string Name { get; set; }
            public string Email { get; set; }
            public string Phone { get; set; }
            public long RoleId { get; set; } 
    
            public Role Role { get; set; }
        }
    }
    

    密码表

    MS.Entities类库中添加UserLogin.cs类:

    using MS.Entities.Core;
    using System;
    
    namespace MS.Entities
    {
        public class UserLogin : IEntity
        {
            public long UserId { get; set; }
            public string Account { get; set; }
            public string HashedPassword { get; set; }
            public DateTime? LastLoginTime { get; set; }
            public int AccessFailedCount { get; set; }
            public bool IsLocked { get; set; }
            public DateTime? LockedTime { get; set; }
    
            public User User { get; set; }
        }
    }
    

    至此,实体类都已完成设计
    项目完成后,如下图

    创建映射配置

    MS.DbContexts类库添加包引用:

    <ItemGroup>
      <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="3.1.1" />
      <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="3.1.1" />
    </ItemGroup>
    

    这两个包给DbContext扩展日志记录,可以实现查看EFCore生成的sql语句,具体使用方法后文会提到

    MS.DbContexts类库中引用MS.EntitiesMS.UnitOfWork类库
    MS.DbContexts类库中添加Mappings文件夹,在该文件夹中添加 LogrecordMap.csRoleMap.csUserLoginMap.csUserMap.cs

    LogrecordMap.cs

    using Microsoft.EntityFrameworkCore;
    using Microsoft.EntityFrameworkCore.Metadata.Builders;
    using MS.Entities;
    
    namespace MS.DbContexts
    {
        public class LogrecordMap : IEntityTypeConfiguration<Logrecord>
        {
            public void Configure(EntityTypeBuilder<Logrecord> builder)
            {
                builder.ToTable("TblLogrecords");
                builder.HasKey(c => c.Id);//自增主键
                builder.Property(c => c.LogDate).IsRequired();
                builder.Property(u => u.LogLevel).IsRequired().HasMaxLength(50);
                builder.Property(u => u.Logger).IsRequired().HasMaxLength(256);
                builder.Property(u => u.Message);
                builder.Property(u => u.Exception);
                builder.Property(u => u.MachineName).HasMaxLength(50);
                builder.Property(u => u.MachineIp).HasMaxLength(50);
                builder.Property(u => u.NetRequestMethod).HasMaxLength(10);
                builder.Property(u => u.NetRequestUrl).HasMaxLength(500);
                builder.Property(u => u.NetUserIsauthenticated).HasMaxLength(10);
                builder.Property(u => u.NetUserAuthtype).HasMaxLength(50);
                builder.Property(u => u.NetUserIdentity).HasMaxLength(50);
            }
        }
    }
    

    RoleMap.cs

    using Microsoft.EntityFrameworkCore;
    using Microsoft.EntityFrameworkCore.Metadata.Builders;
    using MS.Entities;
    
    namespace MS.DbContexts
    {
        public class RoleMap : IEntityTypeConfiguration<Role>
        {
            public void Configure(EntityTypeBuilder<Role> builder)
            {
                builder.ToTable("TblRoles");
                builder.HasKey(c => c.Id);
                builder.Property(c => c.Id).ValueGeneratedNever();
                builder.HasIndex(c => c.Name).IsUnique();//指定索引,不能重复
                builder.Property(c => c.Name).IsRequired().HasMaxLength(16);
                builder.Property(c => c.DisplayName).IsRequired().HasMaxLength(50);
                builder.Property(c => c.Remark).HasMaxLength(4000);
                builder.Property(c => c.Creator).IsRequired();
                builder.Property(c => c.CreateTime).IsRequired();
                builder.Property(c => c.Modifier);
                builder.Property(c => c.ModifyTime);
                //builder.HasQueryFilter(b => b.StatusCode != StatusCode.Deleted);//默认不查询软删除数据
            }
        }
    } 
    

    UserLoginMap.cs

    using Microsoft.EntityFrameworkCore;
    using Microsoft.EntityFrameworkCore.Metadata.Builders;
    using MS.Entities;
    
    namespace MS.DbContexts
    {
        public class UserLoginMap : IEntityTypeConfiguration<UserLogin>
        {
            public void Configure(EntityTypeBuilder<UserLogin> builder)
            {
                builder.ToTable("TblUserLogins");
                builder.HasKey(c => c.Account);
                //builder.Property(c => c.UserId).ValueGeneratedNever();
                builder.Property(c => c.Account).IsRequired().HasMaxLength(20);
                builder.Property(c => c.HashedPassword).IsRequired().HasMaxLength(256);
                builder.Property(c => c.LastLoginTime);
                builder.Property(c => c.AccessFailedCount).IsRequired().HasDefaultValue(0);
                builder.Property(c => c.IsLocked).IsRequired();
                builder.Property(c => c.LockedTime);
                builder.HasOne(c => c.User);
            }
        }
    } 
    

    UserMap.cs

    using Microsoft.EntityFrameworkCore;
    using Microsoft.EntityFrameworkCore.Metadata.Builders;
    using MS.Entities;
    using MS.Entities.Core;
    
    namespace MS.DbContexts
    {
        public class UserMap : IEntityTypeConfiguration<User>
        {
            public void Configure(EntityTypeBuilder<User> builder)
            {
                builder.ToTable("TblUsers");
                builder.HasKey(c => c.Id);
                builder.Property(c => c.Id).ValueGeneratedNever();
                builder.HasIndex(c => c.Account).IsUnique();//指定索引
                builder.Property(c => c.Account).IsRequired().HasMaxLength(16);
                builder.Property(c => c.Name).IsRequired().HasMaxLength(50);
                builder.Property(c => c.Email).HasMaxLength(100);
                builder.Property(c => c.Phone).HasMaxLength(25);
                builder.Property(c => c.RoleId).IsRequired();
                builder.Property(c => c.StatusCode).IsRequired().HasDefaultValue(StatusCode.Enable);
                builder.Property(c => c.Creator).IsRequired();
                builder.Property(c => c.CreateTime).IsRequired();
                builder.Property(c => c.Modifier);
                builder.Property(c => c.ModifyTime);
    
                builder.HasOne(c => c.Role);
                //builder.HasQueryFilter(b => b.StatusCode != StatusCode.Deleted);//默认不查询软删除数据
            }
        }
    } 
    

    至此映射配置完成

    说明

    • User和Role映射中注释掉了HasQueryFilter全局过滤查询,如需要可自行开启
    • LogrecordMap中Id仅配置主键,所以默认是数据库自增主键
    • RoleMap、UserMap中Id设为ValueGeneratedNever,不自动生成值,我们使用雪花算法生成Id赋值
    • UserMap中配置了HasOne(Role),表明关联性,所以RoleId能自动映射为Role表的Id外键,UserLoginMap中的UserId也是如此
    • UserMap中手动显式指定了表名为TblUsers,加"Tbl"前缀是为了避免和数据库默认关键字重复

    建立DbContext上下文

    MS.DbContexts类库中添加MSDbContext.cs类:

    using Microsoft.EntityFrameworkCore;
    using Microsoft.Extensions.Logging;
    
    namespace MS.DbContexts
    {
        public class MSDbContext : DbContext
        {
            //Add-Migration InitialCreate
            //Update-Database InitialCreate
            public MSDbContext(DbContextOptions<MSDbContext> options)
               : base(options)
            {
            }
            //此处用微软原生的控制台日志记录,如果使用NLog很可能数据库还没创建,造成记录日志到数据库性能下降(一直在尝试连接数据库,但是数据库还没创建)
            //此处使用静态实例,这样不会为每个上下文实例创建新的 ILoggerFactory 实例,这一点非常重要。 否则会导致内存泄漏和性能下降。
            //此处使用了Debug和console两种日志输出,会输出到控制台和调试窗口
            public static readonly ILoggerFactory MyLoggerFactory = LoggerFactory.Create(builder => builder.AddDebug().AddConsole());
            protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            {
                base.OnConfiguring(optionsBuilder);
                optionsBuilder.UseLoggerFactory(MyLoggerFactory);
            }
            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                modelBuilder.ApplyConfiguration(new LogrecordMap());
                modelBuilder.ApplyConfiguration(new RoleMap());
                modelBuilder.ApplyConfiguration(new UserLoginMap());
                modelBuilder.ApplyConfiguration(new UserMap());
    
                base.OnModelCreating(modelBuilder);
            }
            
        }
    } 
    

    说明:

    • 使用了微软原生的控制台日志记录,如果使用NLog很可能数据库还没创建,造成记录日志到数据库性能下降(一直在尝试连接数据库,但是数据库还没创建)
    • 使用静态实例,这样不会为每个上下文实例创建新的 ILoggerFactory 实例,这一点非常重要。 否则会导致内存泄漏和性能下降。
    • 使用了Debug和console两种日志输出,会输出到控制台和调试窗口

    至此,数据访问层创建完毕,项目完成后如下图所示

    创建数据种子

    目前我所知道的数据库的创建有三种(生成sql语句单独执行创建暂不讨论):

    1. 先创建迁移文件,然后在代码中自动迁移
    2. 使用.NET Core CLI命令创建数据库
    3. 在代码中直接创建数据库

    一、三两种方法的差别我在EFCore自动迁移中写过,第一种方法有个缺点是如果创建迁移时使用MySQL数据库,编译好代码后,部署的环境必须是同样的数据库,而第三种方法没有这个问题。
    第二种方法需要使用到CLI命令工具单独执行,所以我没有考虑

    我选择直接创建,项目启动时,检查数据库是否存在,如果不存在则创建,创建成功后开始写入种子数据。

    添加包引用

    MS.WebApi应用程序中添加MySQL包引用,如果你使用SQL server,安装Microsoft.EntityFrameworkCore.SqlServer包即可:

    <ItemGroup>
      <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.1.1" />
    </ItemGroup>
    

    我写本章节时,还是3.1.0版本,但是写到第8.1章的时候升级了3.1.1,本文改成了3.1.1,代码中8.1之后的所有分支都改成了最新版本,但是在此之前的分支依然是3.1.0没有去做更新改动了(其实用起来区别也不大)

    添加数据种子方法

    MS.WebApi应用程序中添加Initialize文件夹,把自带的Startup.cs类移至Initialize文件夹中
    在Initialize文件夹新建DBSeed.cs类:

    using MS.Common.Security;
    using MS.DbContexts;
    using MS.Entities;
    using MS.Entities.Core;
    using MS.UnitOfWork;
    using System;
    
    namespace MS.WebApi
    {
        public static class DBSeed
        {
            /// <summary>
            /// 数据初始化
            /// </summary>
            /// <param name="unitOfWork"></param>
            /// <returns>返回是否创建了数据库(非迁移)</returns>
            public static bool Initialize(IUnitOfWork<MSDbContext> unitOfWork)
            {
                bool isCreateDb = false;
                //直接自动执行迁移,如果它创建了数据库,则返回true
                if (unitOfWork.DbContext.Database.EnsureCreated())
                {
                    isCreateDb = true;
                    //打印log-创建数据库及初始化期初数据
    
                    long rootUserId = 1219490056771866624;
    
                    #region 角色、用户、登录
                    Role rootRole = new Role
                    {
                        Id = 1219490056771866625,
                        Name = "SuperAdmin",
                        DisplayName = "超级管理员",
                        Remark = "系统内置超级管理员",
                        Creator = rootUserId,
                        CreateTime = DateTime.Now
                    };
                    User rootUser = new User
                    {
                        Id = rootUserId,
                        Account = "admin",
                        Name = "admin",
                        RoleId = rootRole.Id,
                        StatusCode = StatusCode.Enable,
                        Creator = rootUserId,
                        CreateTime = DateTime.Now,
                    };
    
                    unitOfWork.GetRepository<Role>().Insert(rootRole);
                    unitOfWork.GetRepository<User>().Insert(rootUser);
                    unitOfWork.GetRepository<UserLogin>().Insert(new UserLogin
                    {
                        UserId = rootUserId,
                        Account = rootUser.Account,
                        HashedPassword = Crypto.HashPassword(rootUser.Account),//默认密码同账号名
                        IsLocked = false
                    });
                    unitOfWork.SaveChanges();
    
                    #endregion
                }
                return isCreateDb;
            }
    
    
        }
    }
    

    上面的DBSeed中:

    • EnsureCreated方法确保创建了数据库(如果数据库不存在则创建并返回true,存在则返回false)
    • 创建了一个超级管理员角色,创建了一个超级管理员用户admin(密码同账号)

    添加数据库连接字符串

    appsettings.json中添加数据库连接字符串(具体的连接自行配置):

    "ConectionStrings": {
        "MSDbContext": "server=192.168.137.10;database=MSDB;user=root;password=mysql@local;"
      }
    

    修改后如下图所示:

    开启EntityFrameworkCore日志

    appsettings.Development.json的"Logging:LogLevel"节点添加:

     "Microsoft.EntityFrameworkCore": "Information"
    

    修改完成后,如下图所示

    为什么要把开启EntityFrameworkCore日志写在appsettings.Development.json文件里呢?
    因为appsettings.Development.json文件是默认开发时使用的配置,也就是只在开发时才开启EFCore的日志记录,实际生产环境不开启

    注册工作单元

    Startup.cs类,ConfigureServices方法中添加以下代码:

    //using MS.DbContexts;
    //using MS.UnitOfWork;
    //using Microsoft.EntityFrameworkCore;
    //以上添加到using引用
    services.AddUnitOfWorkService<MSDbContext>(options => { options.UseMySql(Configuration.GetSection("ConectionStrings:MSDbContext").Value); });
    

    说明:

    • 《1-项目结构分层建立》中,MS.WebApi应用程序引用了MS.Services,层层套娃,最终引用了MS.UnitOfWork,所以可以使用AddUnitOfWorkService方法
    • 这里注册数据库用的是MySQL,所以是UseMySql方法

    修改网站启动逻辑

    Program.cs类中,修改Main方法为以下内容(覆盖原先的Main方法内容):

    //using MS.DbContexts;
    //using MS.UnitOfWork;
    //以上代码添加到using
    public static void Main(string[] args)
    {
        try
        {
            var host = CreateHostBuilder(args).Build();
            using (IServiceScope scope = host.Services.CreateScope())
            {
                //初始化数据库
                DBSeed.Initialize(scope.ServiceProvider.GetRequiredService<IUnitOfWork<MSDbContext>>());
            }
            host.Run();
        }
        catch (Exception ex)
        {
            throw;
        }
    }
    

    至此,所有的修改已完成,网站启动将执行DBSeed.Initialize方法来初始化数据
    项目完成后,如下图

    启动项目,此时可以看见控制台EntityFramworkCore的日志:

    而数据库中也生成了对应的数据库:

  • 相关阅读:
    086 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 03 面向对象基础总结 01 面向对象基础(类和对象)总结
    085 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 02 构造方法介绍 04 构造方法调用
    jQuery UI组件库Kendo UI使用技巧小分享
    Kendo UI ListView模板功能,让Web开发更轻松
    UI组件套包DevExpress ASP.NET Core v20.2新版亮点:全新的查询生成器
    Devexpress WinForms最新版开发.NET环境配置Visual Studo和SQL Server对应版本
    全新的桌面应用数据可视化呈现方式,Sankey Diagram控件你了解多少?
    java中的递归方法
    连接数据库查询 将查询结果写入exce文件中
    java连接mysql数据查询数据
  • 原文地址:https://www.cnblogs.com/kasnti/p/12238012.html
Copyright © 2011-2022 走看看