6

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

 3 years ago
source link: http://www.cnblogs.com/Lbqman/p/14381690.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

最近在项目中需要把各个字段的释义写到数据库中,该项目已经上线很长时间了,数据库中的字段没有上千也有上百个,要是一个项目一个项目打开然后再去找对应字段查看什么意思,估计要到明年过年了。由于项目中使用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中,为了搞这个注释得不偿失,最后才采取了上述的方法。若是大佬有更好的方法,恭请分享。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK