using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using VolPro.Core.UserManager;
using VolPro.Entity.DomainModels;
namespace VolPro.Core.Generic
{
///
/// 通用数据库字段与明细数据校验扩展
///
public static class GenericDbValidationExtensions
{
///
/// 字段校验:必填、长度、类型
///
public static string ValidateColumns(this T provider,
Dictionary mainData,
List columns)
where T : GenericDbProviderBase
{
foreach (var col in columns)
{
string colName = col.ColumnName;
string displayName = string.IsNullOrEmpty(col.ColumnCnName) ? colName : col.ColumnCnName;
mainData.TryGetValue(colName, out object val);
bool hasValue = val != null && !(val is string s && string.IsNullOrWhiteSpace(s));
if (col.IsKey==1)
{
continue;
}
// 必填校验(IsNull 为 0 时字段必填)
if ((col.IsNull ?? 1) == 0 && !hasValue)
{
return $"【{displayName}】不能为空";
}
if (!hasValue)
{
continue;
}
string type = (col.ColumnType ?? "").Trim().ToLower();
// 最大长度校验(只判断字符串类型)
if (type == "string" && (col.Maxlength ?? 0) > 0)
{
string str = val.ToString();
if (col.Maxlength.HasValue && str.Length > col.Maxlength.Value)
{
return $"【{displayName}】长度不能超过{col.Maxlength}个字符";
}
}
// 类型匹配校验
if (!provider.CheckAndConvertValue(mainData, colName, val, type, displayName, out string errorMsg))
{
return errorMsg;
}
}
return null;
}
///
/// 处理字段类型转换与校验
///
public static bool CheckAndConvertValue(this T provider,
Dictionary mainData,
string fieldName,
object val,
string type,
string displayName,
out string error)
where T : GenericDbProviderBase
{
error = null;
if (string.IsNullOrEmpty(type))
{
return true;
}
try
{
object converted = val;
string str = val as string ?? val.ToString();
switch (type)
{
case "int":
case "short":
case "byte":
if (!(val is int || val is short || val is byte))
{
if (!int.TryParse(str, out int iv))
{
error = $"【{displayName}】必须是整数";
return false;
}
converted = iv;
}
break;
case "long":
case "bigint":
if (!(val is long))
{
if (!long.TryParse(str, out long lv))
{
error = $"【{displayName}】必须是整数";
mainData[fieldName] = lv;
return false;
}
converted = lv;
}
break;
case "decimal":
if (!(val is decimal))
{
if (!decimal.TryParse(str, out decimal dv))
{
error = $"【{displayName}】必须是数字";
return false;
}
converted = dv;
}
break;
case "float":
if (!(val is float || val is double))
{
if (!double.TryParse(str, out double fv))
{
error = $"【{displayName}】必须是数字";
mainData[fieldName] = fv;
return false;
}
converted = fv;
}
break;
case "datetime":
case "date":
case "time":
if (!(val is DateTime))
{
if (!DateTime.TryParse(str, out DateTime dt))
{
error = $"【{displayName}】日期格式不正确";
return false;
}
converted = dt;
}
break;
case "bool":
if (!(val is bool))
{
string b = str.ToLower();
if (b == "1" || b == "true" || b == "是")
{
converted = true;
}
else if (b == "0" || b == "false" || b == "否")
{
converted = false;
}
else
{
error = $"【{displayName}】必须是布尔值";
return false;
}
}
break;
case "guid":
if (!(val is Guid))
{
if (!Guid.TryParse(str, out Guid g))
{
error = $"【{displayName}】不是有效的Guid";
return false;
}
converted = g;
}
break;
default:
break;
}
mainData[fieldName] = converted;
return true;
}
catch
{
error = $"【{displayName}】数据格式不正确";
return false;
}
}
///
/// 根据表字段配置获取对应的 DbType,避免 PgSql 等数据库将参数推断为 text 导致类型不匹配
///
public static DbType? GetDbType(this TableColumnField col)
{
if (col?.ColumnType == null) return null;
string type = col.ColumnType.Trim().ToLower();
switch (type)
{
case "int":
case "short":
case "byte":
return DbType.Int32;
case "long":
case "bigint":
return DbType.Int64;
case "string":
return DbType.String;
case "guid":
case "uniqueidentifier":
return DbType.Guid;
case "datetime":
case "date":
case "time":
return DbType.DateTime;
case "decimal":
return DbType.Decimal;
case "float":
case "double":
return DbType.Double;
case "bool":
return DbType.Boolean;
default:
return null;
}
}
///
/// 结合列元数据与运行时值确定参数 DbType(与 SugarParameter / Dapper 一致)
///
internal static DbType? EffectiveDapperDbType(object value, TableColumnField col)
{
if (IsGuidColumn(col) && value is Guid) return DbType.Guid;
var fromCol = col?.GetDbType();
if (fromCol != null) return fromCol;
return InferDbTypeFromClrValue(value);
}
private static DbType? InferDbTypeFromClrValue(object value)
{
if (value == null || value is DBNull) return null;
var tc = Type.GetTypeCode(value.GetType());
switch (tc)
{
case TypeCode.Boolean: return DbType.Boolean;
case TypeCode.Byte: return DbType.Byte;
case TypeCode.Int16: return DbType.Int16;
case TypeCode.Int32: return DbType.Int32;
case TypeCode.Int64: return DbType.Int64;
case TypeCode.Single: return DbType.Single;
case TypeCode.Double: return DbType.Double;
case TypeCode.Decimal: return DbType.Decimal;
case TypeCode.DateTime: return DbType.DateTime;
case TypeCode.String: return DbType.String;
default:
return value is Guid ? DbType.Guid : (DbType?)null;
}
}
///
/// 列是否为 Guid / uniqueidentifier 语义
///
internal static bool IsGuidColumn(TableColumnField col)
{
if (col == null) return false;
if (col.GetDbType() == DbType.Guid) return true;
string t = (col.ColumnType ?? "").Trim().ToLowerInvariant();
return t == "guid" || t == "uniqueidentifier";
}
///
/// 判断值是否等于0(用于判断主键是否有意义的值)
///
public static bool IsZero(this T provider, object value)
where T : GenericDbProviderBase
{
if (value == null) return true;
switch (Type.GetTypeCode(value.GetType()))
{
case TypeCode.Byte:
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64:
case TypeCode.Decimal:
case TypeCode.Double:
case TypeCode.Single:
return Convert.ToDecimal(value) == 0;
case TypeCode.String:
if (decimal.TryParse((string)value, out decimal d))
{
return d == 0;
}
break;
}
return false;
}
///
/// Add 时,对所有明细表做字段校验,且忽略外键字段(与主表主键同名字段)的必填要求
///
public static string ValidateAllDetails(this T provider, SaveModel saveModel, List mainTableColumns, TableColumnField mainKeyColumn)
where T : GenericDbProviderBase
{
// 单明细表
if (saveModel.DetailData != null && saveModel.DetailData.Count > 0)
{
string msg = provider.ValidateDetailList(provider.TableInfo.DetailName, saveModel.DetailData, mainKeyColumn);
if (!string.IsNullOrEmpty(msg)) return msg;
}
// 多明细
if (saveModel.Details != null && saveModel.Details.Count > 0)
{
foreach (var item in saveModel.Details)
{
if (item?.Data == null || item.Data.Count == 0) continue;
string msg = provider.ValidateDetailList(item.Table, item.Data, mainKeyColumn);
if (!string.IsNullOrEmpty(msg)) return msg;
}
}
return null;
}
///
/// Add 时,校验明细表的所有数据行(忽略外键必填)
///
public static string ValidateDetailList(this T provider, string detailTableName, List> rows, TableColumnField mainKeyColumn)
where T : GenericDbProviderBase
{
if (string.IsNullOrEmpty(detailTableName) || rows == null || rows.Count == 0) return null;
var detailColumns = TableColumnContext.Data
.Where(x => x.TableName == detailTableName && x.ReferenceField == 0 && x.IsKey != 1)
.ToList();
if (detailColumns == null || detailColumns.Count == 0) return null;
// 找到外键字段:与主表主键同名
var foreignCol = detailColumns.FirstOrDefault(c => c.ColumnName.Equals(mainKeyColumn.ColumnName, StringComparison.OrdinalIgnoreCase));
List ingroCols = new List();
if (foreignCol != null)
{
ingroCols.Add(foreignCol.ColumnName);
}
// 忽略外键必填,把外键字段的 IsNull 视为可空(1),即不参与必填校验
List validateColumns = detailColumns.Where(c => !ingroCols.Contains(c.ColumnName))
.ToList();
for (int i = 0; i < rows.Count; i++)
{
var row = rows[i];
string msg = provider.ValidateColumns(row, validateColumns);
if (!string.IsNullOrEmpty(msg))
{
return "第[{$ts}]行".TranslatorFormat(i + 1) + "," + msg;
}
}
return null;
}
///
/// Update 时,对所有明细表做字段校验(只校验提交的字段,并忽略外键必填)
///
public static string ValidateAllDetailsForUpdate(this T provider, SaveModel saveModel, TableColumnField mainKeyColumn)
where T : GenericDbProviderBase
{
// 单明细表
if (saveModel.DetailData != null && saveModel.DetailData.Count > 0)
{
string msg = provider.ValidateDetailListForUpdate(provider.TableInfo.DetailName, saveModel.DetailData, mainKeyColumn);
if (!string.IsNullOrEmpty(msg)) return msg;
}
// 多明细
if (saveModel.Details != null && saveModel.Details.Count > 0)
{
foreach (var item in saveModel.Details)
{
if (item?.Data == null || item.Data.Count == 0) continue;
string msg = provider.ValidateDetailListForUpdate(item.Table, item.Data, mainKeyColumn);
if (!string.IsNullOrEmpty(msg)) return msg;
}
}
return null;
}
///
/// Update 时,校验某一张明细表的所有数据行(只校验提交的字段,并忽略外键必填)
///
public static string ValidateDetailListForUpdate(this T provider, string detailTableName, List> rows, TableColumnField mainKeyColumn)
where T : GenericDbProviderBase
{
if (string.IsNullOrEmpty(detailTableName) || rows == null || rows.Count == 0) return null;
var detailColumns = TableColumnContext.Data
.Where(x => x.TableName == detailTableName && x.ReferenceField == 0)
.ToList();
if (detailColumns == null || detailColumns.Count == 0) return null;
// 找到外键字段:与主表主键同名、同类型
var foreignCol = detailColumns.FirstOrDefault(c =>
c.ColumnName.Equals(mainKeyColumn.ColumnName, StringComparison.OrdinalIgnoreCase)
&& string.Equals(c.ColumnType, mainKeyColumn.ColumnType, StringComparison.OrdinalIgnoreCase));
foreach (var row in rows)
{
if (row == null) continue;
// 只校验提交的字段
var submitColumns = detailColumns
.Where(c => row.ContainsKey(c.ColumnName))
.ToList();
if (submitColumns.Count == 0) continue;
// 忽略外键必填:将外键列视为可空
if (foreignCol != null && submitColumns.Any(c => c.ColumnName.Equals(foreignCol.ColumnName, StringComparison.OrdinalIgnoreCase)))
{
submitColumns = submitColumns
.Select(c =>
{
if (!c.ColumnName.Equals(foreignCol.ColumnName, StringComparison.OrdinalIgnoreCase))
{
return c;
}
return new TableColumnField
{
ColumnName = c.ColumnName,
ColumnCnName = c.ColumnCnName,
ColumnType = c.ColumnType,
TableName = c.TableName,
IsDisplay = c.IsDisplay,
ReferenceField = c.ReferenceField,
Maxlength = c.Maxlength,
IsKey = c.IsKey,
IsNull = 1
};
})
.ToList();
}
string msg = provider.ValidateColumns(row, submitColumns);
if (!string.IsNullOrEmpty(msg)) return msg;
}
return null;
}
}
}