EntityFramework Core 2.x (ef core) 在迁移中自动生成数据库表和列说明
在项目开发中有没有用过拼音首字母做列名或者接手这样的项目?
看见xmspsqb(项目审批申请表)这种表名时是否有一种无法抑制的想肛了取名的老兄的冲动?
更坑爹的是这种数据库没有文档(或者文档老旧不堪早已无用)也没有数据库内部说明,是不是很无奈?
但是,凡事就怕有但是,有些表和列名字确实太专业(奇葩),用英文不是太长就是根本不知道用什么(英文差……),似乎也只能用拼音。好吧,那就用吧,写个说明凑活用用。这个时候问题就来了,如何用sql生成表和列说明?在ef core中又怎样生成表和列说明?
以sqlserver为例。
1、使用ssms管理器编辑说明:不瞎的都知道吧。
2、使用sql生成说明:
添加
exec sys.sp_addextendedproperty
@name=N'MS_Description'
, @value=N'说明'
, @level0type=N'SCHEMA'
, @level0name=N'dbo'
, @level1type=N'TABLE'
, @level1name=N'表名'
, @level2type=N'COLUMN'
, @level2name=N'列名'
红字根据情况修改,需要注意,如果说明已经存在会报错。如果需要添加的是表说明,那么@level2type 和 @level2name填NULL即可。
删除
exec sys.sp_dropextendedproperty
@name=N'MS_Description'
, @level0type=N'SCHEMA'
, @level0name=N'dbo'
, @level1type=N'TABLE'
, @level1name=N'表名'
, @level2type=N'COLUMN'
, @level2name=N'列名'
需要注意,如果说明不存在会报错。其他同上。
很好,只需要这两个内置存储过程就可以用sql管理说明了,修改虽然也有,但是先删再加也一样,就不写了。还有一个遗留问题,上面的存储过程会报错,后面的sql就得不到执行,这需要解决一下,思路很直接,查询下是否存在,存在的话先删再加,不存在就直接加。
查询说明是否存在
select exists (
select t.name as tname,c.name as cname, d.value as Description
from sysobjects t
left join syscolumns c
on c.id=t.id and t.xtype='U' and t.name<>'dtproperties'
left join sys.extended_properties d
on c.id=d.major_id and c.colid=d.minor_id and d.name = 'MS_Description'
where t.name = '表名' and c.name = '列名' and d.value is not null)
红字根据情况修改,如果要查询的是表说明,删除下划线部分即可。
不错,判断问题也解决了,直接使用sql管理基本上也就够用了,那么如果使用ef core托管数据库该怎么办呢?思路也很清晰,使用ef迁移。在迁移中管理sql,MigrationBuilder.Sql(string sql)方法可以在迁移中执行任何自定义sql。把上面的sql传给方法就可以让ef迁移自动生成说明,并且生成的独立迁移脚本文件也包含说明相关sql。
问题看似解决了,但是(又是但是),这个解决方案实在是太难用了。1、这样的字符串没有智能提示和代码着色,怎么写错的都不知道。2、后续管理困难,很可能跟数据库文档一样的下场,没人维护更新,随着版本推移逐渐沦为垃圾。3、不好用!不优雅!
接下来就是个人思考尝试后得到的解决方案:
1、将说明分散到说明对象的脸上,让查阅和修改都能随手完成,降低维护成本,利用C#的特性可以优雅的解决这个问题。
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Property | AttributeTargets.Field, Inherited = true, AllowMultiple = false)]
public class DbDescriptionAttribute : Attribute
{
/// <summary>
/// 初始化新的实例
/// </summary>
/// <param name="description">说明内容</param>
public DbDescriptionAttribute(string description) => Description = description; /// <summary>
/// 说明
/// </summary>
public virtual string Description { get; }
}
2、读取特性并应用到迁移中。不过我并不打算让迁移直接读取特性,首先在迁移过程中实体类型并不会载入,从模型获取实体类型结果是null,需要自己想办法把模型类型传入迁移。其次我希望迁移能时刻与模型匹配,ef迁移会生成多个迁移类代码,追踪整个实体模型的变更历史,而特性一旦修改,就会丢失旧的内容,无法充分利用ef迁移的跟踪能力。基于以上考虑,可以把模型的说明写入模型注解,ef迁移会将模型注解写入迁移快照。最后就是在适当的时机读取特性并写入注解,很显然,这个时机就是OnModelCreating方法。
public static ModelBuilder ConfigDatabaseDescription(this ModelBuilder modelBuilder)
{
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
//添加表说明
if (entityType.FindAnnotation(DbDescriptionAnnotationName) == null && entityType.ClrType?.CustomAttributes.Any(
attr => attr.AttributeType == typeof(DbDescriptionAttribute)) == true)
{
entityType.AddAnnotation(DbDescriptionAnnotationName,
(entityType.ClrType.GetCustomAttribute(typeof(DbDescriptionAttribute)) as DbDescriptionAttribute
)?.Description);
} //添加列说明
foreach (var property in entityType.GetProperties())
{
if (property.FindAnnotation(DbDescriptionAnnotationName) == null && property.PropertyInfo?.CustomAttributes
.Any(attr => attr.AttributeType == typeof(DbDescriptionAttribute)) == true)
{
var propertyInfo = property.PropertyInfo;
var propertyType = propertyInfo?.PropertyType;
//如果该列的实体属性是枚举类型,把枚举的说明追加到列说明
var enumDbDescription = string.Empty;
if (propertyType.IsEnum
|| (propertyType.IsDerivedFrom(typeof(Nullable<>)) && propertyType.GenericTypeArguments[].IsEnum))
{
var @enum = propertyType.IsDerivedFrom(typeof(Nullable<>))
? propertyType.GenericTypeArguments[]
: propertyType; var descList = new List<string>();
foreach (var field in @enum?.GetFields() ?? new FieldInfo[])
{
if (!field.IsSpecialName)
{
var desc = (field.GetCustomAttributes(typeof(DbDescriptionAttribute), false)
.FirstOrDefault() as DbDescriptionAttribute)?.Description;
descList.Add(
$@"{field.GetRawConstantValue()} : {(desc.IsNullOrWhiteSpace() ? field.Name : desc)}");
}
} var isFlags = @enum?.GetCustomAttribute(typeof(FlagsAttribute)) != null;
var enumTypeDbDescription =
(@enum?.GetCustomAttributes(typeof(DbDescriptionAttribute), false).FirstOrDefault() as
DbDescriptionAttribute)?.Description;
enumTypeDbDescription += enumDbDescription + (isFlags ? " [是标志位枚举]" : string.Empty);
enumDbDescription =
$@"( {(enumTypeDbDescription.IsNullOrWhiteSpace() ? "" : $@"{enumTypeDbDescription}; ")}{string.Join("; ", descList)} )";
} property.AddAnnotation(DbDescriptionAnnotationName,
$@"{(propertyInfo.GetCustomAttribute(typeof(DbDescriptionAttribute)) as DbDescriptionAttribute)
?.Description}{(enumDbDescription.IsNullOrWhiteSpace() ? "" : $@" {enumDbDescription}")}");
}
}
} return modelBuilder;
}
在OnModelCreating方法中调用ConfigDatabaseDescription方法即可将说明写入模型注解。其中的关键是AddAnnotation这个ef core提供的API,不清楚1.x和ef 6.x有没有这个功能。其中DbDescriptionAnnotationName就是个名称,随便取,只要不和已有注解重名即可。可以看到,这个方法同时支持扫描并生成枚举类型的说明,包括可空枚举。
3、在迁移中读取模型注解并生成说明。有了之前的准备工作,到这里就好办了。
public static MigrationBuilder ApplyDatabaseDescription(this MigrationBuilder migrationBuilder, Migration migration)
{
var defaultSchema = "dbo";
var descriptionAnnotationName = ModelBuilderExtensions.DbDescriptionAnnotationName; foreach (var entityType in migration.TargetModel.GetEntityTypes())
{
//添加表说明
var tableName = entityType.Relational().TableName;
var schema = entityType.Relational().Schema;
var tableDescriptionAnnotation = entityType.FindAnnotation(descriptionAnnotationName); if (tableDescriptionAnnotation != null)
{
migrationBuilder.AddOrUpdateTableDescription(
tableName,
tableDescriptionAnnotation.Value.ToString(),
schema.IsNullOrEmpty() ? defaultSchema : schema);
} //添加列说明
foreach (var property in entityType.GetProperties())
{
var columnDescriptionAnnotation = property.FindAnnotation(descriptionAnnotationName); if (columnDescriptionAnnotation != null)
{
migrationBuilder.AddOrUpdateColumnDescription(
tableName,
property.Relational().ColumnName,
columnDescriptionAnnotation.Value.ToString(),
schema.IsNullOrEmpty() ? defaultSchema : schema);
}
}
} return migrationBuilder;
}
在迁移的Up和Down方法末尾调用ApplyDatabaseDescription方法即可取出模型注解中的说明并生成和执行相应的sql。
至此,一个好用的数据库说明管理就基本完成了。因为这个方法使用了大量ef core提供的API,所以基本上是完整支持ef core的各种实体映射,实测包括与实体类名、属性名不一致的表名、列名,(嵌套的)Owned类型属性(类似ef 6.x的复杂类型属性 Complex Type)、表拆分等。可以说基本上没有什么后顾之忧。这里的sql是以sqlserver为例,如果使用的是mysql或其他关系数据库,需要自行修改sql以及AddOrUpdateColumnDescription和AddOrUpdateTableDescription的逻辑。
其中Owned类型属性在生成迁移时可能会生成错误代码,导致编译错误CS1061 "ReferenceOwnershipBuilder"未包含"HasAnnotation"的定义且……,只需要把HasAnnotation替换成HasEntityTypeAnnotation即可。估计是微软的老兄粗心没注意这个问题。
ps:为什么不直接使用Description或者DisplayName之类的内置特性而要使用自定义特性,因为Description在语义上是指广泛的说明,并不能明确表明这是数据库说明,同时避免与现存代码纠缠不清影响使用,为加强语义性,使用新增的自定义特性。
ps2:为什么不使用xml注释文档,因为这会让这个功能产生对/doc编译选项和弱类型文本的依赖,甚至需要对文档配置嵌入式资源,也会增加编码难度,同时会影响现存代码的xml注释,为避免影响现存代码,对非代码和编译器不可检查行为的依赖,保证代码健壮性,不使用xml文档注释。
效果预览:
更新(2019-12-14):
已更新 EF core 3.x 版本代码,具体代码请看 Github 项目库中的 NetCore_3.0 分支(实际上已经更新到 .Net Core 3.1,懒得改名了。好麻烦 (¬_¬") )。
转载请完整保留以下内容,未经授权删除以下内容进行转载盗用的,保留追究法律责任的权利!
本文地址:https://www.cnblogs.com/coredx/p/10026783.html
完整源代码:Github
里面有各种小东西,这只是其中之一,不嫌弃的话可以Star一下。
EntityFramework Core 2.x (ef core) 在迁移中自动生成数据库表和列说明的更多相关文章
- EF CodeFirst关于Mysql如何自动生成数据库表
相对于sqlserver数据库,mysql的配置过程相对麻烦一些,我们从0讲起. 1.新建一个控制台应用程序 右键点击引用--管理NuGet程序包,搜索Mysql.Data.Entity并安装,安装完 ...
- flask-sqlalchemy 迁移数据(生成数据库表)与 查询数据
1, 生成表 db.Model主要用于数据库的增删改查操作, 构建表交给db.Table完成 安装 pip install flask-migrate from datetime import dat ...
- Asp.net core下利用EF core实现从数据实现多租户(1)
前言 随着互联网的的高速发展,大多数的公司由于一开始使用的传统的硬件/软件架构,导致在业务不断发展的同时,系统也逐渐地逼近传统结构的极限. 于是,系统也急需进行结构上的升级换代. 在服务端,系统的I/ ...
- Asp.net core下利用EF core实现从数据实现多租户(3): 按Schema分离 附加:EF Migration 操作
前言 前段时间写了EF core实现多租户的文章,实现了根据数据库,数据表进行多租户数据隔离. 今天开始写按照Schema分离的文章. 其实还有一种,是通过在数据表内添加一个字段做多租户的,但是这种模 ...
- EF Core 快速上手——EF Core的三种主要关系类型
系列文章 EF Core 快速上手--EF Core 入门 本节导航 三种数据库关系类型建模 Migration方式创建和习修改数据库 定义和创建应用DbContext 将复杂查询拆分为子查询 本 ...
- EF Core 快速上手——EF Core 入门
EF Core 快速上手--EF Core 介绍 本章导航 从本书你能学到什么 对EF6.x 程序员的一些话 EF Core 概述 1.3.1 ORM框架的缺点 第一个EF Core应用 本文是对 ...
- EF Core中如何设置数据库表自己与自己的多对多关系
本文的代码基于.NET Core 3.0和EF Core 3.0 有时候在数据库设计中,一个表自己会和自己是多对多关系. 在SQL Server数据库中,现在我们有Person表,代表一个人,建表语句 ...
- EntityFramework系列:SQLite.CodeFirst自动生成数据库
http://www.cnblogs.com/easygame/p/4447457.html 在Code First模式下使用SQLite一直存在不能自动生成数据库的问题,使用SQL Server C ...
- EF Core 迁移过程遇到EF Core tools version版本不相符的解决方案
如果你使用命令: PM> add-migration Inital 提示如下信息时: The EF Core tools version '2.1.1-rtm-30846' is older t ...
随机推荐
- python常用内置模块
#持续更新 #在使用内置模块的时候需要导入,例如import abc,则导入abc模块,当然模块也可以自己写,相当于一个类,后面放到类里说,这个因为环境闲置,有些无法执行,只能理解了 #os系统操作 ...
- Java 中File类的createNewFile()与createTempFile(), delete和deleteOnExit区别
1. Java 中File类的createNewFile()与createTempFile()的区别 最近,在看代码时看到了一个方法, File.createTempFile() ,由此联想到File ...
- Java 的布局管理器GridBagLayout的使用方法【图文说明】
https://www.cnblogs.com/taoweiji/archive/2012/12/14/2818787.html GridBagLayout是java里面最重要的布局管理器之一,可以做 ...
- Linux运维之——每日小技巧,谈进程与线程的区别
线程是进程中执行运算的最小单位,是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源. ...
- 第二次项目冲刺(Beta版本)2017/12/10
一.任务分布 二.燃尽图 三.站立式会议 1.照片(再给我两分钟) 2.任务安排 四.总结 冲刺的最后一天了,时间安排的不够好,还有部分功能没有完成,大家都尽力了,我们后续会抓紧时间完成项目.
- 经典Paxos算法笔记
介绍 Paxos算法是一个高容错性的分布式一致性算法.去年学习过Paxos算法,一直没将整理到博客.现在将经典Paxos算法相关内容整理到博客上. 经典Paxos算法本身也并不是太难理解,Lampor ...
- 详解Web请求中的DNS域名解析
当我们打开浏览器,输入一个URL去请求我们需要的资源,但是URL是需要解析成对应的IP地址才能与远程主机建立连接,如何将URL解析成IP就是DNS的工作范畴,即使作为开发人员,这个过程我们也感觉不到, ...
- console.time方法与console.timeEnd方法
在Node.js中,当需要统计一段代码的执行时间时,可以使用console.time方法与console.timeEnd方法,其中console.time方法用于标记开始时间,console.time ...
- 如何为一个类型为Color的属性设置默认值
最近在研究GDI+的时候,用winform来写自定义控件遇到需要为控件的属性设置默认值,但这个属性的类型是System.Drawing.Color.本文只是总结一下各种设置的方法. Example [ ...
- jquery另外一种类似tab切换效果
简要:最近做项目一些效果不能用淘宝kissy框架 所以代码得自己写啊 网上当然有很多组件 但是用他们的代码很多(有的是我不需要的代码) 且还要看API 还不如自己动手写个简单一个,是这么一种简单的效果 ...