緣起
哈嘍大家好呀,我們又見面啦棠绘,這里先祝大家圣誕節(jié)快樂喲件相,昨天的紅包不知道有沒有小伙伴搶到呢。今天的這篇內(nèi)容灰常簡(jiǎn)單氧苍,只是對(duì)我們的系統(tǒng)的數(shù)據(jù)庫進(jìn)行CodeFirst夜矗,然后就是數(shù)據(jù)處理,因?yàn)檫@幾個(gè)月來让虐,還是有小伙伴陸陸續(xù)續(xù)的向我索要數(shù)據(jù)紊撕,本來想著都是很簡(jiǎn)單的數(shù)據(jù),就不給了赡突,然后僅僅是提供了一個(gè)Sql的表結(jié)構(gòu)对扶,但是想想一個(gè)完整的項(xiàng)目,怎么能沒有一個(gè)初始化的功能呢(不僅僅是表結(jié)構(gòu)惭缰,還是要一些簡(jiǎn)單的數(shù)據(jù))浪南?所以就想著寫今天這篇文章了,這篇文章雖然可能看起來很簡(jiǎn)單漱受,不過也是給大家提供了一個(gè)思路络凿,就是自己以后在寫項(xiàng)目的時(shí)候,如何添加一個(gè)初始化的Seed Data,我也是參考其他小伙伴的絮记,這里重點(diǎn)表揚(yáng)下QQ群里摔踱,@初久童鞋,沒有他的博客園地址到千,就沒辦法放他的首頁了昌渤。
投稿作者:初久,個(gè)人地址:null憔四,實(shí)現(xiàn)項(xiàng)目啟動(dòng)的時(shí)候膀息,自動(dòng)初始化數(shù)據(jù),其中會(huì)涉及到上下文了赵、SqlSugar潜支、依賴注入等知識(shí)。
好啦柿汛,話不多說冗酿,直接開始動(dòng)手。
一络断、對(duì)Mode實(shí)體類進(jìn)行配置
因?yàn)橐褂玫搅薈odeFirst了裁替,所以我們必須要對(duì)我們的實(shí)體類 Model 進(jìn)行配置,當(dāng)然也有很多的小伙伴貌笨,使用的是EFCore弱判,當(dāng)然是可以的,EFCore不需要對(duì)實(shí)體類進(jìn)行處理锥惋,只是需要額外的配置上下文和Map映射昌腰,比如這樣:
EFCore的我就不多少了,很簡(jiǎn)單膀跌,如果有不會(huì)的小伙伴遭商,可以看我的第二個(gè)系列的《讓你明白DDD的小故事 & EFCore初探》和《剪不斷理還亂的 值對(duì)象和Dto》這兩篇文章都有對(duì)EFCore的配置有提到,有需要的可以看看捅伤。
因?yàn)楸鞠盗惺怯玫腟qlSugar ORM劫流,所以就來說說,它是如何配置的丛忆,那咱們就配置下我們的SqlSugar吧祠汇。
這里只用 Advertisement.cs 來舉例吧,其他的蘸际,大家可以自行去查看我的Github上的code:
public class Advertisement : RootEntity
{
/// <summary>
/// 廣告圖片
/// </summary>
[SugarColumn(Length = 512, IsNullable = true)]
public string ImgUrl { get; set; }
/// <summary>
/// 廣告標(biāo)題
/// </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>
/// 創(chuàng)建時(shí)間
/// </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 映射粮彤,只需要對(duì)當(dāng)前類進(jìn)行操作,不過還是有很多小伙伴反映,還是EFCore 在使用上或者在功能上更健壯导坟,這里就不多說二者了屿良,今天的主題是數(shù)據(jù)自動(dòng)初始化,不能本末倒置了惫周。
這個(gè)就很簡(jiǎn)單的了尘惧,主要就是屬性 SugarColumn() ,里邊有一些屬性递递,可以自行配置喷橙,這里給大家簡(jiǎn)單注釋一下:
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; }//長(zhǎng)度
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; }//是否僅對(duì)添加忽略
public bool IsEnableUpdateVersionValidation { get; set; }
}
這里我已經(jīng)配置完成了登舞,而且是盡量的仿照著我的數(shù)據(jù)庫來的贰逾,可能會(huì)有細(xì)微的差別,如果你要想使用的話菠秒,可以用一個(gè)測(cè)試的數(shù)據(jù)庫來實(shí)驗(yàn)疙剑。
二、配置上下文與初始數(shù)據(jù)
大家是否還記得之前在倉儲(chǔ)Repository中践叠,我們創(chuàng)建了一個(gè)上下文言缤,這里可以直接拿來用,不過因?yàn)槲覀兊?API 層已經(jīng)和 Repository 層解耦分割了禁灼,所以我就在 Mode 層來實(shí)現(xiàn)這個(gè)功能吧管挟。如果你不解耦,可以直接使用倉儲(chǔ)層的上下文即可匾二。
1哮独、建立 SqlSugar 上下文
在 Blog.Core.Model 層新建一個(gè) Seed 文件夾,然后把倉儲(chǔ)層中的 context 拷貝過去察藐,我重命名為 MyContext.cs:
重點(diǎn)就是構(gòu)造函數(shù)皮璧,要實(shí)現(xiàn)實(shí)例化 SqlSugarClient 的作用:
public MyContext()
{
if (string.IsNullOrEmpty(_connectionString)) throw new ArgumentNullException("數(shù)據(jù)庫連接字符串為空");
_db = new SqlSugarClient(new ConnectionConfig()
{
ConnectionString = _connectionString,//數(shù)據(jù)庫字符串
DbType = DbType.SqlServer,//數(shù)據(jù)庫類型
IsAutoCloseConnection = true,//自動(dòng)關(guān)閉數(shù)據(jù)庫
IsShardSameThread = false,//啟用異步多線程
InitKeyType = InitKeyType.Attribute,//mark
ConfigureExternalServices = new ConfigureExternalServices()
{ //DataInfoCacheService = new HttpRuntimeCache()
},
MoreSettings = new ConnMoreSettings()
{ //IsWithNoLockQuery = true,
IsAutoRemoveDataCache = true }
});
}
2、實(shí)現(xiàn)初始化種子數(shù)據(jù)的功能
上邊咱們創(chuàng)建了好上下文分飞,那接下來咱們就應(yīng)該實(shí)現(xiàn) CodeFirst 功能了悴务,
還是再 Seed 文件夾,新建 DBSeed.cs 類:
public class DBSeed
{
/// <summary>
/// 異步添加種子數(shù)據(jù) /// </summary>
/// <param name="myContext"></param>
/// <returns></returns>
public static async Task SeedAsync(MyContext myContext)
{
try {
// 注意譬猫!一定要先手動(dòng)創(chuàng)建一個(gè)空的數(shù)據(jù)庫 // 會(huì)覆蓋讯檐,可以設(shè)置為true,來備份數(shù)據(jù) // 如果生成過了染服,第二次别洪,就不用再執(zhí)行一遍了,注釋掉該方法即可
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));
// 下邊的就是種子數(shù)據(jù) #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 = "老張的哲學(xué)",
bcategory = "技術(shù)博文",
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 = "很不錯(cuò)",
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 = "我是一個(gè)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 = "這是一個(gè)蕩氣回腸的故事",
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 = "第一章 羅馬的誕生 第一節(jié) 傳說的年代",
tdContent = "<p>第一節(jié) 傳說的年代</時(shí)代走出送矩,近入了歷史時(shí)代。</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)
{
}
}
}
是不是很簡(jiǎn)單哪替,上邊的 CreateTableByEntity 是用來創(chuàng)建數(shù)據(jù)庫的表結(jié)構(gòu)的栋荸,第一次執(zhí)行完成后,剩下的就可以不用執(zhí)行了凭舶。下邊的是添加種子數(shù)據(jù)晌块,我增加了判斷,其他的大家可以自定義處理库快。
這個(gè)時(shí)候我們已經(jīng)把初始化表結(jié)構(gòu)摸袁,和添加種子數(shù)據(jù)完成了,那我們應(yīng)該怎么用呢义屏,別慌靠汁,請(qǐng)往下看。
三闽铐、在項(xiàng)目啟動(dòng)的時(shí)候蝶怔,執(zhí)行初始化
1、將上邊的類注入服務(wù)
這個(gè)很簡(jiǎn)單兄墅,相信大家都能看懂踢星,我就直接注入到服務(wù),然后服務(wù)會(huì)自動(dòng)注入到Autofac:
2隙咸、在主程序 Main 中啟動(dòng)初始化
相信大家都應(yīng)該知道沐悦,其實(shí) .net core 本身是一個(gè)控制臺(tái)程序,所以項(xiàng)目啟動(dòng)是在 Program.cs 中的 Main主程序方法中的五督,我們做一下修改:
public class Program
{
public static void Main(string[] args)
{
// 生成承載 web 應(yīng)用程序的 Microsoft.AspNetCore.Hosting.IWebHost藏否。Build是WebHostBuilder最終的目的,將返回一個(gè)構(gòu)造的WebHost充包。
var host = CreateWebHostBuilder(args).Build(); // 創(chuàng)建可用于解析作用域服務(wù)的新 Microsoft.Extensions.DependencyInjection.IServiceScope副签。
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider; var loggerFactory = services.GetRequiredService<ILoggerFactory>(); try { // 從 system.IServicec提供程序獲取 T 類型的服務(wù)。
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.");
}
} // 運(yùn)行 web 應(yīng)用程序并阻止調(diào)用線程, 直到主機(jī)關(guān)閉基矮。 // 創(chuàng)建完 WebHost 之后淆储,便調(diào)用它的 Run 方法,而 Run 方法會(huì)去調(diào)用 WebHost 的 StartAsync 方法 // 將Initialize方法創(chuàng)建的Application管道傳入以供處理消息 // 執(zhí)行HostedServiceExecutor.StartAsync方法
host.Run();
} public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
//使用預(yù)配置的默認(rèn)值初始化 Microsoft.AspNetCore.Hosting.WebHostBuilder 類的新實(shí)例家浇。
WebHost.CreateDefaultBuilder(args) //指定要由 web 主機(jī)使用的啟動(dòng)類型本砰。相當(dāng)于注冊(cè)了一個(gè)IStartup服務(wù)。
.UseStartup<Startup>();
}
執(zhí)行流程就是钢悲,我們項(xiàng)目啟動(dòng)点额,首先會(huì)創(chuàng)建一個(gè)初始化WebHostBuilder 實(shí)例青团,然后使用啟動(dòng)默認(rèn)的 Startup 服務(wù),當(dāng)然你也可以自定義這個(gè)啟動(dòng)服務(wù)咖楣,比如 StatupDevelopment 。
這樣寫 .UseStartup(typeof(StartupDevelopment).GetTypeInfo().Assembly.FullName)
接下來芦昔,就是 Build 我們的剛剛實(shí)例化的 webhostbuilder 诱贿,生成一個(gè) WebHost 宿主主機(jī)。
中間我們就可以對(duì)宿主下的服務(wù)進(jìn)行配置咕缎,
最后就是執(zhí)行 Run() 方法珠十,啟動(dòng)應(yīng)用程序,直到主機(jī)關(guān)閉凭豪。
如果有小伙伴想更多的了解 .net core 的啟動(dòng)配置相關(guān)知識(shí)焙蹭,可以看這里有一個(gè)QQ群管理Dave 大神的視頻:
四、測(cè)試結(jié)果
1嫂伞、用動(dòng)圖來演示效果
經(jīng)過配置孔厉,我這里先建立了一個(gè)空的數(shù)據(jù)庫 DBInitTest ,然后看看效果:
這里要注意下:根據(jù)數(shù)據(jù)庫大小的不同帖努,中間可能經(jīng)歷的時(shí)間不一樣撰豺,我們已經(jīng)成功的生成了數(shù)據(jù)庫,并初始化出來了數(shù)據(jù)拼余。
好啦污桦,今天的這個(gè)小技巧就說到這里了,你也可以根據(jù)自己的情況匙监,根據(jù)自己的ORM來設(shè)計(jì)喲凡橱,特別適用于一個(gè)給別人展示的Demo項(xiàng)目,和自己的小項(xiàng)目亭姥。
2稼钩、如果用EFCore會(huì)更簡(jiǎn)單
上邊咱們說到了,有的小伙伴會(huì)使用EFCore致份,而且上邊咱們也簡(jiǎn)單說了变抽,在EFCore 進(jìn)行實(shí)體映射以后,就可以直接進(jìn)行Code First 和 種子數(shù)據(jù)初始化了:
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();
}
}
最后氮块,圣誕節(jié)快樂
最后來個(gè)今天火的不得了的小圖:
(圖片來源于網(wǎng)絡(luò)绍载,侵刪)
五、Github & Gitee
https://github.com/anjoy8/Blog.Core
https://gitee.com/laozhangIsPhi/Blog.Core
--END