EF Core3.1 CodeFirst动态自动添加表和字段的描述信息
我又来啦..
本篇主要记录如何针对CodeFirst做自动添加描述的扩展
为什么要用这个呢.. 因为EF Core3.1 CodeFirst 对于自动添加描述这块 只有少部分的数据库支持..
然而我们的客户大佬们 对这个又有要求..所以..没办法 只能自己扩展~
当然也可以根据这个原理来做一些有意思的扩展~
本文就以不支持的达梦数据库来举个栗子
.(PS:真心希望达梦数据库能开放EF Core相关的源码,这样我们也好提交点贡献,国产数据库还是不能太过敝帚自珍阿..)
1.通过扩展生成器,来实现动态自动添加描述信息
我们知道在SQL Server中,可以通过Fluent API来添加针对表或者字段的描述,如下:
builder.Property(prop.Name) .HasComment("XXX字段描述");
然而在达梦的上下文中,我们如果这样写..是没任何效果的..不用想,肯定是达梦的开发商没写(很多扩展类都缺斤少两)..
那就需要我们自己扩展了, 所以就少不了翻看EF Core源码..
我们通过翻看源码,可以找到MigrationsSqlGenerator这个类. 类名翻译过来,喔唷,这不就是迁移SQL生成器么
那么我们就需要去实现他啦.首先,我们找到达梦实现他的子类:DmMigrationsSqlGenerator
通过反编译,我们发现,果然他并没实现对于Comment属性的代码,那么我们就需要自行扩展
我们添加MyDmigrationsSqlGenerator类继承DmMigrationsSqlGenerator 添加扩展代码如下:
1 using Microsoft.EntityFrameworkCore.Metadata; 2 using Microsoft.EntityFrameworkCore.Migrations; 3 using Microsoft.EntityFrameworkCore.Migrations.Operations; 4 using System; 5 using System.Collections.Generic; 6 using System.Diagnostics.CodeAnalysis; 7 using System.Linq; 8 using System.Text; 9 10 namespace Ciac.ZTBExpert.Model 11 { 12 public class MyDmigrationsSqlGenerator : DmMigrationsSqlGenerator 13 { 14 public MyDmigrationsSqlGenerator([NotNull] MigrationsSqlGeneratorDependencies dependencies, [NotNull] IMigrationsAnnotationProvider migrationsAnnotations) 15 : base(dependencies, migrationsAnnotations) 16 { 17 18 } 19 20 protected override void Generate( 21 CreateTableOperation operation, 22 IModel model, 23 MigrationCommandListBuilder builder, 24 bool terminate) 25 { 26 base.Generate(operation, model, builder, terminate); 27 var comment = operation.Comment; 28 if (comment != null) 29 { 30 if (terminate) 31 { 32 builder.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator); 33 EndStatement(builder); 34 } 35 builder 36 .Append("COMMENT ON TABLE ") 37 .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Name, operation.Schema)) 38 .Append(" IS ") 39 .Append($"'{comment}'") 40 .Append(Dependencies.SqlGenerationHelper.StatementTerminator); 41 } 42 // Comments on the columns 43 foreach (var columnOp in operation.Columns.Where(c => c.Comment != null)) 44 { 45 var columnComment = columnOp.Comment; 46 // builder.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator); 47 EndStatement(builder); 48 builder 49 .Append("COMMENT ON COLUMN ") 50 .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Name, operation.Schema)) 51 .Append('.') 52 .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(columnOp.Name)) 53 .Append(" IS ") 54 .Append($"'{columnComment}'") 55 .Append(Dependencies.SqlGenerationHelper.StatementTerminator); 56 } 57 builder.EndCommand(); 58 } 59 60 61 protected override void Generate(AlterColumnOperation operation, IModel model, MigrationCommandListBuilder builder) 62 { 63 base.Generate(operation, model, builder); 64 // Comment 65 var oldComment = operation.OldColumn.Comment; 66 var newComment = operation.Comment; 67 68 if (oldComment != newComment) 69 { 70 //builder.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator); 71 72 builder 73 .Append("COMMENT ON COLUMN ") 74 .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Table, operation.Schema)) 75 .Append('.') 76 .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Name)) 77 .Append(" IS ") 78 .Append($"'{newComment}'") 79 .Append(Dependencies.SqlGenerationHelper.StatementTerminator); 80 builder.EndCommand(); 81 } 82 83 } 84 85 protected override void Generate(AddColumnOperation operation, IModel model, MigrationCommandListBuilder builder, bool terminate) 86 { 87 base.Generate(operation, model, builder, terminate); 88 // Comment 89 var newComment = operation.Comment; 90 91 if (!string.IsNullOrEmpty(newComment)) 92 { 93 // builder.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator); 94 builder 95 .Append("COMMENT ON COLUMN ") 96 .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Table, operation.Schema)) 97 .Append('.') 98 .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Name)) 99 .Append(" IS ") 100 .Append($"'{newComment}'") 101 .Append(Dependencies.SqlGenerationHelper.StatementTerminator); 102 if (terminate) 103 { 104 EndStatement(builder); 105 } 106 builder.EndCommand(); 107 108 } 109 110 } 111 protected override void Generate([NotNull] AlterTableOperation operation, IModel? model, [NotNull] MigrationCommandListBuilder builder) 112 { 113 base.Generate(operation, model, builder); 114 // Comment 115 var oldComment = operation.OldTable.Comment; 116 var newComment = operation.Comment; 117 118 if (oldComment != newComment) 119 { 120 121 EndStatement(builder); 122 123 builder 124 .Append("COMMENT ON TABLE ") 125 .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Name, operation.Schema)) 126 .Append(" IS ") 127 .Append($"'{newComment}'") 128 .Append(Dependencies.SqlGenerationHelper.StatementTerminator); 129 builder.EndCommand(); 130 } 131 } 132 } 133 }
因为我们只是想在创建或者修改表后添加描述.
所以,我们只需要针对CreateTable,AlterColumn,AddColumn,AlterTable 四个生成方法做重写就好了
最后我们需要在EF上下文初始化之前来替换掉原来的生成器如下:
这样,我们就可以通过在上下文中配置Fluent API就可以自动生成描述了~
我们在EF上下文的OnModelCreating添加代码如下:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<tab_zjcq_ggxx>(a => a.Property("aaa").HasComment("88888")); }
执行迁移语句Script-Migration
结果如下:
ALTER TABLE "tab_zjcq_ggxx" MODIFY "aaa" NVARCHAR2(50) NULL; /COMMENT ON COLUMN "tab_zjcq_ggxx"."aaa" IS '8888';
2.通过添加Description特性来优化代码风格,方便管理
虽然上面第一步就已经实现了我们的要求,但是我们发现,通过Fluent API 来添加描述,代码可读性会很差,
且一旦表多起来,那么OnModelCreating 方法就会变的超长(虽然也可以写在实体类里面,但是就觉得很麻烦)..
那么能不能像[MaxLength(50)] 这种特性一样,直接在字段上加个特性来解决这个事情呢?~
当然是可以的啦~
我们修改OnModelCreating 中的代码如下:
protected override void OnModelCreating(ModelBuilder modelBuilder) { var ddd= modelBuilder.Model.GetEntityTypes().ToList(); foreach (var item in ddd) { var tabtype = Type.GetType(item.ClrType.FullName); var props = tabtype.GetProperties(); var descriptionAttrtable = tabtype.GetCustomAttributes(typeof(DescriptionAttribute), true); if (descriptionAttrtable.Length > 0) { modelBuilder.Entity(item.Name).HasComment(((DescriptionAttribute)descriptionAttrtable[0]).Description); } foreach (var prop in props) { var descriptionAttr = prop.GetCustomAttributes(typeof(DescriptionAttribute), true); if (descriptionAttr.Length>0) { modelBuilder.Entity(item.Name).Property(prop.Name).HasComment(((DescriptionAttribute)descriptionAttr[0]).Description); } } } }
这里通过反射,得到包含DescriptionAttribute特性的字段,然后读取描述信息,通过HasComment 自动添加~
然后我们给字段添加描述如下:
执行迁移语句Script-Migration~
我们会发现,描述已经自动生成啦~
其实不管是.NET 5.0 还是EF Core 在开源化的今天,我们只要愿意去多翻翻源码,会发现自己可以扩展的东西还有很多~很多~
最后..在提一嘴,真心希望国产数据库的访问库 能够开源.. 毕竟,人多力量大~
又不需要数据库应用开源..起码访问组件 你能开源吧..
好了..就到这了 瑞思拜~