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的日誌:
而數據庫中也生成了對應的數據庫: