EF Core3.1 CodeFirst動態自動新增表和欄位的描述資訊

GuZhenYin發表於2021-07-14
前言

我又來啦..

本篇主要記錄如何針對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 在開源化的今天,我們只要願意去多翻翻原始碼,會發現自己可以擴充套件的東西還有很多~很多~

最後..在提一嘴,真心希望國產資料庫的訪問庫 能夠開源.. 畢竟,人多力量大~

又不需要資料庫應用開源..起碼訪問元件 你能開源吧..

好了..就到這了 瑞思拜~

 

相關文章