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.1html
摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站架构【5-网站数据库实体设计及映射配置】
网站数据库实体设计,使用EntityFrameworkCore 3.1 FluentAPI映射配置实体,网站启动时建立数据库并添加种子数据,开发调试时能够看到执行的具体sql语句mysql
文章目录git
此分支项目代码github
本章节介绍后台管理的网站数据库实体设计,使用FluentAPI方式配置数据库字段映射,网站启动时建立数据库并添加种子数据算法
首先要实现的功能有用户登陆、角色管理、日志记录
大概有四张表:用户表、密码表、角色表、日志表
日志表:
sql
用户表:
数据库
密码表:
json
角色表:
后端
好像博客园md不支持表格功能?因此只能截图展现,excel表格上传至项目docs文件夹中架构
在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.Entities
、MS.UnitOfWork
类库
在MS.DbContexts
类库中添加Mappings文件夹,在该文件夹中添加 LogrecordMap.cs
、RoleMap.cs
、UserLoginMap.cs
、UserMap.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); } } }
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);//默认不查询软删除数据 } } }
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); } } }
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);//默认不查询软删除数据 } } }
至此映射配置完成
在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); } } }
说明:
至此,数据访问层建立完毕,项目完成后以下图所示
目前我所知道的数据库的建立有三种(生成sql语句单独执行建立暂不讨论):
1、三两种方法的差异我在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中:
在appsettings.json
中添加数据库链接字符串(具体的链接自行配置):
"ConectionStrings": { "MSDbContext": "server=192.168.137.10;database=MSDB;user=root;password=mysql@local;" }
修改后以下图所示:
在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); });
说明:
MS.WebApi
应用程序引用了MS.Services
,层层套娃,最终引用了MS.UnitOfWork
,因此可使用AddUnitOfWorkService方法在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的日志:
而数据库中也生成了对应的数据库: