EFCore 通過(guò)實(shí)體Model生成創(chuàng)建SQL Server數(shù)據(jù)庫(kù)表腳本
在我們的項(xiàng)目中經(jīng)常采用Model First這種方式先來(lái)設(shè)計(jì)數(shù)據(jù)庫(kù)Model,然后通過(guò)Migration來(lái)生成數(shù)據(jù)庫(kù)表結(jié)構(gòu),有些時(shí)候我們需要?jiǎng)討B(tài)通過(guò)實(shí)體Model來(lái)創(chuàng)建數(shù)據(jù)庫(kù)的表結(jié)構(gòu),特別是在創(chuàng)建像臨時(shí)表這一類型的時(shí)候,我們直接通過(guò)代碼來(lái)進(jìn)行創(chuàng)建就可以了不用通過(guò)創(chuàng)建實(shí)體然后遷移這種方式來(lái)進(jìn)行,其實(shí)原理也很簡(jiǎn)單就是通過(guò)遍歷當(dāng)前Model然后獲取每一個(gè)屬性并以此來(lái)生成部分創(chuàng)建腳本,然后將這些創(chuàng)建的腳本拼接成一個(gè)完整的腳本到數(shù)據(jù)庫(kù)中去執(zhí)行就可以了,只不過(guò)這里有一些需要注意的地方,下面我們來(lái)通過(guò)代碼來(lái)一步步分析怎么進(jìn)行這些代碼規(guī)范編寫以及需要注意些什么問(wèn)題。
一 代碼分析
/// <summary> /// Model 生成數(shù)據(jù)庫(kù)表腳本 /// </summary> public class TableGenerator : ITableGenerator { private static Dictionary<Type, string> DataMapper { get { var dataMapper = new Dictionary<Type, string> { {typeof(int), "NUMBER(10) NOT NULL"}, {typeof(int?), "NUMBER(10)"}, {typeof(string), "VARCHAR2({0} CHAR)"}, {typeof(bool), "NUMBER(1)"}, {typeof(DateTime), "DATE"}, {typeof(DateTime?), "DATE"}, {typeof(float), "FLOAT"}, {typeof(float?), "FLOAT"}, {typeof(decimal), "DECIMAL(16,4)"}, {typeof(decimal?), "DECIMAL(16,4)"}, {typeof(Guid), "CHAR(36)"}, {typeof(Guid?), "CHAR(36)"} }; return dataMapper; } } private readonly List<KeyValuePair<string, PropertyInfo>> _fields = new List<KeyValuePair<string, PropertyInfo>>(); /// <summary> /// /// </summary> private string _tableName; /// <summary> /// /// </summary> /// <returns></returns> private string GetTableName(MemberInfo entityType) { if (_tableName != null) return _tableName; var prefix = entityType.GetCustomAttribute<TempTableAttribute>() != null ? "#" : string.Empty; return _tableName = $"{prefix}{entityType.GetCustomAttribute<TableAttribute>()?.Name ?? entityType.Name}"; } /// <summary> /// 生成創(chuàng)建表的腳本 /// </summary> /// <returns></returns> public string GenerateTableScript(Type entityType) { if (entityType == null) throw new ArgumentNullException(nameof(entityType)); GenerateFields(entityType); const int DefaultColumnLength = 500; var script = new StringBuilder(); script.AppendLine($"CREATE TABLE {GetTableName(entityType)} ("); foreach (var (propName, propertyInfo) in _fields) { if (!DataMapper.ContainsKey(propertyInfo.PropertyType)) throw new NotSupportedException($"尚不支持 {propertyInfo.PropertyType}, 請(qǐng)聯(lián)系開發(fā)人員."); if (propertyInfo.PropertyType == typeof(string)) { var maxLengthAttribute = propertyInfo.GetCustomAttribute<MaxLengthAttribute>(); script.Append($"\t {propName} {string.Format(DataMapper[propertyInfo.PropertyType], maxLengthAttribute?.Length ?? DefaultColumnLength)}"); if (propertyInfo.GetCustomAttribute<RequiredAttribute>() != null) script.Append(" NOT NULL"); script.AppendLine(","); } else { script.AppendLine($"\t {propName} {DataMapper[propertyInfo.PropertyType]},"); } } script.Remove(script.Length - 1, 1); script.AppendLine(")"); return script.ToString(); } private void GenerateFields(Type entityType) { foreach (var p in entityType.GetProperties()) { if (p.GetCustomAttribute<NotMappedAttribute>() != null) continue; var columnName = p.GetCustomAttribute<ColumnAttribute>()?.Name ?? p.Name; var field = new KeyValuePair<string, PropertyInfo>(columnName, p); _fields.Add(field); } } }
這里的TableGenerator繼承自接口ITableGenerator,在這個(gè)接口內(nèi)部只定義了一個(gè) string GenerateTableScript(Type entityType) 方法。
/// <summary> /// Model 生成數(shù)據(jù)庫(kù)表腳本 /// </summary> public interface ITableGenerator { /// <summary> /// 生成創(chuàng)建表的腳本 /// </summary> /// <returns></returns> string GenerateTableScript(Type entityType); }
這里我們來(lái)一步步分析這些部分的含義,這個(gè)里面DataMapper主要是用來(lái)定義一些C#基礎(chǔ)數(shù)據(jù)類型和數(shù)據(jù)庫(kù)生成腳本之間的映射關(guān)系。
1 GetTableName
接下來(lái)我們看看GetTableName這個(gè)函數(shù),這里首先來(lái)當(dāng)前Model是否定義了TempTableAttribute,這個(gè)看名字就清楚了就是用來(lái)定義當(dāng)前Model是否是用來(lái)生成一張臨時(shí)表的。
/// <summary> /// 是否臨時(shí)表, 僅限 Dapper 生成 數(shù)據(jù)庫(kù)表結(jié)構(gòu)時(shí)使用 /// </summary> [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] public class TempTableAttribute : Attribute { }
具體我們來(lái)看看怎樣在實(shí)體Model中定義TempTableAttribute這個(gè)自定義屬性。
[TempTable] class StringTable { public string DefaultString { get; set; } [MaxLength(30)] public string LengthString { get; set; } [Required] public string NotNullString { get; set; } }
就像這樣定義的話,我們就知道當(dāng)前Model會(huì)生成一張SQL Server的臨時(shí)表。
當(dāng)然如果是生成臨時(shí)表,則會(huì)在生成的表名稱前面加一個(gè)‘#'標(biāo)志,在這段代碼中我們還會(huì)去判斷當(dāng)前實(shí)體是否定義了TableAttribute,如果定義過(guò)就去取這個(gè)TableAttribute的名稱,否則就去當(dāng)前Model的名稱,這里也舉一個(gè)實(shí)例。
[Table("Test")] class IntTable { public int IntProperty { get; set; } public int? NullableIntProperty { get; set; } }
這樣我們通過(guò)代碼創(chuàng)建的數(shù)據(jù)庫(kù)名稱就是Test啦。
2 GenerateFields
這個(gè)主要是用來(lái)一個(gè)個(gè)讀取Model中的屬性,并將每一個(gè)實(shí)體屬性整理成一個(gè)KeyValuePair<string, PropertyInfo>的對(duì)象從而方便最后一步來(lái)生成整個(gè)表完整的腳本,這里也有些內(nèi)容需要注意,如果當(dāng)前屬性定義了NotMappedAttribute標(biāo)簽,那么我們可以直接跳過(guò)當(dāng)前屬性,另外還需要注意的地方就是當(dāng)前屬性的名稱首先看當(dāng)前屬性是否定義了ColumnAttribute的如果定義了,那么數(shù)據(jù)庫(kù)中字段名稱就取自ColumnAttribute定義的名稱,否則才是取自當(dāng)前屬性的名稱,通過(guò)這樣一步操作我們就能夠?qū)⑺械膶傩宰x取到一個(gè)自定義的數(shù)據(jù)結(jié)構(gòu)List<KeyValuePair<string, PropertyInfo>>里面去了。
3 GenerateTableScript
有了前面的兩步準(zhǔn)備工作,后面就是進(jìn)入到生成整個(gè)創(chuàng)建表腳本的部分了,其實(shí)這里也比較簡(jiǎn)單,就是通過(guò)循環(huán)來(lái)一個(gè)個(gè)生成每一個(gè)屬性對(duì)應(yīng)的腳本,然后通過(guò)StringBuilder來(lái)拼接到一起形成一個(gè)完整的整體。這里面有一點(diǎn)需要我們注意的地方就是當(dāng)前字段是否可為空還取決于當(dāng)前屬性是否定義過(guò)RequiredAttribute標(biāo)簽,如果定義過(guò)那么就需要在創(chuàng)建的腳本后面添加Not Null,最后一個(gè)重點(diǎn)就是對(duì)于string類型的屬性我們需要讀取其定義的MaxLength屬性從而確定數(shù)據(jù)庫(kù)中的字段長(zhǎng)度,如果沒(méi)有定義則取默認(rèn)長(zhǎng)度500。
當(dāng)然一個(gè)完整的代碼怎么能少得了單元測(cè)試呢?下面我們來(lái)看看單元測(cè)試。
二 單元測(cè)試
public class SqlServerTableGenerator_Tests { [Table("Test")] class IntTable { public int IntProperty { get; set; } public int? NullableIntProperty { get; set; } } [Fact] public void GenerateTableScript_Int_Number10() { // Act var sql = new TableGenerator().GenerateTableScript(typeof(IntTable)); // Assert sql.ShouldContain("IntProperty NUMBER(10) NOT NULL"); sql.ShouldContain("NullableIntProperty NUMBER(10)"); } [Fact] public void GenerateTableScript_TestTableName_Test() { // Act var sql = new TableGenerator().GenerateTableScript(typeof(IntTable)); // Assert sql.ShouldContain("CREATE TABLE Test"); } [TempTable] class StringTable { public string DefaultString { get; set; } [MaxLength(30)] public string LengthString { get; set; } [Required] public string NotNullString { get; set; } } [Fact] public void GenerateTableScript_TempTable_TableNameWithSharp() { // Act var sql = new TableGenerator().GenerateTableScript(typeof(StringTable)); // Assert sql.ShouldContain("Create Table #StringTable"); } [Fact] public void GenerateTableScript_String_Varchar() { // Act var sql = new TableGenerator().GenerateTableScript(typeof(StringTable)); // Assert sql.ShouldContain("DefaultString VARCHAR2(500 CHAR)"); sql.ShouldContain("LengthString VARCHAR2(30 CHAR)"); sql.ShouldContain("NotNullString VARCHAR2(500 CHAR) NOT NULL"); } class ColumnTable { [Column("Test")] public int IntProperty { get; set; } [NotMapped] public int Ingored {get; set; } } [Fact] public void GenerateTableScript_ColumnName_NewName() { // Act var sql = new TableGenerator().GenerateTableScript(typeof(ColumnTable)); // Assert sql.ShouldContain("Test NUMBER(10) NOT NULL"); } [Fact] public void GenerateTableScript_NotMapped_Ignore() { // Act var sql = new TableGenerator().GenerateTableScript(typeof(ColumnTable)); // Assert sql.ShouldNotContain("Ingored NUMBER(10) NOT NULL"); } class NotSupportedTable { public dynamic Ingored {get; set; } } [Fact] public void GenerateTableScript_NotSupported_ThrowException() { // Act Assert.Throws<NotSupportedException>(() => { new TableGenerator().GenerateTableScript(typeof(NotSupportedTable)); }); } }
最后我們來(lái)看看最終生成的創(chuàng)建表的腳本。
1 定義過(guò)TableAttribute的腳本。
CREATE TABLE Test ( IntProperty NUMBER(10) NOT NULL, NullableIntProperty NUMBER(10), )
2 生成的臨時(shí)表的腳本。
CREATE TABLE #StringTable ( DefaultString VARCHAR2(500 CHAR), LengthString VARCHAR2(30 CHAR), NotNullString VARCHAR2(500 CHAR) NOT NULL, )
通過(guò)這種方式我們就能夠在代碼中去動(dòng)態(tài)生成數(shù)據(jù)庫(kù)表結(jié)構(gòu)了。
以上就是EFCore 通過(guò)實(shí)體Model生成創(chuàng)建SQL Server數(shù)據(jù)庫(kù)表腳本的詳細(xì)內(nèi)容,更多關(guān)于EFCore 創(chuàng)建SQL Server數(shù)據(jù)庫(kù)表腳本的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Asp.NET Core 如何調(diào)用WebService的方法
這篇文章主要介紹了Asp.NET Core 如何調(diào)用WebService的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-08-08Visual Studio實(shí)現(xiàn)xml文件使用app.config、web.config等的智能提示
這篇文章主要為大家詳細(xì)介紹了Visual Studio中xml文件使用app.config、web.config等的智能提示方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-09-09.Net?ORM?訪問(wèn)?Firebird?數(shù)據(jù)庫(kù)的方法
這篇文章簡(jiǎn)單介紹了在?.net6.0?環(huán)境中使用?FreeSql?對(duì)?Firebird?數(shù)據(jù)庫(kù)的訪問(wèn),目前?FreeSql?還支持.net?framework?4.0?和?xamarin?平臺(tái)上使用,對(duì).Net?ORM?訪問(wèn)?Firebird?數(shù)據(jù)庫(kù)相關(guān)知識(shí)感興趣的朋友一起看看吧2022-07-07asp.net+js實(shí)時(shí)奧運(yùn)金牌榜代碼
運(yùn)期間,公司交給我一個(gè)任務(wù),在公司主頁(yè)上放上奧運(yùn)金牌榜的排名,之前的實(shí)現(xiàn)方式是采用ajax2008-09-09使用.NET Core實(shí)現(xiàn)餓了嗎拆紅包功能
這篇文章主要介紹了使用.NET Core實(shí)現(xiàn)餓了嗎拆紅包功能,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-07-07asp.net 程序性能優(yōu)化的七個(gè)方面 (c#(或vb.net)程序改進(jìn))
在我們開發(fā)asp.net過(guò)程中,需要注意的一些細(xì)節(jié),以達(dá)到我們優(yōu)化程序執(zhí)行效率。2009-03-03ASP.NET.4.5.1+MVC5.0設(shè)置系統(tǒng)角色與權(quán)限(二)
這篇文章主要介紹了使用ASP.NET.4.5.1+MVC5.0構(gòu)建項(xiàng)目中設(shè)置系統(tǒng)角色的全部過(guò)程,十分的詳細(xì),附上全部源碼,推薦給想學(xué)習(xí).net+mvc的小伙伴們2015-01-01動(dòng)態(tài)指定任意類型的ObjectDataSource對(duì)象的查詢參數(shù)
我在使用ObjectDataSource控件在ASP.NET中實(shí)現(xiàn)Ajax真分頁(yè) 一文中詳細(xì)介紹過(guò)如何使用ObjectDataSource和ListView實(shí)現(xiàn)數(shù)據(jù)綁定和分頁(yè)功能。事實(shí)上,采用ObjectDataSource和ListView相結(jié)合,可以減少我們很多的開發(fā)任務(wù)。2009-11-11ASP.NET MVC5驗(yàn)證系列之客戶端驗(yàn)證
這篇文章主要為大家詳細(xì)介紹了ASP.NET MVC5驗(yàn)證系列之客戶端驗(yàn)證,感興趣的小伙伴們可以參考一下2016-07-07