緣起
哈嘍大家好呀,我們又見面啦,這里先祝大家聖誕節快樂喲,昨天的紅包不知道有沒有小伙伴搶到呢。今天的這篇內容灰常簡單,只是對我們的系統的數據庫進行CodeFirst,然后就是數據處理,因為這幾個月來,還是有小伙伴陸陸續續的向我索要數據,本來想着都是很簡單的數據,就不給了,然后僅僅是提供了一個Sql的表結構,但是想想一個完整的項目,怎么能沒有一個初始化的功能呢(不僅僅是表結構,還是要一些簡單的數據)?所以就想着寫今天這篇文章了,這篇文章雖然可能看起來很簡單,不過也是給大家提供了一個思路,就是自己以后在寫項目的時候,如何添加一個初始化的Seed Data,我也是參考其他小伙伴的,這里重點表揚下QQ群里,@初久童鞋,沒有他的博客園地址,就沒辦法放他的首頁了。
投稿作者:初久,個人地址:null,實現項目啟動的時候,自動初始化數據,其中會涉及到上下文、SqlSugar、依賴注入等知識。
好啦,話不多說,直接開始動手。
一、對Mode實體類進行配置
因為要使用到了CodeFirst了,所以我們必須要對我們的實體類 Model 進行配置,當然也有很多的小伙伴,使用的是EFCore,當然是可以的,EFCore不需要對實體類進行處理,只是需要額外的配置上下文和Map映射,比如這樣:

EFCore的我就不多少了,很簡單,如果有不會的小伙伴,可以看我的第二個系列的《讓你明白DDD的小故事 & EFCore初探》和《剪不斷理還亂的 值對象和Dto》這兩篇文章都有對EFCore的配置有提到,有需要的可以看看。
因為本系列是用的SqlSugar ORM,所以就來說說,它是如何配置的,那咱們就配置下我們的SqlSugar吧。
這里只用 Advertisement.cs 來舉例吧,其他的,大家可以自行去查看我的Github上的code:
public class Advertisement : RootEntity { /// <summary> /// 廣告圖片 /// </summary> [SugarColumn(Length = 512, IsNullable = true)] public string ImgUrl { get; set; } /// <summary> /// 廣告標題 /// </summary> [SugarColumn(Length = 64, IsNullable = true)] public string Title { get; set; } /// <summary> /// 廣告鏈接 /// </summary> [SugarColumn(Length = 256, IsNullable = true)] public string Url { get; set; } /// <summary> /// 備注 /// </summary> [SugarColumn(Length = int.MaxValue, IsNullable = true)] public string Remark { get; set; } /// <summary> /// 創建時間 /// </summary> public DateTime Createdate { get; set; } = DateTime.Now; } public class RootEntity { /// <summary> /// ID /// </summary> [SugarColumn(IsNullable = false, IsPrimaryKey = true, IsIdentity = true)] public int Id { get; set; } }
大家可以看到,SqlSugar 和 EFCore在操作上還是不一樣的,sugar不需要配置額外的Map 映射,只需要對當前類進行操作,不過還是有很多小伙伴反映,還是EFCore 在使用上或者在功能上更健壯,這里就不多說二者了,今天的主題是數據自動初始化,不能本末倒置了。
這個就很簡單的了,主要就是屬性 SugarColumn() ,里邊有一些屬性,可以自行配置,這里給大家簡單注釋一下:
public class SugarColumn : Attribute { public SugarColumn(); public string ColumnName { get; set; }//列名 public bool IsIgnore { get; set; }//是否忽略 public bool IsPrimaryKey { get; set; }//是否是主鍵 public bool IsIdentity { get; set; }//是否自增 public string MappingKeys { get; set; }//映射key public string ColumnDescription { get; set; }//列描述 public int Length { get; set; }//長度 public bool IsNullable { get; set; }//是否為空 public string OldColumnName { get; set; }//舊的列名 public string ColumnDataType { get; set; }//列類型,自定義 public int DecimalDigits { get; set; }//dicimal精度 public string OracleSequenceName { get; set; }//Oracle序列名 public bool IsOnlyIgnoreInsert { get; set; }//是否僅對添加忽略 public bool IsEnableUpdateVersionValidation { get; set; } }

這里我已經配置完成了,而且是盡量的仿照着我的數據庫來的,可能會有細微的差別,如果你要想使用的話,可以用一個測試的數據庫來實驗。
二、配置上下文與初始數據
大家是否還記得之前在倉儲Repository中,我們創建了一個上下文,這里可以直接拿來用,不過因為我們的 API 層已經和 Repository 層解耦分割了,所以我就在 Mode 層來實現這個功能吧。如果你不解耦,可以直接使用倉儲層的上下文即可。
1、建立 SqlSugar 上下文
在 Blog.Core.Model 層新建一個 Seed 文件夾,然后把倉儲層中的 context 拷貝過去,我重命名為 MyContext.cs:

重點就是構造函數,要實現實例化 SqlSugarClient 的作用:
public MyContext() { if (string.IsNullOrEmpty(_connectionString)) throw new ArgumentNullException("數據庫連接字符串為空"); _db = new SqlSugarClient(new ConnectionConfig() { ConnectionString = _connectionString,//數據庫字符串 DbType = DbType.SqlServer,//數據庫類型 IsAutoCloseConnection = true,//自動關閉數據庫 IsShardSameThread = false,//啟用異步多線程 InitKeyType = InitKeyType.Attribute,//mark ConfigureExternalServices = new ConfigureExternalServices() { //DataInfoCacheService = new HttpRuntimeCache() }, MoreSettings = new ConnMoreSettings() { //IsWithNoLockQuery = true, IsAutoRemoveDataCache = true } }); }

2、實現初始化種子數據的功能
上邊咱們創建了好上下文,那接下來咱們就應該實現 CodeFirst 功能了,
還是再 Seed 文件夾,新建 DBSeed.cs 類:
public class DBSeed { /// <summary> /// 異步添加種子數據 /// </summary> /// <param name="myContext"></param> /// <returns></returns> public static async Task SeedAsync(MyContext myContext) { try {
// 注意!一定要先手動創建一個空的數據庫,5.x 版本會自動創建數據庫了 // 會覆蓋,可以設置為true,來備份數據 // 如果生成過了,第二次,就不用再執行一遍了,注釋掉該方法即可 myContext.CreateTableByEntity(false, typeof(Advertisement), typeof(BlogArticle), typeof(Guestbook), typeof(Module), typeof(ModulePermission), typeof(OperateLog),
typeof(PasswordLib), typeof(Permission), typeof(Role), typeof(RoleModulePermission), typeof(sysUserInfo), typeof(Topic), typeof(TopicDetail), typeof(UserRole));
// 下邊的就是種子數據 #region Advertisement if (!await myContext.Db.Queryable<Advertisement>().AnyAsync()) { myContext.GetEntityDB<Advertisement>().Insert( new Advertisement() { Createdate = DateTime.Now, Remark = "mark", Title = "good" }); } #endregion #region BlogArticle Guestbook if (!await myContext.Db.Queryable<BlogArticle>().AnyAsync()) { int bid = myContext.GetEntityDB<BlogArticle>().InsertReturnIdentity( new BlogArticle() { bsubmitter = "admins", btitle = "老張的哲學", bcategory = "技術博文", bcontent = "<p>1。。。。。。", btraffic = 1, bcommentNum = 0, bUpdateTime = DateTime.Now, bCreateTime = DateTime.Now }); if (bid > 0) { if (!await myContext.Db.Queryable<Guestbook>().AnyAsync()) { myContext.GetEntityDB<Guestbook>().Insert( new Guestbook() { blogId = bid, createdate = DateTime.Now, username = "user", phone = "110", QQ = "100", body = "很不錯", ip = "127.0.0.1", isshow = true, }); } } } #endregion #region Module int mid = 0; if (!await myContext.Db.Queryable<Module>().AnyAsync()) { mid = myContext.GetEntityDB<Module>().InsertReturnIdentity( new Module() { IsDeleted = false, Name = "values的接口信息", LinkUrl = "/api/values", OrderSort = 1, IsMenu = false, Enabled = true, }); } #endregion #region Role int rid = 0; if (!await myContext.Db.Queryable<Role>().AnyAsync()) { rid = myContext.GetEntityDB<Role>().InsertReturnIdentity( new Role() { IsDeleted = false, Name = "Admin", Description = "我是一個admin管理員", OrderSort = 1, CreateTime = DateTime.Now, Enabled = true, ModifyTime = DateTime.Now }); } #endregion #region RoleModulePermission if (mid > 0 && rid > 0) { if (!await myContext.Db.Queryable<RoleModulePermission>().AnyAsync()) { myContext.GetEntityDB<RoleModulePermission>().Insert( new RoleModulePermission() { IsDeleted = false, RoleId = rid, ModuleId = mid, CreateTime = DateTime.Now, ModifyTime = DateTime.Now }); } } #endregion #region sysUserInfo int uid = 0; if (!await myContext.Db.Queryable<sysUserInfo>().AnyAsync()) { uid = myContext.GetEntityDB<sysUserInfo>().InsertReturnIdentity( new sysUserInfo() { uLoginName = "admins", uLoginPWD = "admins", uRealName = "admins", uStatus = 0, uCreateTime = DateTime.Now, uUpdateTime = DateTime.Now, uLastErrTime = DateTime.Now, uErrorCount = 0 }); } #endregion #region UserRole if (uid > 0 && rid > 0) { if (!await myContext.Db.Queryable<UserRole>().AnyAsync()) { myContext.GetEntityDB<UserRole>().Insert( new UserRole() { IsDeleted = false, UserId = uid, RoleId = rid, CreateTime = DateTime.Now, ModifyTime = DateTime.Now }); } } #endregion #region Topic TopicDetail if (!await myContext.Db.Queryable<Topic>().AnyAsync()) { int tid = myContext.GetEntityDB<Topic>().InsertReturnIdentity( new Topic() { tLogo = "/Upload/20180626/95445c8e288e47e3af7a180b8a4cc0c7.jpg", tName = "《羅馬人的故事》", tDetail = "這是一個盪氣回腸的故事", tIsDelete = false, tRead = 0, tCommend = 0, tGood = 0, tCreatetime = DateTime.Now, tUpdatetime = DateTime.Now, tAuthor = "laozhang" }); if (tid > 0) { if (!await myContext.Db.Queryable<TopicDetail>().AnyAsync()) { myContext.GetEntityDB<TopicDetail>().Insert( new TopicDetail() { TopicId = tid, tdLogo = "/Upload/20180627/7548de20944c45d48a055111b5a6c1b9.jpg", tdName = "第一章 羅馬的誕生 第一節 傳說的年代", tdContent = "<p>第一節 傳說的年代</時代走出,近入了歷史時代。</p><p><br></p>", tdDetail = "第一回", tdIsDelete = false, tdRead = 1, tdCommend = 0, tdGood = 0, tdCreatetime = DateTime.Now, tdUpdatetime = DateTime.Now, tdTop = 0, }); } } } #endregion } catch (Exception ex) { } } }
是不是很簡單,上邊的 CreateTableByEntity 是用來創建數據庫的表結構的,第一次執行完成后,剩下的就可以不用執行了。下邊的是添加種子數據,我增加了判斷,其他的大家可以自定義處理。
這個時候我們已經把初始化表結構,和添加種子數據完成了,那我們應該怎么用呢,別慌,請往下看。
三、在項目啟動的時候,執行初始化
1、將上邊的類注入服務
這個很簡單,相信大家都能看懂,我就直接注入到服務,然后服務會自動注入到Autofac:

2、在主程序 Main 中啟動初始化
相信大家都應該知道,其實 .net core 本身是一個控制台程序,所以項目啟動是在 Program.cs 中的 Main主程序方法中的,我們做一下修改:
public class Program { public static void Main(string[] args) { // 生成承載 web 應用程序的 Microsoft.AspNetCore.Hosting.IWebHost。Build是WebHostBuilder最終的目的,將返回一個構造的WebHost。 var host = CreateWebHostBuilder(args).Build(); // 創建可用於解析作用域服務的新 Microsoft.Extensions.DependencyInjection.IServiceScope。 using (var scope = host.Services.CreateScope()) { var services = scope.ServiceProvider; var loggerFactory = services.GetRequiredService<ILoggerFactory>(); try { // 從 system.IServicec提供程序獲取 T 類型的服務。 var myContext = services.GetRequiredService<MyContext>(); DBSeed.SeedAsync(myContext).Wait(); } catch (Exception e) { var logger = loggerFactory.CreateLogger<Program>(); logger.LogError(e, "Error occured seeding the Database."); } } // 運行 web 應用程序並阻止調用線程, 直到主機關閉。 // 創建完 WebHost 之后,便調用它的 Run 方法,而 Run 方法會去調用 WebHost 的 StartAsync 方法 // 將Initialize方法創建的Application管道傳入以供處理消息 // 執行HostedServiceExecutor.StartAsync方法 host.Run(); } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => //使用預配置的默認值初始化 Microsoft.AspNetCore.Hosting.WebHostBuilder 類的新實例。 WebHost.CreateDefaultBuilder(args) //指定要由 web 主機使用的啟動類型。相當於注冊了一個IStartup服務。 .UseStartup<Startup>(); }
執行流程就是,我們項目啟動,首先會創建一個初始化WebHostBuilder 實例,然后使用啟動默認的 Startup 服務,當然你也可以自定義這個啟動服務,比如 StatupDevelopment 。
這樣寫 .UseStartup(typeof(StartupDevelopment).GetTypeInfo().Assembly.FullName)
接下來,就是 Build 我們的剛剛實例化的 webhostbuilder ,生成一個 WebHost 宿主主機。
中間我們就可以對宿主下的服務進行配置,
最后就是執行 Run() 方法,啟動應用程序,直到主機關閉。
如果有小伙伴想更多的了解 .net core 的啟動配置相關知識,可以看這里有一個QQ群管理Dave 大神的視頻:
四、測試結果
1、用動圖來演示效果
經過配置,我這里先建立了一個空的數據庫 DBInitTest ,然后看看效果:

這里要注意下:根據數據庫大小的不同,中間可能經歷的時間不一樣,我們已經成功的生成了數據庫,並初始化出來了數據。
好啦,今天的這個小技巧就說到這里了,你也可以根據自己的情況,根據自己的ORM來設計喲,特別適用於一個給別人展示的Demo項目,和自己的小項目。
2、如果用EFCore會更簡單
上邊咱們說到了,有的小伙伴會使用EFCore,而且上邊咱們也簡單說了,在EFCore 進行實體映射以后,就可以直接進行Code First 和 種子數據初始化了:
官方地址:https://docs.microsoft.com/en-us/ef/core/modeling/data-seeding
try { // TODO: Only run this if using a real database myContext.Database.Migrate(); if (!myContext.Posts.Any()) { myContext.Posts.AddRange( new List<Post>{ new Post{ Title = "Post Title 1", Body = "Post Body 1", Author = "Dave", LastModified = DateTime.Now } } ); await myContext.SaveChangesAsync(); } }
最后,聖誕節快樂
最后來個今天火的不得了的小圖:

(圖片來源於網絡,侵刪)
五、Github & Gitee
https://github.com/anjoy8/Blog.Core
https://gitee.com/laozhangIsPhi/Blog.Core
--END
