從壹開始前后端分離[.NetCore ] 38 ║自動初始化數據庫(不定期更新)


緣起

哈嘍大家好呀,我們又見面啦,這里先祝大家聖誕節快樂喲,昨天的紅包不知道有沒有小伙伴搶到呢。今天的這篇內容灰常簡單,只是對我們的系統的數據庫進行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 大神的視頻:

https://www.bilibili.com/video/av38392956/?p=2

 

四、測試結果

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM