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. 升级openssh编译报错“configure: error: *** working libcrypto not found, check config.log”的解决办法

    问题描述 在linux上,欲将OpenSSH_6.4p1编译升级到OpenSSH_8.0p1时,执行了./configure --prefix=/usr --sysconfdir=/etc/ssh - ...

  2. python面试的100题(9)

    17.python如何实现单例模式?请写出两种实现方式? 第一种方法:使用装饰器 def singleton(cls): instances = {} def wrapper(*args, **kwa ...

  3. C#泛型应用及原理

    https://blog.csdn.net/ananlele_/article/details/97623254 https://blog.csdn.net/kebi007/article/detai ...

  4. ubuntu---yolo报错darknet: ./src/cuda.c:36: check_error: Assertion `0' failed.

    装好darknet后,直接测试的时候,报错: darknet: ./src/cuda.c:36: check_error: Assertion `0' failed.解决办法是打开yolov3.cfg ...

  5. 智能指针和异常、 weak_ptr、unique_ptr

    12.1.4智能指针和异常 1.在块中创建的动态内存,如果是由内置指针来指向这块内存,那么若是在块结束时未delete这个指针,则该内存不会被释放,若在delete之前发生异常,由于还没执行delet ...

  6. 题解【UVA839】天平 Not so Mobile

    Description Input Output Examples Input 1 0 2 0 4 0 3 0 1 1 1 1 1 2 4 4 2 1 6 3 2 Output YES Transla ...

  7. C. Polygon for the Angle 几何数学

    C. Polygon for the Angle 几何数学 题意 给出一个度数 ,问可以实现的最小的n的n边形是多少 思路 由n边形的外角和是180度直接就可以算出最小的角是多少 如果给出的度数是其最 ...

  8. 【做题笔记】洛谷P1036 选数

    作为一个 DFS 初学者这题真的做得很惨...其实窝学 DFS 一年多了,然后一开始就学不会最近被图论和数据结构打自闭后才准备好好学一学233 一开始,直接套框架,于是就有 #include < ...

  9. 搭建FEBS权限系统

    在码云看到一个FEBS权限系统,但是没有找到搭建手册,自己记录一下. 1.下载项目:https://github.com/wuyouzhuguli/FEBS-Shiro2.创建数据库:执行sql文件夹 ...

  10. Unity Coroutine详解(一)

    Unity 中协程是个非常强大的功能,其作用主要是用于游戏中的延时调用或者执行一连串的有时间间隔的事件流程,例如剧情对话等.简单总结了几点协程相关的知识点,旨在加深记忆,同时为初学者解惑. 1.协程. ...