z8018
3 天以前 d8dc91f9c1fece5711e38edd1b1274cb9e579015
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
using SqlSugar;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using WIDESEA_Core.Const;
using WIDESEA_Core.Helper;
 
namespace WIDESEA_Core.Utilities
{
    public static class EntityProperties
    {
 
        /// <summary>
        /// 验证属性值是否符合数据库字段类型要求
        /// </summary>
        /// <param name="propertyInfo">属性信息</param>
        /// <param name="value">待验证的值</param>
        /// <returns>
        /// 元组包含三个值:
        /// Item1 - 验证是否通过(true/false)
        /// Item2 - 验证失败时的错误信息
        /// Item3 - 原始输入值
        /// </returns>
        /// <remarks>
        /// 支持验证以下数据库类型:
        /// - 整数类型(Int/BigInt)
        /// - 日期时间类型(DateTime/Date等)
        /// - 浮点类型(Float/Double/Decimal)
        /// - GUID类型(UniqueIdentifier)
        /// - 字符串类型(VarChar/NVarChar等)的长度限制
        /// </remarks>
        public static (bool, string, object) ValidationVal(this PropertyInfo propertyInfo, object value)
        {
            string dbType = "";
            SugarColumn sugarColumn = null;
            if (propertyInfo != null)
            {
                sugarColumn = propertyInfo.GetCustomAttribute<SugarColumn>();
                dbType = propertyInfo.PropertyType != null ? propertyInfo.GetProperWithDbType() : SqlDbTypeName.NVarChar;
            }
            dbType = dbType.ToLower();
            string val = value?.ToString();
            //验证长度
            string reslutMsg = string.Empty;
            if (dbType == SqlDbTypeName.Int)
            {
                if (!value.IsInt())
                    reslutMsg = "只能为有效整数";
            }  //2021.10.12增加属性校验long类型的支持
            else if (dbType == SqlDbTypeName.BigInt)
            {
                if (!long.TryParse(val, out _))
                {
                    reslutMsg = "只能为有效整数";
                }
            }
            else if (dbType == SqlDbTypeName.DateTime
                || dbType == SqlDbTypeName.Date
                || dbType == SqlDbTypeName.SmallDateTime
                || dbType == SqlDbTypeName.SmallDate
                )
            {
                if (!value.IsDate())
                    reslutMsg = "必须为日期格式";
            }
            else if (dbType == SqlDbTypeName.Float || dbType == SqlDbTypeName.Decimal || dbType == SqlDbTypeName.Double)
            {
 
                if (!val.IsNumber(null))
                {
                    reslutMsg = "不是有效数字";
                }
            }
            else if (dbType == SqlDbTypeName.UniqueIdentifier)
            {
                if (!val.IsGuid())
                {
                    reslutMsg = propertyInfo.Name + "Guid不正确";
                }
            }
            else if (propertyInfo != null
                && (dbType == SqlDbTypeName.VarChar
                || dbType == SqlDbTypeName.NVarChar
                || dbType == SqlDbTypeName.NChar
                || dbType == SqlDbTypeName.Char
                || dbType == SqlDbTypeName.Text))
            {
 
                //默认nvarchar(max) 、text 长度不能超过20000
                if (val.Length > 200000)
                {
                    reslutMsg = $"字符长度最多【200000】";
                }
                else
                {
                    int length = sugarColumn.Length;
                    if (length == 0) { return (true, null, value); }
                    //判断双字节与单字段
                    else if (length < 8000 &&
                        ((dbType.Substring(0, 1) != "n"
                        && Encoding.UTF8.GetBytes(val.ToCharArray()).Length > length)
                         || val.Length > length)
                         )
                    {
                        reslutMsg = $"最多只能【{length}】个字符。";
                    }
                }
            }
            if (!string.IsNullOrEmpty(reslutMsg) && propertyInfo != null)
            {
                reslutMsg = sugarColumn.ColumnDescription + reslutMsg;
            }
            return (reslutMsg == "" ? true : false, reslutMsg, value);
        }
 
        /// <summary>
        /// 验证属性值是否符合数据库类型要求
        /// </summary>
        /// <param name="propertyInfo">要验证的属性信息</param>
        /// <param name="values">要验证的值数组</param>
        /// <returns>返回验证结果列表,每个结果包含是否成功、错误信息和验证后的值</returns>
        public static List<(bool, string, object)> ValidationValueForDbType(this PropertyInfo propertyInfo, params object[] values)
        {
            List<(bool, string, object)> result = new List<(bool, string, object)>();
            foreach (object value in values)
            {
                result.Add(propertyInfo.ValidationVal(value));
            }
            return result;
        }
 
        private static readonly Dictionary<Type, string> ProperWithDbType = new Dictionary<Type, string>() {
            { typeof(string), SqlDbTypeName.NVarChar },
            { typeof(DateTime), SqlDbTypeName.DateTime},
            { typeof(long), SqlDbTypeName.BigInt },
            { typeof(int), SqlDbTypeName.Int},
            { typeof(decimal), SqlDbTypeName.Decimal },
            { typeof(float), SqlDbTypeName.Float },
            { typeof(double), SqlDbTypeName.Double },
            { typeof(byte), SqlDbTypeName.Int },//类型待完
            { typeof(Guid), SqlDbTypeName.UniqueIdentifier}
        };
 
        /// <summary>
        /// 获取属性对应的数据库类型
        /// </summary>
        /// <param name="propertyInfo">属性信息</param>
        /// <returns>如果找到匹配则返回对应的数据库类型,否则返回默认的NVarChar类型</returns>
        public static string GetProperWithDbType(this PropertyInfo propertyInfo)
        {
            bool result = ProperWithDbType.TryGetValue(propertyInfo.PropertyType, out string value);
            if (result)
            {
                return value;
            }
            return SqlDbTypeName.NVarChar;
        }
 
        /// <summary>
        /// 验证字典中的字段是否匹配实体属性,并进行必要的清理和验证
        /// </summary>
        /// <param name="typeinfo">实体类型信息</param>
        /// <param name="dic">待验证的字段字典</param>
        /// <param name="removerKey">是否移除主键字段</param>
        /// <param name="propertyInfo">实体属性集合</param>
        /// <param name="ignoreFields">需要忽略的字段名数组</param>
        /// <returns>验证通过返回空字符串,否则返回错误信息</returns>
        /// <remarks>
        /// 1. 移除字典中不存在的实体字段
        /// 2. 根据配置决定是否移除主键字段
        /// 3. 检查必填字段是否为空
        /// 4. 将空字符串值转为null
        /// </remarks>
        public static string ValidateDicInEntity(this Type typeinfo, Dictionary<string, object> dic, bool removerKey, PropertyInfo[] propertyInfo, string[] ignoreFields = null)
        {
            if (dic == null || dic.Count == 0) { return "参数无效"; }
 
            // 不存在的字段直接移除
            dic.Where(x => !propertyInfo.Any(p => p.Name == x.Key.FirstLetterToUpper())).Select(s => s.Key).ToList().ForEach(f =>
            {
                dic.Remove(f);
            });
 
            string keyName = typeinfo.GetKeyName();
            //移除主键
            if (removerKey)
                dic.Remove(keyName);
            //else
            //{
            //    if (!dic.ContainsKey(keyName))
            //        return "请传入主键参数";
            //}
 
            foreach (PropertyInfo property in propertyInfo)
            {
                SugarColumn sugarColumn = property.GetCustomAttribute<SugarColumn>();
                if (sugarColumn == null)
                    return "请配置SugarColumn属性";
                //忽略与主键的字段不做验证
                if (property.Name == keyName.FirstLetterToUpper() || (ignoreFields != null && ignoreFields.Contains(property.Name)) || sugarColumn.IsOnlyIgnoreInsert || sugarColumn.IsOnlyIgnoreUpdate || sugarColumn.IsIgnore)
                    continue;
 
                //不在编辑中的列,是否也要必填
                if (!dic.ContainsKey(property.Name.FirstLetterToLower()))
                {
                    if (!sugarColumn.IsNullable)
                    {
                        if (sugarColumn.DefaultValue == null)
                            return sugarColumn.ColumnDescription + "为必须提交项";
                        continue;
                    }
                    continue;
                }
                if(dic[property.Name.FirstLetterToLower()] != null)
                {
                    string str = dic[property.Name.FirstLetterToLower()].ToString();
                    //将所有空值设置为null
                    if (str == string.Empty)
                        dic[property.Name.FirstLetterToLower()] = null;
                }
                
            }
            return string.Empty;
        }
 
        /// <summary>
        /// 验证字典列表中的字段是否与实体类型属性匹配
        /// </summary>
        /// <param name="typeinfo">实体类型</param>
        /// <param name="dicList">要验证的字典列表</param>
        /// <param name="removerKey">是否移除不匹配的键</param>
        /// <param name="ignoreFields">要忽略的字段名数组</param>
        /// <returns>返回验证结果消息,若为空表示验证通过</returns>
        public static string ValidateDicInEntity(this Type typeinfo, List<Dictionary<string, object>> dicList, bool removerKey, string[] ignoreFields = null)
        {
            PropertyInfo[] propertyInfo = typeinfo.GetProperties();
            string reslutMsg = string.Empty;
            foreach (Dictionary<string, object> dic in dicList)
            {
                reslutMsg = typeinfo.ValidateDicInEntity(dic, removerKey, propertyInfo, ignoreFields);
                if (!string.IsNullOrEmpty(reslutMsg))
                    return reslutMsg;
            }
            return reslutMsg;
        }
 
        /// <summary>
        /// 获取指定类型的键名称
        /// </summary>
        /// <param name="typeinfo">要获取键名称的类型</param>
        /// <returns>返回该类型的键名称</returns>
        public static string GetKeyName(this Type typeinfo)
        {
            return typeinfo.GetProperties().GetKeyName();
        }
 
        /// <summary>
        /// 从属性集合中获取主键属性的名称
        /// </summary>
        /// <param name="properties">要搜索的属性集合</param>
        /// <returns>如果找到主键属性则返回其名称,否则返回null</returns>
        /// <remarks>通过查找带有[SugarColumn(IsPrimaryKey = true)]特性的属性来确定主键</remarks>
        public static string GetKeyName(this PropertyInfo[] properties)
        {
            foreach (PropertyInfo property in properties)
            {
                SugarColumn sugarColumn = property.GetCustomAttribute<SugarColumn>();
                if (sugarColumn.IsPrimaryKey)
                    return property.Name;
            }
            return null;
        }
 
        /// <summary>
        /// 获取指定类型的主键属性
        /// </summary>
        /// <param name="typeinfo">要检查的类型</param>
        /// <returns>如果找到标记为[SugarColumn(IsPrimaryKey = true)]的属性则返回该属性,否则返回null</returns>
        public static PropertyInfo GetKeyProperty(this Type typeinfo)
        {
            PropertyInfo[] properties = typeinfo.GetProperties();
            foreach (PropertyInfo property in properties)
            {
                SugarColumn sugarColumn = property.GetCustomAttribute<SugarColumn>();
                if (sugarColumn?.IsPrimaryKey ?? false)
                {
                    return property;
                }
            }
            return null;
        }
 
        /// <summary>
        /// 获取类型的导航属性详细类型
        /// </summary>
        /// <param name="typeinfo">要检查的类型</param>
        /// <returns>
        /// 如果找到OneToOne导航属性则返回属性类型,
        /// 如果是其他导航类型则返回泛型参数类型,
        /// 未找到导航属性则返回null
        /// </returns>
        public static Type GetDetailType(this Type typeinfo)
        {
            PropertyInfo[] properties = typeinfo.GetProperties();
            foreach (PropertyInfo property in properties)
            {
                Navigate? navigate = property.GetCustomAttribute<Navigate>();
                if (navigate is not null)
                {
                    if (navigate.GetNavigateType() == NavigateType.OneToOne)
                        return property.PropertyType;
                    else
                        return property.PropertyType.GenericTypeArguments[0];
                }
            }
            return null;
        }
 
        /// <summary>
        /// 通过实体类型获取其主键属性名称
        /// </summary>
        /// <param name="typeinfo">实体类型</param>
        /// <returns>主键属性名称,如果未找到则返回null</returns>
        /// <remarks>
        /// 该方法通过查找实体类型上标记了[Navigate]特性的属性来获取主键名称
        /// </remarks>
        public static string GetMainIdByDetail(this Type typeinfo)
        {
            PropertyInfo[] properties = typeinfo.GetProperties();
            foreach (PropertyInfo property in properties)
            {
                Navigate? navigate = property.GetCustomAttribute<Navigate>();
                if (navigate is not null)
                {
                    return navigate.GetName();
                }
            }
            return null;
        }
 
        /// <summary>
        /// 为实体对象设置指定名称的属性值
        /// </summary>
        /// <typeparam name="T">实体类型</typeparam>
        /// <param name="typeinfo">实体类型信息</param>
        /// <param name="enetiy">目标实体对象</param>
        /// <param name="id">要设置的属性值</param>
        /// <param name="name">属性名称</param>
        public static void SetDetailId<T>(this Type typeinfo, T enetiy, object id, string name)
        {
            PropertyInfo property = typeinfo.GetProperty(name);
            if (property != null)
            {
                property.SetValue(enetiy, id);
            }
        }
 
        /// <summary>
        /// 获取指定类型中标记了[Navigate]特性的属性
        /// </summary>
        /// <param name="typeinfo">要搜索的目标类型</param>
        /// <returns>找到的PropertyInfo对象,若未找到则返回null</returns>
        public static PropertyInfo? GetNavigatePro(this Type typeinfo)
        {
            PropertyInfo[] properties = typeinfo.GetProperties();
            foreach (PropertyInfo property in properties)
            {
                Navigate? navigate = property.GetCustomAttribute<Navigate>();
                if (navigate is not null)
                {
                    return property;
                }
            }
            return null;
        }
 
        /// <summary>
        /// 获取指定对象的属性值
        /// </summary>
        /// <typeparam name="T">对象类型</typeparam>
        /// <param name="typeinfo">类型信息</param>
        /// <param name="data">目标对象</param>
        /// <param name="propertyName">属性名称</param>
        /// <returns>属性值,如果属性不存在则返回null</returns>
        public static object GetPropertyValue<T>(this Type typeinfo, T data, string propertyName)
        {
            if (typeinfo != typeof(T))
                return null;
 
            PropertyInfo? property = typeinfo.GetProperty(propertyName);
            if (property != null)
            {
                return property.GetValue(data);
            }
            return null;
        }
    }
}