【繁星Code】如何在EF将实体注释写入数据库中
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,然后读取对应的字段注释,自动调用改方法即可。需要解决的问题大概有以下几点。
- 如何获取当前配置的字段;
- 加载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中,为了搞这个注释得不偿失,最后才采取了上述的方法。若是大佬有更好的方法,恭请分享。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK