【繁星Code】如何在EF将实体注释写入数据库中
程序员文章站
2022-05-03 10:32:12
最近在项目中需要把各个字段的释义写到数据库中,该项目已经上线很长时间了,数据库中的字段没有上千也有上百个,要是一个项目一个项目打开然后再去找对应字段查看什么意思,估计要到明年过年了。由于项目中使用EntityFramework,本身这个项目只有手动设置字段注释的功能,Coder平时写代码的时候都懒得 ......
最近在项目中需要把各个字段的释义写到数据库中,该项目已经上线很长时间了,数据库中的字段没有上千也有上百个,要是一个项目一个项目打开然后再去找对应字段查看什么意思,估计要到明年过年了。由于项目中使用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,然后读取对应的字段注释,自动调用改方法即可。需要解决的问题大概有以下几点。
- 如何获取当前配置的字段;
- 加载xml,并根据字段获取对应的注释;
- 如何将枚举的各项信息都放入注释中;
二、实现方法
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中,为了搞这个注释得不偿失,最后才采取了上述的方法。若是大佬有更好的方法,恭请分享。
上一篇: SpringBoot整合Shiro实现登录认证的方法
下一篇: 微信小程序bug记录与解决