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的日志:

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

ASP.NET Core搭建多层网站架构【5-网站数据库实体设计及映射配置】的更多相关文章

  1. ASP.NET Core搭建多层网站架构【0-前言】

    2020/01/26, ASP.NET Core 3.1, VS2019 摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站架构 目录 0-前言 1-项目结构分层建立 2-公共基 ...

  2. ASP.NET Core搭建多层网站架构【7-使用NLog日志记录器】

    2020/01/29, ASP.NET Core 3.1, VS2019, NLog.Web.AspNetCore 4.9.0 摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站 ...

  3. ASP.NET Core搭建多层网站架构【1-项目结构分层建立】

    2020/01/26, ASP.NET Core 3.1, VS2019 摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站架构[1-项目结构分层建立] 文章目录 此分支项目代码 ...

  4. ASP.NET Core搭建多层网站架构【2-公共基础库】

    2020/01/28, ASP.NET Core 3.1, VS2019,Newtonsoft.Json 12.0.3, Microsoft.AspNetCore.Cryptography.KeyDe ...

  5. ASP.NET Core搭建多层网站架构【3-xUnit单元测试之简单方法测试】

    2020/01/28, ASP.NET Core 3.1, VS2019, xUnit 2.4.0 摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站架构[3-xUnit单元测试 ...

  6. ASP.NET Core搭建多层网站架构【4-工作单元和仓储设计】

    2020/01/28, ASP.NET Core 3.1, VS2019, Microsoft.EntityFrameworkCore.Relational 3.1.1 摘要:基于ASP.NET Co ...

  7. ASP.NET Core搭建多层网站架构【6-注册跨域、网站核心配置】

    2020/01/29, ASP.NET Core 3.1, VS2019, NLog.Web.AspNetCore 4.9.0 摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站 ...

  8. ASP.NET Core搭建多层网站架构【8.1-使用ViewModel注解验证】

    2020/01/29, ASP.NET Core 3.1, VS2019 摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站架构[8.1-使用ViewModel注解验证] 使用V ...

  9. ASP.NET Core搭建多层网站架构【8.2-使用AutoMapper映射实体对象】

    2020/01/29, ASP.NET Core 3.1, VS2019, AutoMapper.Extensions.Microsoft.DependencyInjection 7.0.0 摘要:基 ...

随机推荐

  1. Python - isinstance()更深刻的理解

    起因经过 今天翻collections.abc的文档时,我知道list的实例在逻辑上(因为duck typing 鸭子类型)是Container和不能hash的(因为list可变),就试下面的代码是否 ...

  2. Python基础与科学计算常用方法

    Python基础与科学计算常用方法 本文使用的是Jupyter Notebook,Python3.你可以将代码直接复制到Jupyter Notebook中运行,以便更好的学习. 导入所需要的头文件 i ...

  3. 百度地图使用http ,https

    通过判断http或https if($_SERVER['REQUEST_SCHEME']=='http'){ return true; }else{ return false; } https网站使用 ...

  4. 每天进步一点点------Allegro 布线时显示延迟以及相对延迟信息

    PROPAGATION_DELAYPROPAGATION_DELAY这个设定主要用来对Net绝对长度的设定,如要求设定一组Net的长度要在Min Mil到 Max Mil之间的话,就可以用这种设定来完 ...

  5. 每天进步一点点------MicroBlaze

             有了前面两个实例的铺垫,下面这个工程就要带大家尝试搭建一个基于MicroBlaze的应用.特权同学也是第一次接插Xilinx的嵌入式开发平台,跑了一个流程下来,正如所料,和Alter ...

  6. qt5.9.0 msvc2015优雅的崩溃:dumpfile

    交给客户的软件奔溃了怎么办? 我们不能再客户电脑上安装vs,也不想傻傻的用log来猜测出错的地方. 利用Dbghelp可以解决这一问题. 首先是vs生成release版本的时候需要同时生成pdb文件, ...

  7. 【一句话解释】docker and vm

    效果 在一个host上面运行多个os,达到快速部署以及充分利用资源的额目的 vm 虚拟机,会模拟一个完整的操作系统堆栈出来. 缺点开销大,优点,guest os 是一个完整的操作系统 根据hyperv ...

  8. hadoop fs -put could only be replicated to 0 nodes, instead of 1 解决方法

    我的坏境是在虚拟机linux操作系统中,启动start-all.sh后 1.执行jps,如下 2.执行hadoop fs -mkdir input 创建成功 执行hadoop fs -ls 可以看到i ...

  9. Linux - XShell - alt 快捷键的设置

    1. 概述 命令行的 alt 快捷键可能会冲突 2. 环境 os win10 centos7 xshell xhell6 3. 场景 开启 centos7 虚拟机 在 win10 打开 xshell6 ...

  10. jvm字节码助记符

    反编译指令 javap -c xxxx.class JVM参数设置 -xx:+<option>                  开启option -xx: -<option> ...