天天看點

ASP.NET Core搭建多層網站架構【5-網站資料庫實體設計及映射配置】

基于ASP.NET Core 3.1 WebApi搭建後端多層網站架構【5-網站資料庫實體設計及映射配置】

網站資料庫實體設計,使用EntityFrameworkCore 3.1 FluentAPI映射配置實體,網站啟動時建立資料庫并添加種子資料,開發調試時可以看到執行的具體sql語句

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-網站資料庫實體設計及映射配置】

文章目錄

此分支項目代碼

本章節介紹背景管理的網站資料庫實體設計,使用FluentAPI方式配置資料庫字段映射,網站啟動時建立資料庫并添加種子資料

需求分析

首先要實作的功能有使用者登入、角色管理、日志記錄

大概有四張表:使用者表、密碼表、角色表、日志表

日志表:

ASP.NET Core搭建多層網站架構【5-網站資料庫實體設計及映射配置】

使用者表:

ASP.NET Core搭建多層網站架構【5-網站資料庫實體設計及映射配置】

密碼表:

ASP.NET Core搭建多層網站架構【5-網站資料庫實體設計及映射配置】

角色表:

ASP.NET Core搭建多層網站架構【5-網站資料庫實體設計及映射配置】

好像部落格園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; }
    }
}
           

至此,實體類都已完成設計

項目完成後,如下圖

ASP.NET Core搭建多層網站架構【5-網站資料庫實體設計及映射配置】

建立映射配置

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

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兩種日志輸出,會輸出到控制台和調試視窗

至此,資料通路層建立完畢,項目完成後如下圖所示

ASP.NET Core搭建多層網站架構【5-網站資料庫實體設計及映射配置】

建立資料種子

目前我所知道的資料庫的建立有三種(生成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;"
  }
           

修改後如下圖所示:

ASP.NET Core搭建多層網站架構【5-網站資料庫實體設計及映射配置】

開啟EntityFrameworkCore日志

appsettings.Development.json

的"Logging:LogLevel"節點添加:

"Microsoft.EntityFrameworkCore": "Information"
           

修改完成後,如下圖所示

ASP.NET Core搭建多層網站架構【5-網站資料庫實體設計及映射配置】

為什麼要把開啟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方法來初始化資料

ASP.NET Core搭建多層網站架構【5-網站資料庫實體設計及映射配置】

啟動項目,此時可以看見控制台EntityFramworkCore的日志:

ASP.NET Core搭建多層網站架構【5-網站資料庫實體設計及映射配置】

而資料庫中也生成了對應的資料庫:

ASP.NET Core搭建多層網站架構【5-網站資料庫實體設計及映射配置】