【繁星Code】如何在EF将实体注释写入数据库中

  • A+
所属分类:.NET技术
摘要

        最近在项目中需要把各个字段的释义写到数据库中,该项目已经上线很长时间了,数据库中的字段没有上千也有上百个,要是一个项目一个项目打开然后再去找对应字段查看什么意思,估计要到明年过年了。由于项目中使用EntityFramework,本身这个项目只有手动设置字段注释的功能,Coder平时写代码的时候都懒得写注释,更别提能在配置数据库的时候将注释配置进去,所以如何在EF中自动将实体注释写入数据库,减轻Coder的压力(ru he tou lan)尤为重要。gitee地址:https://gitee.com/lbqman/Blog20210206.git 。下面进入正题。

        最近在项目中需要把各个字段的释义写到数据库中,该项目已经上线很长时间了,数据库中的字段没有上千也有上百个,要是一个项目一个项目打开然后再去找对应字段查看什么意思,估计要到明年过年了。由于项目中使用EntityFramework,本身这个项目只有手动设置字段注释的功能,Coder平时写代码的时候都懒得写注释,更别提能在配置数据库的时候将注释配置进去,所以如何在EF中自动将实体注释写入数据库,减轻Coder的压力(ru he tou lan)尤为重要。gitee地址:https://gitee.com/lbqman/Blog20210206.git 。下面进入正题。

一、实现思路

        在FluentAPI中提供了HasComment方法,如下
 
 
 
 
 
11

 
 
 

1

    /// <summary>Configures a comment to be applied to the column</summary>

2

    /// <typeparam name="TProperty"> The type of the property being configured. </typeparam>

3

    /// <param name="propertyBuilder"> The builder for the property being configured. </param>

4

    /// <param name="comment"> The comment for the column. </param>

5

    /// <returns> The same builder instance so that multiple calls can be chained. </returns>

6

    public static PropertyBuilder<TProperty> HasComment<TProperty>(

7

      [NotNull] this PropertyBuilder<TProperty> propertyBuilder,

8

      [CanBeNull] string comment)

9

    {

10

      return (PropertyBuilder<TProperty>) propertyBuilder.HasComment(comment);

11

    }

 
 

也就是说我们要获取到实体对象的注释xml,然后读取对应的字段注释,自动调用改方法即可。需要解决的问题大概有以下几点。
  1. 如何获取当前配置的字段;
  2. 加载Xml,并根据字段获取对应的注释;
  3. 如何将枚举的各项信息都放入注释中;

二、实现方法

    1.如何获取当前配置的字段

       在获取xml的注释中,需要的信息有实体对应的类型以及对应的字段。而包含这两种类型的方法只有Property这个方法,该方法的出入参如下:
 
 
 
 
 
2

 
 
 

1

public virtual PropertyBuilder<TProperty> Property<TProperty>(

2

      [NotNull] Expression<Func<TEntity, TProperty>> propertyExpression);

 
 

        所以我们准备对这个方法进行改造。并且根据传入的propertyExpression获取字段名称。方法如下:
 
 
 
 
 
4

 
 
 

1

        public static PropertyBuilder<TProperty> SummaryProperty<TEntity, TProperty>(

2

            this EntityTypeBuilder<TEntity> entityTypeBuilder,

3

            Expression<Func<TEntity, TProperty>> propertyExpression)

4

            where TEntity : class

 
 

        根据表达式获取字段名称如下:
 
 
 
 
 
10

 
 
 

1

        public static MemberInfo GetMember<T, TProperty>(this Expression<Func<T, TProperty>> expression)

2

        {

3

            MemberExpression memberExp;

4

            if (expression.Body is UnaryExpression unaryExpression)

5

                memberExp = unaryExpression.Operand as MemberExpression;

6

            else

7

                memberExp = expression.Body as MemberExpression;

8


9

            return memberExp?.Member;

10

        }

 
 

  2.加载Xml,并根据字段获取对应的注释

    VS中的Xml格式不在此处过多解释,属性、方法等注释可以根据规律去获取。此处需要注意的是当字段是从父类继承而来并且父类属于不同的dll时,需要获取父类所在dll的xml注释才行,另外枚举也需要同样去处理。虽然获取Xml数据的方法只在更新数据库时才调用,但是还是需要使用字典将数据缓存下来,方便下次快速获取。具体代码如下:
 
 
 
 
 
204

 
 
 

1

   /// <summary>

2

    /// xml注释获取器

3

    /// </summary>

4

    internal static class SummaryXmlCacheProvider

5

    {

6

        #region TClass

7


8

        /// <summary>

9

        /// 根据类型初始化该类所在程序集的xml

10

        /// </summary>

11

        /// <typeparam name="TClass"></typeparam>

12

        internal static void InitSummaryXml<TClass>()

13

        {

14

            var assembly = Assembly.GetAssembly(typeof(TClass));

15

            SerializeXmlFromAssembly(assembly);

16

        }

17


18

        /// <summary>

19

        /// 根据类型获取该类所在程序集的xml

20

        /// </summary>

21

        /// <typeparam name="TClass"></typeparam>

22

        /// <returns></returns>

23

        internal static Dictionary<string, string> GetSummaryXml<TClass>()

24

        {

25

            var assembly = Assembly.GetAssembly(typeof(TClass));

26

            return SummaryCache[assembly];

27

        }

28


29

        /// <summary>

30

        /// 获取该类在xml的key

31

        /// </summary>

32

        /// <typeparam name="TClass"></typeparam>

33

        /// <returns></returns>

34

        internal static string GetClassTypeKey<TClass>()

35

        {

36

            return TableSummaryRuleProvider.TypeSummaryKey(typeof(TClass).FullName);

37

        }

38


39

        #endregion

40


41

        #region TProperty

42


43

        /// <summary>

44

        /// 根据类型以及字段初始化该类所在程序集的xml

45

        /// </summary>

46

        /// <typeparam name="TClass"></typeparam>

47

        /// <typeparam name="TProperty"></typeparam>

48

        /// <param name="propertyExpression"></param>

49

        internal static void InitSummaryXml<TClass, TProperty>(Expression<Func<TClass, TProperty>> propertyExpression)

50

        {

51

            var propertyAssembly = GetPropertyAssembly(propertyExpression);

52

            SerializeXmlFromAssembly(propertyAssembly);

53

        }

54


55

        /// <summary>

56

        /// 根据类型以及字段获取该类所在程序集的xml

57

        /// </summary>

58

        /// <typeparam name="TClass"></typeparam>

59

        /// <typeparam name="TProperty"></typeparam>

60

        /// <param name="propertyExpression"></param>

61

        /// <returns></returns>

62

        internal static Dictionary<string, string> GetSummaryXml<TClass, TProperty>(

63

            Expression<Func<TClass, TProperty>> propertyExpression)

64

        {

65

            var propertyAssembly = GetPropertyAssembly(propertyExpression);

66

            return SummaryCache[propertyAssembly];

67

        }

68


69

        /// <summary>

70

        /// 获取该类以及字段所在xml的key

71

        /// </summary>

72

        /// <typeparam name="TClass"></typeparam>

73

        /// <typeparam name="TProperty"></typeparam>

74

        /// <param name="propertyExpression"></param>

75

        /// <returns></returns>

76

        internal static string GetPropertyTypeKey<TClass, TProperty>(

77

            Expression<Func<TClass, TProperty>> propertyExpression)

78

        {

79

            var memberName = propertyExpression.GetMember().Name;

80

            var propertyInfo = GetPropertyInfo(propertyExpression);

81

            var propertyKey =

82

                $"{propertyInfo.DeclaringType.Namespace}.{propertyInfo.DeclaringType.Name}.{memberName}";

83

            return PropertySummaryRuleProvider.PropertyTypeSummaryKey(propertyKey);

84

        }

85


86

        #endregion

87


88

        #region TEnum

89


90

        /// <summary>

91

        /// 获取枚举字段的描述信息

92

        /// </summary>

93

        /// <typeparam name="TClass"></typeparam>

94

        /// <typeparam name="TProperty"></typeparam>

95

        /// <param name="propertyExpression"></param>

96

        /// <returns></returns>

97

        internal static string GetEnumPropertyDescription<TClass, TProperty>(Expression<Func<TClass, TProperty>> propertyExpression)

98

        {

99

            var propertyInfo = GetPropertyInfo(propertyExpression);

100

            if (!propertyInfo.PropertyType.IsEnum)

101

                return string.Empty;

102

            var enumType = propertyInfo.PropertyType;

103

            SerializeXmlFromAssembly(enumType.Assembly);

104

            var propertySummaryDic = SummaryCache[enumType.Assembly];

105

            var enumNames = enumType.GetEnumNames();

106

            var enumDescDic = enumType.GetNameAndValues();

107

            var enumSummaries = new List<string>();

108

            foreach (var enumName in enumNames)

109

            {

110

                var propertyEnumKey = PropertySummaryRuleProvider.EnumTypeSummaryKey($"{enumType.FullName}.{enumName}");

111

                var enumSummary = propertySummaryDic.ContainsKey(propertyEnumKey)

112

                    ? propertySummaryDic[propertyEnumKey]

113

                    : string.Empty;

114

                var enumValue = enumDescDic[enumName];

115

                enumSummaries.Add(PropertySummaryRuleProvider.EnumTypeSummaryFormat(enumValue,enumName,enumSummary));

116

            }

117


118

            return string.Join(";", enumSummaries);

119


120

        }

121


122

        #endregion

123


124

        /// <summary>

125

        /// 根据表达式获取属性所在的程序集

126

        /// </summary>

127

        /// <typeparam name="TClass"></typeparam>

128

        /// <typeparam name="TProperty"></typeparam>

129

        /// <param name="propertyExpression"></param>

130

        /// <returns></returns>

131

        private static Assembly GetPropertyAssembly<TClass, TProperty>(

132

            Expression<Func<TClass, TProperty>> propertyExpression)

133

        {

134

            var propertyInfo = GetPropertyInfo(propertyExpression);

135

            var propertyAssembly = propertyInfo.Module.Assembly;

136

            return propertyAssembly;

137

        }

138


139

        /// <summary>

140

        /// 根据表达式获取字段属性

141

        /// </summary>

142

        /// <typeparam name="TClass"></typeparam>

143

        /// <typeparam name="TProperty"></typeparam>

144

        /// <param name="propertyExpression"></param>

145

        /// <returns></returns>

146

        private static PropertyInfo GetPropertyInfo<TClass, TProperty>(

147

            Expression<Func<TClass, TProperty>> propertyExpression)

148

        {

149

            var entityType = typeof(TClass);

150

            var memberName = propertyExpression.GetMember().Name;

151

            var propertyInfo = entityType.GetProperty(memberName, typeof(TProperty));

152

            if (propertyInfo == null || propertyInfo.DeclaringType == null)

153

                throw new ArgumentNullException($"this property {memberName} is not belong to {entityType.Name}");

154


155

            return propertyInfo;

156

        }

157


158

        /// <summary>

159

        /// 根据程序集初始化xml

160

        /// </summary>

161

        /// <param name="assembly"></param>

162

        private static void SerializeXmlFromAssembly(Assembly assembly)

163

        {

164

            var assemblyPath = assembly.Location;

165

            var lastIndexOf = assemblyPath.LastIndexOf(".dll", StringComparison.Ordinal);

166

            var xmlPath = assemblyPath.Remove(lastIndexOf, 4) + ".xml";

167


168

            if (SummaryCache.ContainsKey(assembly))

169

                return;

170

            var xmlDic = new Dictionary<string, string>();

171

            if (!File.Exists(xmlPath))

172

            {

173

                Console.WriteLine($"未能加载xml文件,原因:xml文件不存在,path:{xmlPath}");

174

                SummaryCache.Add(assembly, xmlDic);

175

                return;

176

            }

177


178

            var doc = new XmlDocument();

179

            doc.Load(xmlPath);

180

            var members = doc.SelectNodes("doc/members/member");

181

            if (members == null)

182

            {

183

                Console.WriteLine($"未能加载xml文件,原因:doc/members/member节点不存在");

184

                SummaryCache.Add(assembly, xmlDic);

185

                return;

186

            }

187


188

            foreach (XmlElement member in members)

189

            {

190

                var name = member.Attributes["name"].InnerText.Trim();

191

                if (string.IsNullOrWhiteSpace(name))

192

                    continue;

193

                xmlDic.Add(name, member.SelectSingleNode("summary")?.InnerText.Trim());

194

            }

195


196

            SummaryCache.Add(assembly, xmlDic);

197

        }

198


199

        /// <summary>

200

        /// xml注释缓存

201

        /// </summary>

202

        private static Dictionary<Assembly, Dictionary<string, string>> SummaryCache { get; } =

203

            new Dictionary<Assembly, Dictionary<string, string>>();

204

    }

 
 

    3.如何将枚举的各项信息都放入注释中

  上面的两个步骤已经根据表达式将字段的注释获取到,直接调用EF提供的HasComment即可。见代码:
 
 
 
 
 
15

 
 
 

1

        public static PropertyBuilder<TProperty> SummaryProperty<TEntity, TProperty>(

2

            this EntityTypeBuilder<TEntity> entityTypeBuilder,

3

            Expression<Func<TEntity, TProperty>> propertyExpression)

4

            where TEntity : class

5

        {

6

            SummaryXmlCacheProvider.InitSummaryXml(propertyExpression);

7

            var entitySummaryDic = SummaryXmlCacheProvider.GetSummaryXml(propertyExpression);

8

            var propertyKey = SummaryXmlCacheProvider.GetPropertyTypeKey(propertyExpression);

9

            var summary = entitySummaryDic.ContainsKey(propertyKey) ? entitySummaryDic[propertyKey] : string.Empty;

10

            var enumDescription = SummaryXmlCacheProvider.GetEnumPropertyDescription(propertyExpression);

11

            summary = string.IsNullOrWhiteSpace(enumDescription) ? summary : $"{summary}:{enumDescription}";

12

            return string.IsNullOrWhiteSpace(summary)

13

                ? entityTypeBuilder.Property(propertyExpression)

14

                : entityTypeBuilder.Property(propertyExpression).HasComment(summary);

15

        }

 
 

  同时也可以设置表的注释以及表的名称。如下:
 
 
 
 
 
26

 
 
 

1

public static EntityTypeBuilder<TEntity> SummaryToTable<TEntity>(

2

            this EntityTypeBuilder<TEntity> entityTypeBuilder, bool hasTableComment = true,

3

            Func<string> tablePrefix = null)

4

            where TEntity : class

5

        {

6

            var tableName = GetTableName<TEntity>(tablePrefix);

7

            return hasTableComment

8

                ? entityTypeBuilder.ToTable(tableName)

9

                    .SummaryHasComment()

10

                : entityTypeBuilder.ToTable(tableName);

11

        }

12


13

        public static EntityTypeBuilder<TEntity> SummaryHasComment<TEntity>(

14

            this EntityTypeBuilder<TEntity> entityTypeBuilder) where TEntity : class

15

        {

16

            SummaryXmlCacheProvider.InitSummaryXml<TEntity>();

17

            var entityDic = SummaryXmlCacheProvider.GetSummaryXml<TEntity>();

18

            var tableKey = SummaryXmlCacheProvider.GetClassTypeKey<TEntity>();

19

            var summary = entityDic.ContainsKey(tableKey) ? entityDic[tableKey] : string.Empty;

20

            return string.IsNullOrWhiteSpace(summary) ? entityTypeBuilder : entityTypeBuilder.HasComment(summary);

21

        }

22


23

        private static string GetTableName<TEntity>(Func<string> tablePrefix)

24

        {

25

            return typeof(TEntity).Name.Replace("Entity", $"Tb{tablePrefix?.Invoke()}");

26

        }

 
 

  搞定。

三、效果展示

  运行Add-Migration InitDb即可查看生成的代码。
 
 
 
 
 
 
 
 
 

 
 
1

    public partial class InitDb : Migration

2

    {

3

        protected override void Up(MigrationBuilder migrationBuilder)

4

        {

5

            migrationBuilder.CreateTable(

6

                name: "TbGood",

7

                columns: table => new

8

                {

9

                    Id = table.Column<long>(nullable: false, comment: "主键")

10

                        .Annotation("SqlServer:Identity", "1, 1"),

11

                    CreateTime = table.Column<DateTime>(nullable: false, comment: "创建时间"),

12

                    CreateName = table.Column<string>(maxLength: 64, nullable: false, comment: "创建人姓名"),

13

                    UpdateTime = table.Column<DateTime>(nullable: true, comment: "更新时间"),

14

                    UpdateName = table.Column<string>(maxLength: 64, nullable: true, comment: "更新人姓名"),

15

                    Name = table.Column<string>(maxLength: 64, nullable: false, comment: "商品名称"),

16

                    GoodType = table.Column<int>(nullable: false, comment: "物品类型:(0,Electronic) 电子产品;(1,Clothes) 衣帽服装;(2,Food) 食品;(3,Other) 其他物品"),

17

                    Description = table.Column<string>(maxLength: 2048, nullable: true, comment: "物品描述"),

18

                    Store = table.Column<int>(nullable: false, comment: "储存量")

19

                },

20

                constraints: table =>

21

                {

22

                    table.PrimaryKey("PK_TbGood", x => x.Id);

23

                },

24

                comment: "商品实体类");

25


26

            migrationBuilder.CreateTable(

27

                name: "TbOrder",

28

                columns: table => new

29

                {

30

                    Id = table.Column<long>(nullable: false, comment: "主键")

31

                        .Annotation("SqlServer:Identity", "1, 1"),

32

                    CreateTime = table.Column<DateTime>(nullable: false, comment: "创建时间"),

33

                    CreateName = table.Column<string>(maxLength: 64, nullable: false, comment: "创建人姓名"),

34

                    UpdateTime = table.Column<DateTime>(nullable: true, comment: "更新时间"),

35

                    UpdateName = table.Column<string>(maxLength: 64, nullable: true, comment: "更新人姓名"),

36

                    GoodId = table.Column<long>(nullable: false, comment: "商品Id"),

37

                    OrderStatus = table.Column<int>(nullable: false, comment: "订单状态:(0,Ordered) 已下单;(1,Payed) 已付款;(2,Complete) 已付款;(3,Cancel) 已取消"),

38

                    OrderTime = table.Column<DateTime>(nullable: false, comment: "下订单时间"),

39

                    Address = table.Column<string>(maxLength: 2048, nullable: false, comment: "订单地址"),

40

                    UserName = table.Column<string>(maxLength: 16, nullable: false, comment: "收件人姓名"),

41

                    TotalAmount = table.Column<decimal>(nullable: false, comment: "总金额")

42

                },

43

                constraints: table =>

44

                {

45

                    table.PrimaryKey("PK_TbOrder", x => x.Id);

46

                },

47

                comment: "订单实体类");

48

        }

49


50

        protected override void Down(MigrationBuilder migrationBuilder)

51

        {

52

            migrationBuilder.DropTable(

53

                name: "TbGood");

54


55

            migrationBuilder.DropTable(

56

                name: "TbOrder");

57

        }

58

    }

 
 

四、写在最后

  此种方法是在我目前能想起来比较方便生成注释的方法了,另外还想过EntityTypeBuilder中的Property方法,最后还是放弃了,因为EntityTypeBuilder是由ModelBuilder生成,而ModelBuilder又是ModelSource中的CreateModel方法产生,最后一路深扒到DbContext中,为了搞这个注释得不偿失,最后才采取了上述的方法。若是大佬有更好的方法,恭请分享。