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