EFCore 通過實(shí)體Model生成創(chuàng)建SQL Server數(shù)據(jù)庫表腳本
在我們的項(xiàng)目中經(jīng)常采用Model First這種方式先來設(shè)計(jì)數(shù)據(jù)庫Model,然后通過Migration來生成數(shù)據(jù)庫表結(jié)構(gòu),有些時(shí)候我們需要?jiǎng)討B(tài)通過實(shí)體Model來創(chuàng)建數(shù)據(jù)庫的表結(jié)構(gòu),特別是在創(chuàng)建像臨時(shí)表這一類型的時(shí)候,我們直接通過代碼來進(jìn)行創(chuàng)建就可以了不用通過創(chuàng)建實(shí)體然后遷移這種方式來進(jìn)行,其實(shí)原理也很簡單就是通過遍歷當(dāng)前Model然后獲取每一個(gè)屬性并以此來生成部分創(chuàng)建腳本,然后將這些創(chuàng)建的腳本拼接成一個(gè)完整的腳本到數(shù)據(jù)庫中去執(zhí)行就可以了,只不過這里有一些需要注意的地方,下面我們來通過代碼來一步步分析怎么進(jìn)行這些代碼規(guī)范編寫以及需要注意些什么問題。
一 代碼分析
/// <summary>
/// Model 生成數(shù)據(jù)庫表腳本
/// </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ù)庫表腳本
/// </summary>
public interface ITableGenerator {
/// <summary>
/// 生成創(chuàng)建表的腳本
/// </summary>
/// <returns></returns>
string GenerateTableScript(Type entityType);
}
這里我們來一步步分析這些部分的含義,這個(gè)里面DataMapper主要是用來定義一些C#基礎(chǔ)數(shù)據(jù)類型和數(shù)據(jù)庫生成腳本之間的映射關(guān)系。
1 GetTableName
接下來我們看看GetTableName這個(gè)函數(shù),這里首先來當(dāng)前Model是否定義了TempTableAttribute,這個(gè)看名字就清楚了就是用來定義當(dāng)前Model是否是用來生成一張臨時(shí)表的。
/// <summary>
/// 是否臨時(shí)表, 僅限 Dapper 生成 數(shù)據(jù)庫表結(jié)構(gòu)時(shí)使用
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class TempTableAttribute : Attribute {
}
具體我們來看看怎樣在實(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,如果定義過就去取這個(gè)TableAttribute的名稱,否則就去當(dāng)前Model的名稱,這里也舉一個(gè)實(shí)例。
[Table("Test")]
class IntTable {
public int IntProperty { get; set; }
public int? NullableIntProperty { get; set; }
}
這樣我們通過代碼創(chuàng)建的數(shù)據(jù)庫名稱就是Test啦。
2 GenerateFields
這個(gè)主要是用來一個(gè)個(gè)讀取Model中的屬性,并將每一個(gè)實(shí)體屬性整理成一個(gè)KeyValuePair<string, PropertyInfo>的對(duì)象從而方便最后一步來生成整個(gè)表完整的腳本,這里也有些內(nèi)容需要注意,如果當(dāng)前屬性定義了NotMappedAttribute標(biāo)簽,那么我們可以直接跳過當(dāng)前屬性,另外還需要注意的地方就是當(dāng)前屬性的名稱首先看當(dāng)前屬性是否定義了ColumnAttribute的如果定義了,那么數(shù)據(jù)庫中字段名稱就取自ColumnAttribute定義的名稱,否則才是取自當(dāng)前屬性的名稱,通過這樣一步操作我們就能夠?qū)⑺械膶傩宰x取到一個(gè)自定義的數(shù)據(jù)結(jié)構(gòu)List<KeyValuePair<string, PropertyInfo>>里面去了。
3 GenerateTableScript
有了前面的兩步準(zhǔn)備工作,后面就是進(jìn)入到生成整個(gè)創(chuàng)建表腳本的部分了,其實(shí)這里也比較簡單,就是通過循環(huán)來一個(gè)個(gè)生成每一個(gè)屬性對(duì)應(yīng)的腳本,然后通過StringBuilder來拼接到一起形成一個(gè)完整的整體。這里面有一點(diǎn)需要我們注意的地方就是當(dāng)前字段是否可為空還取決于當(dāng)前屬性是否定義過RequiredAttribute標(biāo)簽,如果定義過那么就需要在創(chuàng)建的腳本后面添加Not Null,最后一個(gè)重點(diǎn)就是對(duì)于string類型的屬性我們需要讀取其定義的MaxLength屬性從而確定數(shù)據(jù)庫中的字段長度,如果沒有定義則取默認(rèn)長度500。
當(dāng)然一個(gè)完整的代碼怎么能少得了單元測試呢?下面我們來看看單元測試。
二 單元測試
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));
});
}
}
最后我們來看看最終生成的創(chuàng)建表的腳本。
1 定義過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, )
通過這種方式我們就能夠在代碼中去動(dòng)態(tài)生成數(shù)據(jù)庫表結(jié)構(gòu)了。
以上就是EFCore 通過實(shí)體Model生成創(chuàng)建SQL Server數(shù)據(jù)庫表腳本的詳細(xì)內(nèi)容,更多關(guān)于EFCore 創(chuàng)建SQL Server數(shù)據(jù)庫表腳本的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解.Net Core + Angular2 環(huán)境搭建
這篇文章主要介紹了詳解.Net Core + Angular2 環(huán)境搭建,具有一定的參考價(jià)值,有興趣的可以了解一下。2016-12-12
asp.net中利用ashx實(shí)現(xiàn)圖片防盜鏈的原理分析
盜鏈的危害我就不說了,網(wǎng)上有很多。下面是asp.net下利用ashx的防盜鏈原理分析2008-09-09
Asp.NET 隨機(jī)碼生成基類(隨機(jī)字母,隨機(jī)數(shù)字,隨機(jī)字母+數(shù)字)
對(duì)于需要用asp.net 字母,隨機(jī)數(shù)字,隨機(jī)字母+數(shù)字生成隨機(jī)碼的朋友用的到2008-11-11
.NET core 3.0如何使用Jwt保護(hù)api詳解
這篇文章主要給大家介紹了關(guān)于.NET core 3.0如何使用Jwt保護(hù)api的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用.NET core 3.0具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11
ASP.NET Core中使用MialKit實(shí)現(xiàn)郵件發(fā)送功能
這篇文章主要介紹了ASP.NET Core中使用MialKit實(shí)現(xiàn)郵件發(fā)送功能,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-10-10
asp.net生成字母和數(shù)字混合圖形驗(yàn)證碼
這篇文章主要為大家詳細(xì)介紹了asp.net生成字母和數(shù)字混合圖形驗(yàn)證碼的實(shí)現(xiàn)方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-02-02
asp.net模板引擎Razor調(diào)用外部方法用法實(shí)例
這篇文章主要介紹了asp.net模板引擎Razor調(diào)用外部方法用法,實(shí)例分析了Razor調(diào)用外部方法的相關(guān)使用技巧,需要的朋友可以參考下2015-06-06

