在项目开发中有没有用过拼音首字母做列名或者接手这样的项目?

看见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) 在迁移中自动生成数据库表和列说明的更多相关文章

  1. EF CodeFirst关于Mysql如何自动生成数据库表

    相对于sqlserver数据库,mysql的配置过程相对麻烦一些,我们从0讲起. 1.新建一个控制台应用程序 右键点击引用--管理NuGet程序包,搜索Mysql.Data.Entity并安装,安装完 ...

  2. flask-sqlalchemy 迁移数据(生成数据库表)与 查询数据

    1, 生成表 db.Model主要用于数据库的增删改查操作, 构建表交给db.Table完成 安装 pip install flask-migrate from datetime import dat ...

  3. Asp.net core下利用EF core实现从数据实现多租户(1)

    前言 随着互联网的的高速发展,大多数的公司由于一开始使用的传统的硬件/软件架构,导致在业务不断发展的同时,系统也逐渐地逼近传统结构的极限. 于是,系统也急需进行结构上的升级换代. 在服务端,系统的I/ ...

  4. Asp.net core下利用EF core实现从数据实现多租户(3): 按Schema分离 附加:EF Migration 操作

    前言 前段时间写了EF core实现多租户的文章,实现了根据数据库,数据表进行多租户数据隔离. 今天开始写按照Schema分离的文章. 其实还有一种,是通过在数据表内添加一个字段做多租户的,但是这种模 ...

  5. EF Core 快速上手——EF Core的三种主要关系类型

    系列文章 EF Core 快速上手--EF Core 入门 本节导航 三种数据库关系类型建模 Migration方式创建和习修改数据库 定义和创建应用DbContext 将复杂查询拆分为子查询   本 ...

  6. EF Core 快速上手——EF Core 入门

    EF Core 快速上手--EF Core 介绍 本章导航 从本书你能学到什么 对EF6.x 程序员的一些话 EF Core 概述 1.3.1 ORM框架的缺点 第一个EF Core应用   本文是对 ...

  7. EF Core中如何设置数据库表自己与自己的多对多关系

    本文的代码基于.NET Core 3.0和EF Core 3.0 有时候在数据库设计中,一个表自己会和自己是多对多关系. 在SQL Server数据库中,现在我们有Person表,代表一个人,建表语句 ...

  8. EntityFramework系列:SQLite.CodeFirst自动生成数据库

    http://www.cnblogs.com/easygame/p/4447457.html 在Code First模式下使用SQLite一直存在不能自动生成数据库的问题,使用SQL Server C ...

  9. EF Core 迁移过程遇到EF Core tools version版本不相符的解决方案

    如果你使用命令: PM> add-migration Inital 提示如下信息时: The EF Core tools version '2.1.1-rtm-30846' is older t ...

随机推荐

  1. Redis缓存雪崩、缓存穿透、热点Key解决方案和分析

    缓存穿透 缓存系统,按照KEY去查询VALUE,当KEY对应的VALUE一定不存在的时候并对KEY并发请求量很大的时候,就会对后端造成很大的压力. (查询一个必然不存在的数据.比如文章表,查询一个不存 ...

  2. Azure DevKit(AZ3166)源码找不到头文件问题

    Get “Error Presented: #include errors detected” when opening a project The error message is Error Pr ...

  3. Alpha冲刺报告(10/12)(麻瓜制造者)

    今日已完成 邓弘立: 实现了登录的所有功能 符天愉: 有点事没有参与冲刺- 江郑: 刘双玉: 写商品下架接口说明的时候,就发现一个bug,因为私人原因中间离开了很长时间,最后修改完成就很晚了. 困难: ...

  4. RLP(转发注明出处)

    目录 RLP序列化 什么是序列化? 为什么要序列化? RLP序列化处理的两项数据 RLP序列化采取的5项规则: 利用python写的RLP 实际中的使用是个怎么样子? RLP分析 参考目录 @ RLP ...

  5. Hadoop YARN简介

    背景 本文整理一些Hadoop YARN的相关内容. 简介 YARN(Yet Another Resource Negotiator)是Hadoop通用资源管理平台,为各类计算框架(离线MR.在线St ...

  6. Oracle_spatial的常见错误与注意事项

    常见的错误 1.ORA-13226:没有空间索引接口将不被支持 当使用一个空间操作符时,如果没有使用空间索引导致该操作符不能被完成将会返回该错误.这可能会发生在当你使用的列上没有空间索引.或者优化器没 ...

  7. Opatching PSU in Oracle Database 11g Release 2 RAC on RHEL6

    Opatching PSU in Oracle Database 11g Release 2(11.2.0.4) RAC on RHEL6 1) 升级opatch工具 1.1) For GI home ...

  8. LFS 8.3 中文翻译版本发布!

    导读 很多同学都已经学习了 Linux ,可能已经在自己的机器上安装过 Linux,甚至都能搭建个简单的个人博客,也有可能编译部署过邮件服务器之类的软件,可是感觉仍然对 Linux 有点摸不着,颇有隔 ...

  9. Android 配置从GitHub上下载下来的不太规则的源代码库,并保证程序正常运行

    用过github的朋友一定会发现,我们在github上下载下来的源代码(例子和库),放到eclipse中并不是总能正常运行的,它有可能会出现这样或者那样的错误,例如:找不到jar包,配置文件错误,R文 ...

  10. kubernetes 限制网络带宽 annotation -- 注解 -- 类似 label

    1.可以通过给Pod增加 kubernetes.io/ingressbandwidth和 kubernetes.io/egress-bandwidth 这两个annotation来限制Pod的网络带宽 ...