Initial_commit_SecMPS_v2
This commit is contained in:
138
api_sqlsugar/VolPro.Core/Generic/GenericBaseController.cs
Normal file
138
api_sqlsugar/VolPro.Core/Generic/GenericBaseController.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using System.Linq;
|
||||
using VolPro.Core.Controllers.Basic;
|
||||
using VolPro.Core.Filters;
|
||||
using VolPro.Core.UserManager;
|
||||
using VolPro.Entity.DomainModels;
|
||||
using VolPro.Core.ManageUser;
|
||||
using VolPro.Core.Enums;
|
||||
using VolPro.Core.Utilities;
|
||||
using Microsoft.AspNetCore.Mvc.Authorization;
|
||||
using System.Reflection;
|
||||
using VolPro.Core.Extensions;
|
||||
|
||||
namespace VolPro.Core.Generic
|
||||
{
|
||||
[JWTAuthorize, ApiController]
|
||||
public class GenericBaseController : VolController
|
||||
{
|
||||
public GenericBaseController() { }
|
||||
public override void OnActionExecuting(ActionExecutingContext context)
|
||||
{
|
||||
GenericTableAsyncLocal.Clear();
|
||||
string TableName = null;
|
||||
if (context.ActionArguments?.Count > 0)
|
||||
{
|
||||
foreach (var argument in context.ActionArguments.Values.Where(argument => argument != null))
|
||||
{
|
||||
|
||||
var argumentType = argument.GetType();
|
||||
if (argumentType == typeof(PageDataOptions)|| argumentType == typeof(SaveModel))
|
||||
{
|
||||
var tableNameProperty = argumentType.GetProperty("TableName");
|
||||
if (tableNameProperty != null)
|
||||
{
|
||||
TableName = tableNameProperty.GetValue(argument)?.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WebResponseContent webResponse = new();
|
||||
if (string.IsNullOrEmpty(TableName))
|
||||
{
|
||||
TableName = HttpContext.Request.Query["tableName"];
|
||||
}
|
||||
if (string.IsNullOrEmpty(TableName))
|
||||
{
|
||||
context.Result = GetResult(context, "缺少参数table,请检查代码生成器生器Sys_TableInfo、Sys_TableColumn是否有当前表配置,或菜单设置的表名是否正确");
|
||||
return;
|
||||
}
|
||||
var list = TableColumnContext.TableInfo
|
||||
.Where(x => x.TableName == TableName).ToList();
|
||||
if (list.Count == 0)
|
||||
{
|
||||
context.Result = GetResult(context, $"未找到表【{TableName}】 配置信息,请检查代码生成器配置是否存在当前表");
|
||||
context.Result = Json(webResponse);
|
||||
return;
|
||||
}
|
||||
if (list.Count > 1)
|
||||
{
|
||||
context.Result = GetResult(context, $"表【{TableName}】 存在多个配置信息,请检查代码生成器配置是否重复");
|
||||
return;
|
||||
}
|
||||
|
||||
GenericTableAsyncLocal.CurrentTableName = TableName;
|
||||
if (context.Filters.Any(item => item is IAllowAnonymousFilter))
|
||||
{
|
||||
base.OnActionExecuting(context);
|
||||
return;
|
||||
}
|
||||
if (UserContext.Current.IsSuperAdmin)
|
||||
{
|
||||
base.OnActionExecuting(context);
|
||||
return;
|
||||
}
|
||||
|
||||
string[] currentActionPermissionNames = [];
|
||||
if (!(context.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor))
|
||||
{
|
||||
base.OnActionExecuting(context);
|
||||
return;
|
||||
}
|
||||
CustomAttributeData attrData = controllerActionDescriptor.MethodInfo
|
||||
.CustomAttributes
|
||||
.FirstOrDefault(a => a.AttributeType == typeof(ApiActionPermissionAttribute))
|
||||
?? controllerActionDescriptor.ControllerTypeInfo
|
||||
.CustomAttributes
|
||||
.FirstOrDefault(a => a.AttributeType == typeof(ApiActionPermissionAttribute));
|
||||
|
||||
if (attrData == null)
|
||||
{
|
||||
base.OnActionExecuting(context);
|
||||
return;
|
||||
}
|
||||
ActionPermissionOptions currentActionPermission = default;
|
||||
foreach (var arg in attrData.ConstructorArguments)
|
||||
{
|
||||
if (arg.ArgumentType == typeof(ActionPermissionOptions) && arg.Value != null)
|
||||
{
|
||||
currentActionPermission = (ActionPermissionOptions)arg.Value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (Equals(currentActionPermission, default(ActionPermissionOptions)))
|
||||
{
|
||||
base.OnActionExecuting(context);
|
||||
return;
|
||||
}
|
||||
//ActionPermissionFilter.cs中统一验证权限
|
||||
//var names = new List<string>();
|
||||
//foreach (ActionPermissionOptions option in Enum.GetValues(typeof(ActionPermissionOptions)))
|
||||
//{
|
||||
// if (option == 0) continue;
|
||||
// if (currentActionPermission.HasFlag(option))
|
||||
// {
|
||||
// names.Add(option.ToString());
|
||||
// }
|
||||
//}
|
||||
//currentActionPermissionNames = names.ToArray();
|
||||
//var hasActionAuth = UserContext.Current.Permissions
|
||||
// .Where(x => x.TableName == TableName.ToLower())
|
||||
// .Any(c => c.UserAuthArr != null && currentActionPermissionNames.Any(action => c.UserAuthArr.Contains(action)));
|
||||
|
||||
//if (!hasActionAuth)
|
||||
//{
|
||||
// context.Result = GetResult(context, "没有权限操作");
|
||||
// return;
|
||||
//}
|
||||
base.OnActionExecuting(context);
|
||||
}
|
||||
private IActionResult GetResult(ActionExecutingContext context, string message)
|
||||
{
|
||||
return Json(new { status = false, message });
|
||||
}
|
||||
}
|
||||
}
|
||||
164
api_sqlsugar/VolPro.Core/Generic/GenericBaseService.cs
Normal file
164
api_sqlsugar/VolPro.Core/Generic/GenericBaseService.cs
Normal file
@@ -0,0 +1,164 @@
|
||||
using Dapper;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Internal;
|
||||
using SqlSugar;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using VolPro.Core.Controllers.Basic;
|
||||
using VolPro.Core.Dapper;
|
||||
using VolPro.Core.DBManager;
|
||||
using VolPro.Core.DbSqlSugar;
|
||||
using VolPro.Core.EFDbContext;
|
||||
using VolPro.Core.Filters;
|
||||
using VolPro.Core.UserManager;
|
||||
using VolPro.Core.Utilities;
|
||||
|
||||
namespace VolPro.Core.Generic
|
||||
{
|
||||
|
||||
public class GenericBaseService
|
||||
{
|
||||
public WebResponseContent WebResponse { get; set; }
|
||||
public GenericBaseService()
|
||||
{
|
||||
WebResponse = new WebResponseContent();
|
||||
}
|
||||
|
||||
public int DbParamsCount
|
||||
{
|
||||
get
|
||||
{
|
||||
return DbRelativeCache.GetDbType(TableInfo.DBServer) == "MsSql" ? 2000 : 100000;
|
||||
}
|
||||
}
|
||||
private UserManager.TableInfo _tableInfo { get; set; }
|
||||
public UserManager.TableInfo TableInfo
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_tableInfo != null) return _tableInfo;
|
||||
_tableInfo = TableColumnContext.TableInfo
|
||||
.Where(x => x.TableName == GenericTableAsyncLocal.CurrentTableName)
|
||||
.FirstOrDefault();
|
||||
if (_tableInfo == null) throw new Exception($" 未找到表【{GenericTableAsyncLocal.CurrentTableName}】 配置信息");
|
||||
if (string.IsNullOrEmpty(_tableInfo.DBServer))
|
||||
{
|
||||
_tableInfo.DBServer = typeof(SysDbContext).Name;
|
||||
}
|
||||
return _tableInfo;
|
||||
}
|
||||
}
|
||||
|
||||
public UserManager.TableInfo GetDetailTableInfo(string tableName = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(TableInfo.DetailName))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
//主从表
|
||||
if (string.IsNullOrEmpty(tableName))
|
||||
{
|
||||
return TableColumnContext.TableInfo.Where(x => x.TableName == TableInfo.DetailName).FirstOrDefault();
|
||||
}
|
||||
//一对多
|
||||
return TableColumnContext.TableInfo.Where(x => x.TableName == tableName).FirstOrDefault();
|
||||
}
|
||||
|
||||
public UserManager.TableInfo GetTableInfo(string tableName = null)
|
||||
{
|
||||
return TableColumnContext.TableInfo.Where(x => x.TableName == tableName).FirstOrDefault();
|
||||
}
|
||||
private BaseDbContext _dbContext { get; set; }
|
||||
public BaseDbContext DbContext
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_dbContext != null) return _dbContext;
|
||||
string dbServer = _tableInfo.DBServer;
|
||||
var type = DbRelativeCache.GetDbContextType(dbServer);
|
||||
object dbObj = HttpContext.Current.RequestServices.GetService(type);
|
||||
_dbContext = (BaseDbContext)dbObj;
|
||||
return _dbContext;
|
||||
}
|
||||
}
|
||||
|
||||
private List<TableColumnField> _columns { get; set; }
|
||||
public List<TableColumnField> Columns
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_columns != null) return _columns;
|
||||
_columns = TableColumnContext.Data
|
||||
.Where(x => x.TableName == GenericTableAsyncLocal.CurrentTableName)
|
||||
.ToList();
|
||||
return _columns;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实际表字段
|
||||
/// </summary>
|
||||
private List<TableColumnField> _tableColumns { get; set; }
|
||||
public List<TableColumnField> TableColumns
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_tableColumns != null) return _tableColumns;
|
||||
_tableColumns = Columns.Where(x => x.ReferenceField == 0).ToList();
|
||||
return _tableColumns;
|
||||
}
|
||||
}
|
||||
|
||||
public List<TableColumnField> GetTableColumns(string table)
|
||||
{
|
||||
return TableColumnContext.Data
|
||||
.Where(x => x.TableName == table)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private ISqlSugarClient _genericDbContext { get; set; }
|
||||
public ISqlSugarClient GenericDbContext
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_genericDbContext == null)
|
||||
{
|
||||
_genericDbContext = DbManger.GetSqlSugarClient(TableInfo.DBServer);
|
||||
}
|
||||
return _genericDbContext;
|
||||
}
|
||||
}
|
||||
public async Task<List<dynamic>> QueryListAsync(string sql, List<SugarParameter> parameters)
|
||||
{
|
||||
return await GenericDbContext.Ado.SqlQueryAsync<dynamic>(sql, parameters);
|
||||
}
|
||||
public async Task<object> ExecuteScalarAsync(string sql, List<SugarParameter> parameters)
|
||||
{
|
||||
return await GenericDbContext.Ado.GetScalarAsync(sql, parameters);
|
||||
}
|
||||
|
||||
public async Task<int> ExcuteNonQueryAsync(string sql, object parameters)
|
||||
{
|
||||
return await GenericDbContext.Ado.ExecuteCommandAsync(sql, parameters);
|
||||
}
|
||||
|
||||
public async Task BeginTranAsync()
|
||||
{
|
||||
await GenericDbContext.Ado.BeginTranAsync();
|
||||
}
|
||||
|
||||
public async Task CommitTranAsync()
|
||||
{
|
||||
await GenericDbContext.Ado.CommitTranAsync();
|
||||
}
|
||||
|
||||
public async Task RollbackTranAsync()
|
||||
{
|
||||
await GenericDbContext.Ado.RollbackTranAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
928
api_sqlsugar/VolPro.Core/Generic/GenericDbProviderBase.cs
Normal file
928
api_sqlsugar/VolPro.Core/Generic/GenericDbProviderBase.cs
Normal file
@@ -0,0 +1,928 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using OfficeOpenXml.FormulaParsing.Excel.Functions.Math;
|
||||
using OfficeOpenXml.FormulaParsing.Excel.Functions.Text;
|
||||
using SqlSugar;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using VolPro.Core.DBManager;
|
||||
using VolPro.Core.Enums;
|
||||
using VolPro.Core.Extensions;
|
||||
using VolPro.Core.Services;
|
||||
using VolPro.Core.Tenancy;
|
||||
using VolPro.Core.UserManager;
|
||||
using VolPro.Core.Utilities;
|
||||
using VolPro.Entity.DomainModels;
|
||||
|
||||
namespace VolPro.Core.Generic
|
||||
{
|
||||
/// <summary>
|
||||
/// 通用数据库 CRUD 抽象基类,封装三种数据库公共逻辑
|
||||
/// </summary>
|
||||
public abstract class GenericDbProviderBase : GenericBaseService, IGenericDbProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// 不同数据库的左右引号符号(MySql: ``, PgSql: "", SqlServer: [])
|
||||
/// </summary>
|
||||
protected abstract string LeftQuote { get; }
|
||||
protected abstract string RightQuote { get; }
|
||||
|
||||
protected GenericDbProviderBase() : base()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual async Task<PageGridData<object>> GetPageDataAsync(PageDataOptions options, bool isDetail = false)
|
||||
{
|
||||
//租户过滤
|
||||
options = options ?? new PageDataOptions();
|
||||
if (!string.IsNullOrEmpty(options.Wheres) && (options.Filter == null || options.Filter.Count == 0))
|
||||
{
|
||||
options.Filter = options.Wheres.DeserializeObject<List<SearchParameters>>();
|
||||
}
|
||||
//获取逻辑删除过滤
|
||||
string logicDelField = this.GetLogicDelField(Columns);
|
||||
if (logicDelField != null)
|
||||
{
|
||||
options.Filter.Add(new SearchParameters() { Name = logicDelField, Value = "0" });
|
||||
}
|
||||
string dbTableName = string.IsNullOrEmpty(TableInfo.TableTrueName)
|
||||
? TableInfo.TableName
|
||||
: TableInfo.TableTrueName;
|
||||
|
||||
string selectColumns = string.Join(",", Columns.Select(c => $"{LeftQuote}{c.ColumnName}{RightQuote}"));
|
||||
string baseSql = string.IsNullOrEmpty(TableInfo.DbSql)
|
||||
? $"SELECT {selectColumns} FROM {LeftQuote}{dbTableName}{RightQuote}"
|
||||
: $"SELECT * from ({TableInfo.DbSql}) TDbSQL ";
|
||||
|
||||
var whereList = new List<string>();
|
||||
// SqlSugar 参数集合
|
||||
var parameters = new List<SugarParameter>();
|
||||
//执行自定义sql
|
||||
baseSql = dbTableName.GetSearchSqlQuery(baseSql, options.Filter, parameters) ?? baseSql;
|
||||
//过滤租户数据权限
|
||||
baseSql = dbTableName.CreateTenancySqlFilter(baseSql, options.Filter, parameters);
|
||||
|
||||
this.BuildWhere(options.Filter, Columns, whereList, parameters, LeftQuote, RightQuote);
|
||||
|
||||
var baseWithWhere = new StringBuilder();
|
||||
baseWithWhere.Append(baseSql);
|
||||
if (whereList.Count > 0)
|
||||
{
|
||||
baseWithWhere.Append(" WHERE ").Append(string.Join(" AND ", whereList));
|
||||
}
|
||||
|
||||
string sortField = options.Sort;
|
||||
string sortOrder = string.IsNullOrEmpty(options.Order) ? "DESC" : options.Order.ToUpper();
|
||||
if (string.IsNullOrEmpty(sortField))
|
||||
{
|
||||
sortField = Columns.FirstOrDefault(c => c.IsKey == 1)?.ColumnName
|
||||
?? Columns.First().ColumnName;
|
||||
}
|
||||
string orderBy = $"ORDER BY {LeftQuote}{sortField}{RightQuote} {(sortOrder == "ASC" ? "ASC" : "DESC")}";
|
||||
|
||||
|
||||
string countSql = $"SELECT COUNT(1) FROM ({baseWithWhere}) T";
|
||||
//导出
|
||||
if (options.Export)
|
||||
{
|
||||
options.Rows = 200000;
|
||||
}
|
||||
int page = options.Page <= 0 ? 1 : options.Page;
|
||||
int rows = options.Rows <= 0 ? 30 : options.Rows;
|
||||
|
||||
string pageSql = BuildPageSql(baseWithWhere.ToString(), selectColumns, orderBy, page, rows);
|
||||
|
||||
return new PageGridData<object>()
|
||||
{
|
||||
rows = await QueryListAsync(pageSql, parameters),
|
||||
total = options.Export ? 0 : (await ExecuteScalarAsync(countSql, parameters)).GetInt()
|
||||
};
|
||||
}
|
||||
public virtual async Task<PageGridData<object>> GetDetailPageAsync(PageDataOptions options)
|
||||
{
|
||||
return await GetPageDataAsync(options, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造分页 SQL,默认使用 LIMIT/OFFSET(MySql、PgSql)
|
||||
/// </summary>
|
||||
protected virtual string BuildPageSql(string baseWithWhereSql, string selectColumns, string orderBy, int page, int rows)
|
||||
{
|
||||
int offset = (page - 1) * rows;
|
||||
return $"{baseWithWhereSql} {orderBy} LIMIT {rows} OFFSET {offset}";
|
||||
}
|
||||
/// <summary>
|
||||
/// 添加数据Add
|
||||
/// </summary>
|
||||
/// <param name="saveModel"></param>
|
||||
/// <returns></returns>
|
||||
public virtual async Task<WebResponseContent> AddAsync(SaveModel saveModel)
|
||||
{
|
||||
var response = WebResponseContent.Instance;
|
||||
|
||||
if (saveModel == null || saveModel.MainData == null || saveModel.MainData.Count == 0)
|
||||
{
|
||||
return response.Error("提交数据为空");
|
||||
}
|
||||
|
||||
string dbTableName = string.IsNullOrEmpty(TableInfo.TableTrueName)
|
||||
? TableInfo.TableName
|
||||
: TableInfo.TableTrueName;
|
||||
|
||||
var tableColumns = TableColumns;
|
||||
if (tableColumns == null || tableColumns.Count == 0)
|
||||
{
|
||||
return response.Error("未找到表字段配置信息");
|
||||
}
|
||||
|
||||
var keyColumn = tableColumns.FirstOrDefault(c => c.IsKey == 1);
|
||||
|
||||
// 先设置创建人/创建时间默认值
|
||||
saveModel.MainData.SetCreateDefaultVal();
|
||||
|
||||
//生成自增单据号
|
||||
IdentitySqlCode.CreateCode(saveModel.MainData, TableInfo.TableName, tableColumns, LeftQuote, RightQuote);
|
||||
|
||||
// 设置逻辑删除字段0
|
||||
this.SetLogicDelDefault(saveModel.MainData, tableColumns)
|
||||
//审批字段默认值为0
|
||||
.SetAuditDefault(saveModel.MainData, tableColumns)
|
||||
// 按主键类型自动生成主键值
|
||||
.SetPrimaryKey(saveModel.MainData, keyColumn);
|
||||
|
||||
// 根据字段配置校验主表必填、长度、类型
|
||||
string validMsg = this.ValidateColumns(saveModel.MainData, tableColumns);
|
||||
if (!string.IsNullOrEmpty(validMsg))
|
||||
{
|
||||
return response.Error(validMsg);
|
||||
}
|
||||
|
||||
// 在主表校验通过后,提前校验所有明细表(忽略外键字段的必填)
|
||||
validMsg = this.ValidateAllDetails(saveModel, tableColumns, keyColumn);
|
||||
if (!string.IsNullOrEmpty(validMsg))
|
||||
{
|
||||
return response.Error(validMsg);
|
||||
}
|
||||
|
||||
bool keyIsIdentity = this.IsIdentity(keyColumn.ColumnType);
|
||||
// 需要忽略的字段(修改人、修改时间等)
|
||||
var ignoreFields = this.GetModifyFieldsToIgnore();
|
||||
var fieldNames = new List<string>();
|
||||
var paramNames = new List<string>();
|
||||
var parameters = new List<SugarParameter>();
|
||||
|
||||
// 只写入 TableColumns 中配置的真实表字段,且排除需要忽略的字段
|
||||
foreach (var col in tableColumns)
|
||||
{
|
||||
if (ignoreFields.Contains(col.ColumnName)) continue;
|
||||
if (!saveModel.MainData.TryGetValue(col.ColumnName, out object value)) continue;
|
||||
//自增不写入主键
|
||||
if (keyColumn.ColumnName == col.ColumnName && keyIsIdentity) continue;
|
||||
|
||||
// 对于允许为 null 的字段,需要支持显式写入 NULL。
|
||||
// Dapper/Npgsql 不接受 System.DBNull 类型作为参数实体成员,这里统一将 DBNull 转成 null。
|
||||
if (value is DBNull)
|
||||
{
|
||||
value = null;
|
||||
}
|
||||
value = CoerceDapperParam(value, col);
|
||||
fieldNames.Add($"{LeftQuote}{col.ColumnName}{RightQuote}");
|
||||
paramNames.Add("@" + col.ColumnName);
|
||||
|
||||
var par = new SugarParameter("@" + col.ColumnName, value);
|
||||
var dbType = GenericDbValidationExtensions.EffectiveDapperDbType(value, col);
|
||||
if (dbType != null)
|
||||
{
|
||||
par.DbType = (System.Data.DbType)dbType;
|
||||
}
|
||||
parameters.Add(par);
|
||||
}
|
||||
|
||||
if (fieldNames.Count == 0) return response.Error("提交字段与表配置不匹配");
|
||||
|
||||
string insertSql = $"INSERT INTO {LeftQuote}{dbTableName}{RightQuote} ({string.Join(",", fieldNames)}) VALUES ({string.Join(",", paramNames)})";
|
||||
|
||||
try
|
||||
{
|
||||
await BeginTranAsync();
|
||||
if (keyIsIdentity)
|
||||
{
|
||||
saveModel.MainData.Remove(keyColumn.ColumnName);
|
||||
string identitySql = BuildIdentitySql(keyColumn);
|
||||
object newId = await ExecuteInsertWithIdentityAsync(insertSql, identitySql, parameters, keyColumn);
|
||||
saveModel.MainData[keyColumn.ColumnName] = long.Parse(newId.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
await ExcuteNonQueryAsync(insertSql, parameters);
|
||||
}
|
||||
|
||||
// 主表插入成功后,处理一对多明细
|
||||
await InsertDetailsAsync(saveModel, keyColumn);
|
||||
|
||||
Logger.OK(LoggerType.Add, saveModel.Serialize());
|
||||
response.OK(ResponseType.SaveSuccess);
|
||||
response.Data = saveModel.MainData;
|
||||
await CommitTranAsync();
|
||||
return response;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await RollbackTranAsync();
|
||||
throw new Exception($"add新建异常,table:{TableInfo.TableName},参数:{saveModel.Serialize()},异常信息:{ex.Message + ex.StackTrace}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 编辑
|
||||
/// </summary>
|
||||
/// <param name="saveModel"></param>
|
||||
/// <returns></returns>
|
||||
public virtual async Task<WebResponseContent> UpdateAsync(SaveModel saveModel)
|
||||
{
|
||||
var response = WebResponseContent.Instance;
|
||||
|
||||
string dbTableName = string.IsNullOrEmpty(TableInfo.TableTrueName)
|
||||
? TableInfo.TableName
|
||||
: TableInfo.TableTrueName;
|
||||
|
||||
var keyColumn = TableColumns.FirstOrDefault(c => c.IsKey == 1);
|
||||
if (keyColumn == null)
|
||||
{
|
||||
return response.Error("未配置主键,不能编辑");
|
||||
}
|
||||
var columns = TableColumns.Where(x => x.ReferenceField == 0).ToList();
|
||||
|
||||
// 1、saveModel.MainData 取出主键字段,如果没有值,提示缺少主键字段参数
|
||||
if (!saveModel.MainData.TryGetValue(keyColumn.ColumnName, out object keyVal) || string.IsNullOrEmpty(keyVal.ToString()))
|
||||
{
|
||||
return response.Error(ResponseType.KeyError);
|
||||
}
|
||||
saveModel.MainData.SetModifyDefaultVal();
|
||||
var data = saveModel.MainData
|
||||
.Where(kv => !kv.Key.Equals(keyColumn.ColumnName, StringComparison.OrdinalIgnoreCase)
|
||||
&& columns.Any(c => c.ColumnName.Equals(kv.Key, StringComparison.OrdinalIgnoreCase)))
|
||||
.ToDictionary(k => k.Key, v => v.Value);
|
||||
|
||||
if (data.Count == 0)
|
||||
{
|
||||
return response.Error("没有需要更新的字段");
|
||||
}
|
||||
|
||||
// 2、根据 TableColumns 中的字段校验 MainData 中【提交的字段】:
|
||||
// IsNull、Maxlength、ColumnType(只针对 data 里存在的字段)
|
||||
var submitMainColumns = columns
|
||||
.Where(c => data.ContainsKey(c.ColumnName))
|
||||
.ToList();
|
||||
string validMsg = this.ValidateColumns(saveModel.MainData, submitMainColumns);
|
||||
if (!string.IsNullOrEmpty(validMsg))
|
||||
{
|
||||
return response.Error(validMsg);
|
||||
}
|
||||
|
||||
// 3、明细表数据校验(规则同 Add,但编辑时只校验提交的字段,并忽略外键必填)
|
||||
validMsg = this.ValidateAllDetailsForUpdate(saveModel, keyColumn);
|
||||
if (!string.IsNullOrEmpty(validMsg))
|
||||
{
|
||||
return response.Error(validMsg);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var setList = new List<string>();
|
||||
var parameters = new List<SugarParameter>();
|
||||
// 5、6:忽略 ReferenceField=1 的字段已经在 columns 过滤;这里再忽略 ModifyMember 对应字段
|
||||
var ignoreFields = this.GetAddFieldsToIgnore();
|
||||
|
||||
foreach (var kv in data)
|
||||
{
|
||||
if (ignoreFields.Contains(kv.Key)) continue;
|
||||
|
||||
// 校验阶段可能已把 MainData 中的字符串转为 Guid 等;data 为校验前快照,优先取 MainData
|
||||
if (!saveModel.MainData.TryGetValue(kv.Key, out object value))
|
||||
{
|
||||
value = kv.Value;
|
||||
}
|
||||
if (value is DBNull)
|
||||
{
|
||||
value = null;
|
||||
}
|
||||
var col = columns.FirstOrDefault(c => c.ColumnName.Equals(kv.Key, StringComparison.OrdinalIgnoreCase));
|
||||
if (col == null) continue;
|
||||
|
||||
value = CoerceDapperParam(value, col);
|
||||
|
||||
string paramName = kv.Key;
|
||||
setList.Add($"{LeftQuote}{kv.Key}{RightQuote} = @{paramName}");
|
||||
//parameters.Add(new SugarParameter("@" + paramName, value));
|
||||
var par = new SugarParameter("@" + col.ColumnName, value);
|
||||
var dbType = GenericDbValidationExtensions.EffectiveDapperDbType(value, col);
|
||||
if (dbType != null)
|
||||
{
|
||||
par.DbType = (System.Data.DbType)dbType;
|
||||
}
|
||||
parameters.Add(par);
|
||||
}
|
||||
|
||||
object pkParam = CoerceDapperParam(keyVal, keyColumn) ?? DBNull.Value;
|
||||
var parPk = new SugarParameter("@pk", pkParam);
|
||||
var dbTypePk = GenericDbValidationExtensions.EffectiveDapperDbType(pkParam, keyColumn);
|
||||
if (dbTypePk != null)
|
||||
{
|
||||
parPk.DbType = (System.Data.DbType)dbTypePk;
|
||||
}
|
||||
parameters.Add(parPk);
|
||||
|
||||
await BeginTranAsync();
|
||||
|
||||
string sql = $"UPDATE {LeftQuote}{dbTableName}{RightQuote} SET {string.Join(",", setList)} WHERE {LeftQuote}{keyColumn.ColumnName}{RightQuote} = @pk";
|
||||
|
||||
int count = await ExcuteNonQueryAsync(sql, parameters);
|
||||
if (count <= 0)
|
||||
{
|
||||
await RollbackTranAsync();
|
||||
return response.Error("未找到更新的数据");
|
||||
}
|
||||
|
||||
// 4、根据明细主键是否有值区分新建或编辑,分别执行插入或更新
|
||||
response = await UpdateDetailsAsync(saveModel, keyColumn, keyVal);
|
||||
if (!response.Status)
|
||||
{
|
||||
await RollbackTranAsync();
|
||||
return response;
|
||||
}
|
||||
await CommitTranAsync();
|
||||
response.OK(ResponseType.EidtSuccess);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await RollbackTranAsync();
|
||||
throw new Exception($"编辑异常,table:{TableInfo.TableName},参数:{saveModel.Serialize()},异常信息:{ex.Message + ex.StackTrace}");
|
||||
}
|
||||
return response;
|
||||
}
|
||||
public virtual async Task<WebResponseContent> DelAsync(List<object> keys, bool delDetail = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
await BeginTranAsync();
|
||||
var res = await Del(keys, TableInfo.TableTrueName);
|
||||
if (delDetail && !string.IsNullOrEmpty(TableInfo.DetailName))
|
||||
{
|
||||
var tables = TableInfo.DetailName.Split(",");
|
||||
foreach (var table in tables)
|
||||
{
|
||||
res = await Del(keys, table);
|
||||
}
|
||||
}
|
||||
await CommitTranAsync();
|
||||
return WebResponse.OK("删除成功".Translator());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await RollbackTranAsync();
|
||||
throw new Exception($"表删除异常,table:{TableInfo.TableName},异常信息:{ex.Message + ex.StackTrace}");
|
||||
}
|
||||
}
|
||||
private async Task<WebResponseContent> Del(List<object> keys, string table, TableColumnField keyColumn = null)
|
||||
{
|
||||
if (keys == null || keys.Count == 0) return WebResponse.OK("无数据");
|
||||
string dbTableName = table;
|
||||
|
||||
keyColumn = keyColumn ?? GetTableColumns(table).FirstOrDefault(c => c.IsKey == 1);
|
||||
if (keyColumn == null) return WebResponse.Error("未配置主键,不能删除");
|
||||
keys = keys.Select(k => CoerceDapperParam(k, keyColumn)).ToList();
|
||||
var parameters = new List<SugarParameter>();
|
||||
string sql = $"DELETE FROM {LeftQuote}{dbTableName}{RightQuote} WHERE {LeftQuote}{keyColumn.ColumnName}{RightQuote} IN (@keys)";
|
||||
var parKeys = new SugarParameter("@keys", keys);
|
||||
var dbTypeKeys = keys != null && keys.Count > 0
|
||||
? GenericDbValidationExtensions.EffectiveDapperDbType(keys[0], keyColumn)
|
||||
: keyColumn?.GetDbType();
|
||||
if (dbTypeKeys != null)
|
||||
{
|
||||
parKeys.DbType = (System.Data.DbType)dbTypeKeys;
|
||||
}
|
||||
parameters.Add(parKeys);
|
||||
int count = await ExcuteNonQueryAsync(sql, parameters);
|
||||
WebResponse.OK(ResponseType.DelSuccess);
|
||||
return WebResponse;
|
||||
}
|
||||
|
||||
public virtual async Task<WebResponseContent> UploadAsync(List<IFormFile> files)
|
||||
{
|
||||
if (files == null || files.Count == 0) return WebResponse.Error("请上传文件");
|
||||
string date = DateTime.Now.ToString("yyyMMddHHmmsss");
|
||||
string filePath = $"Upload/Generic/{TableInfo.TableName}/{date}/";
|
||||
string fullPath = filePath.MapPath(true);
|
||||
if (!Directory.Exists(fullPath)) Directory.CreateDirectory(fullPath);
|
||||
for (int i = 0; i < files.Count; i++)
|
||||
{
|
||||
string fileName = Utilities.HttpContext.Current.Request("fileName");
|
||||
if (string.IsNullOrEmpty(fileName))
|
||||
{
|
||||
fileName = files[i].FileName;
|
||||
}
|
||||
using var stream = new FileStream(fullPath + fileName, FileMode.Create);
|
||||
await files[i].CopyToAsync(stream);
|
||||
}
|
||||
return WebResponse.OK("文件上传成功".Translator(), filePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 下载导入Excel模板(基于当前表配置动态生成)
|
||||
/// </summary>
|
||||
/// <returns>Excel 文件字节数组</returns>
|
||||
public byte[] DownLoadTemplateAsync()
|
||||
{
|
||||
var ignoreFields = this.GetAddAndModifyFieldsToIgnore();
|
||||
var bytes = GenericExcelTemplateHelper.BuildTemplateBytes(TableInfo.ColumnCNName, TableColumns.Where(x => !ignoreFields.Contains(x.ColumnName)).ToList());
|
||||
return bytes;
|
||||
}
|
||||
/// <summary>
|
||||
/// 导入表数据Excel
|
||||
/// </summary>
|
||||
/// <param name="fileInput"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<WebResponseContent> ImportAsync(List<IFormFile> fileInput)
|
||||
{
|
||||
if (fileInput == null || fileInput.Count == 0)
|
||||
{
|
||||
return WebResponse.Error("请选择上传的文件".Translator());
|
||||
}
|
||||
var ignoreFields = this.GetAddAndModifyFieldsToIgnore();
|
||||
// TableColumns.Where(x => !ignoreFields.Contains(x.ColumnName)).ToList()
|
||||
var formFile = fileInput[0];
|
||||
List<Dictionary<string, object>> rows = null;
|
||||
using (var stream = formFile.OpenReadStream())
|
||||
{
|
||||
var resp = GenericExcelImportHelper.ReadRowsByCellOptions(TableInfo.TableName, stream, ignoreFields: ignoreFields);
|
||||
if (!resp.Status) return resp;
|
||||
|
||||
rows = ((List<Dictionary<string, object>>)resp.Data).Where(x => x.Count > 0).ToList();
|
||||
}
|
||||
if (rows == null || rows.Count == 0)
|
||||
{
|
||||
return WebResponse.Error("未读取到导入数据".Translator());
|
||||
}
|
||||
var keyColumn = TableColumns.FirstOrDefault(c => c.IsKey == 1);
|
||||
string msg = this.ValidateDetailList(TableInfo.DetailName, rows, keyColumn);
|
||||
if (!string.IsNullOrEmpty(msg))
|
||||
{
|
||||
return WebResponse.Error(msg);
|
||||
}
|
||||
foreach (var row in rows)
|
||||
{
|
||||
IdentitySqlCode.CreateCode(row, TableInfo.TableName, TableColumns, LeftQuote, RightQuote);
|
||||
}
|
||||
//明细表导入
|
||||
string mainId = Utilities.HttpContext.Current.Request.Query["id"];
|
||||
if (!string.IsNullOrEmpty(mainId))
|
||||
{
|
||||
string mainTable = Utilities.HttpContext.Current.Request.Query["mainTable"];
|
||||
var keyName = GetTableInfo(mainTable)?.MainKeyField ??
|
||||
GetTableColumns(mainTable).Where(x => x.IsKey == 1).Select(s => s.ColumnName).FirstOrDefault();
|
||||
if (string.IsNullOrEmpty(keyName))
|
||||
{
|
||||
return WebResponse.Error("未找到明细表的主表配置信息,请检查代码生成器是否有主表配置".Translator());
|
||||
}
|
||||
foreach (var item in rows)
|
||||
{
|
||||
item[keyName] = mainId;
|
||||
}
|
||||
}
|
||||
await InsertDetailListAsync(TableInfo.TableName, rows, null, null);
|
||||
return WebResponse.OK("导入成功,共{$ts}条".TranslatorFormat(rows.Count), new { rows.Count });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导出文件(参照 ServiceBase.Export,实现字典数据源转换、字段显示等)
|
||||
/// </summary>
|
||||
/// <param name="loadData"></param>
|
||||
/// <returns>Excel 文件字节数组</returns>
|
||||
public async Task<byte[]> ExportAsync(PageDataOptions loadData)
|
||||
{
|
||||
loadData.Export = true;
|
||||
var pageData = await GetPageDataAsync(loadData);
|
||||
var dictRows = new List<Dictionary<string, object>>();
|
||||
|
||||
foreach (var item in pageData.rows)
|
||||
{
|
||||
var entry = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
||||
if (item is IDictionary<string, object> genericDict)
|
||||
{
|
||||
foreach (var kv in genericDict)
|
||||
{
|
||||
entry[kv.Key] = kv.Value;
|
||||
}
|
||||
dictRows.Add(entry);
|
||||
}
|
||||
}
|
||||
var exportFields = loadData.Columns ?? [];
|
||||
var ignoreColumns = new List<string>();
|
||||
var bytes = GenericExcelExportHelper.BuildExportBytes(TableInfo.TableName, dictRows, loadData.Columns ?? [], ignoreColumns);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public async Task<WebResponseContent> AuditAsync(object[] id, int? auditStatus, string auditReason)
|
||||
{
|
||||
string table = TableInfo.TableName;
|
||||
await Task.CompletedTask;
|
||||
return null;
|
||||
}
|
||||
|
||||
/// </summary>
|
||||
protected virtual string BuildIdentitySql(TableColumnField keyColumn, bool batch = false)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
protected virtual async Task<object> ExecuteInsertWithIdentityAsync(string insertSql, string identitySql, List<SugarParameter> parameters, TableColumnField keyColumn)
|
||||
{
|
||||
return await ExecuteScalarAsync($"{insertSql} {identitySql}", parameters);
|
||||
}
|
||||
/// <summary>
|
||||
/// 插入一对多明细数据
|
||||
/// </summary>
|
||||
protected virtual async Task InsertDetailsAsync(SaveModel saveModel, TableColumnField mainKeyColumn)
|
||||
{
|
||||
// 主键值
|
||||
object mainKeyValue = saveModel.MainData[mainKeyColumn.ColumnName];
|
||||
// 单明细表 List<Dictionary<string, object>>
|
||||
if (saveModel.DetailData != null && saveModel.DetailData.Count > 0)
|
||||
{
|
||||
await InsertDetailListAsync(TableInfo.DetailName, saveModel.DetailData, mainKeyColumn, mainKeyValue);
|
||||
saveModel.MainData[TableInfo.DetailName] = saveModel.DetailData;
|
||||
}
|
||||
|
||||
// 新结构:多明细 List<DetailInfo>
|
||||
if (saveModel.Details != null && saveModel.Details.Count > 0)
|
||||
{
|
||||
foreach (var item in saveModel.Details)
|
||||
{
|
||||
if (item?.Data == null || item.Data.Count == 0) continue;
|
||||
await InsertDetailListAsync(item.Table, item.Data, mainKeyColumn, mainKeyValue);
|
||||
saveModel.MainData[item.Table] = item.Data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update 时,根据明细主键是否有值区分新建/编辑,分别执行插入或更新
|
||||
/// </summary>
|
||||
private async Task<WebResponseContent> UpdateDetailsAsync(SaveModel saveModel, TableColumnField mainKeyColumn, object mainKeyValue)
|
||||
{
|
||||
// 旧结构:单明细表
|
||||
if (saveModel.DetailData != null && saveModel.DetailData.Count > 0 && !string.IsNullOrEmpty(TableInfo.DetailName))
|
||||
{
|
||||
await UpsertDetailListAsync(TableInfo.DetailName, saveModel.DetailData, mainKeyColumn, mainKeyValue);
|
||||
WebResponse = await Del(saveModel.DelKeys, TableInfo.DetailName);
|
||||
if (!WebResponse.Status)
|
||||
{
|
||||
return WebResponse;
|
||||
}
|
||||
}
|
||||
|
||||
// 新结构:多明细
|
||||
if (saveModel.Details != null && saveModel.Details.Count > 0)
|
||||
{
|
||||
foreach (var item in saveModel.Details)
|
||||
{
|
||||
if (item?.Data == null || item.Data.Count == 0) continue;
|
||||
await UpsertDetailListAsync(item.Table, item.Data, mainKeyColumn, mainKeyValue);
|
||||
WebResponse = await Del(item.DelKeys, item.Table);
|
||||
if (!WebResponse.Status)
|
||||
{
|
||||
return WebResponse;
|
||||
}
|
||||
}
|
||||
}
|
||||
return WebResponse.OK();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对指定明细表的数据做“有主键则更新,无主键则插入”的操作
|
||||
/// </summary>
|
||||
private async Task UpsertDetailListAsync(string detailTableName, List<Dictionary<string, object>> rows, TableColumnField mainKeyColumn, object mainKeyValue)
|
||||
{
|
||||
if (string.IsNullOrEmpty(detailTableName) || rows == null || rows.Count == 0) return;
|
||||
|
||||
var detailColumns = TableColumnContext.Data
|
||||
.Where(x => x.TableName == detailTableName && x.ReferenceField == 0)
|
||||
.ToList();
|
||||
if (detailColumns == null || detailColumns.Count == 0) return;
|
||||
|
||||
var detailKeyCol = detailColumns.FirstOrDefault(c => c.IsKey == 1);
|
||||
if (detailKeyCol == null) return;
|
||||
|
||||
var foreignCol = detailColumns.FirstOrDefault(c =>
|
||||
c.ColumnName.Equals(mainKeyColumn.ColumnName, StringComparison.OrdinalIgnoreCase)
|
||||
&& string.Equals(c.ColumnType, mainKeyColumn.ColumnType, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var detailTableInfo = TableColumnContext.TableInfo
|
||||
.FirstOrDefault(t => t.TableName == detailTableName);
|
||||
string detailDbTableName = string.IsNullOrEmpty(detailTableInfo?.TableTrueName)
|
||||
? detailTableName
|
||||
: detailTableInfo.TableTrueName;
|
||||
|
||||
var ignoreModifyFields = this.GetModifyFieldsToIgnore();
|
||||
var ignoreAddFields = this.GetAddFieldsToIgnore();
|
||||
bool keyIsIdentity = this.IsIdentity(detailKeyCol.ColumnType);
|
||||
|
||||
// 新增明细行
|
||||
var addRows = new List<Dictionary<string, object>>();
|
||||
|
||||
|
||||
// 编辑明细行批量 UPDATE,控制单次参数数量
|
||||
int maxParams = DbParamsCount;
|
||||
var updateSqlBuilder = new StringBuilder();
|
||||
var updateParameters = new List<SugarParameter>();
|
||||
int currentUpdateParamCount = 0;
|
||||
int updateRowIndex = 0;
|
||||
|
||||
async Task FlushUpdateAsync()
|
||||
{
|
||||
if (updateSqlBuilder.Length == 0) return;
|
||||
await ExcuteNonQueryAsync(updateSqlBuilder.ToString(), updateParameters);
|
||||
updateSqlBuilder.Clear();
|
||||
updateParameters = new List<SugarParameter>();
|
||||
currentUpdateParamCount = 0;
|
||||
updateRowIndex = 0;
|
||||
}
|
||||
|
||||
foreach (var row in rows)
|
||||
{
|
||||
if (row == null) continue;
|
||||
|
||||
bool hasDetailKey = row.TryGetValue(detailKeyCol.ColumnName, out object detailKeyVal)
|
||||
&& detailKeyVal != null
|
||||
&& !string.IsNullOrEmpty(detailKeyVal.ToString())
|
||||
&& !new string[] { "0", Guid.Empty.ToString() }.Contains(detailKeyVal?.ToString());
|
||||
|
||||
// 外键字段
|
||||
if (foreignCol != null)
|
||||
{
|
||||
row[foreignCol.ColumnName] = mainKeyValue;
|
||||
}
|
||||
|
||||
if (!hasDetailKey)
|
||||
{
|
||||
// 新建明细:仅加入待新增列表,真正的插入逻辑统一走 InsertDetailListAsync(与 Add 保持一致)
|
||||
addRows.Add(row);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 编辑明细:只更新提交的字段
|
||||
row.SetModifyDefaultVal();
|
||||
|
||||
var setList = new List<string>();
|
||||
var localParams = new List<(string ParamName, object Value, TableColumnField Col)>();
|
||||
|
||||
foreach (var kv in row)
|
||||
{
|
||||
if (kv.Key.Equals(detailKeyCol.ColumnName, StringComparison.OrdinalIgnoreCase)) continue;
|
||||
if (ignoreAddFields.Contains(kv.Key)) continue;
|
||||
|
||||
var col = detailColumns.FirstOrDefault(c => c.ColumnName.Equals(kv.Key, StringComparison.OrdinalIgnoreCase));
|
||||
if (col == null) continue;
|
||||
|
||||
object value = kv.Value;
|
||||
if (value is DBNull) value = null;
|
||||
|
||||
string baseParamName = kv.Key;
|
||||
string paramName = $"{baseParamName}_u{updateRowIndex}";
|
||||
setList.Add($"{LeftQuote}{kv.Key}{RightQuote} = @{paramName}");
|
||||
localParams.Add((paramName, value, col));
|
||||
}
|
||||
|
||||
if (setList.Count == 0) continue;
|
||||
|
||||
// 预估本行 UPDATE 需要的参数数量(字段数 + 主键)
|
||||
int needed = localParams.Count + 1;
|
||||
if (currentUpdateParamCount + needed > maxParams)
|
||||
{
|
||||
await FlushUpdateAsync();
|
||||
}
|
||||
|
||||
foreach (var (ParamName, Value, Col) in localParams)
|
||||
{
|
||||
object v = CoerceDapperParam(Value, Col);
|
||||
var par = new SugarParameter("@" + ParamName, v);
|
||||
var dbType = GenericDbValidationExtensions.EffectiveDapperDbType(v, Col);
|
||||
if (dbType != null)
|
||||
{
|
||||
par.DbType = (System.Data.DbType)dbType;
|
||||
}
|
||||
updateParameters.Add(par);
|
||||
currentUpdateParamCount++;
|
||||
}
|
||||
|
||||
string pkParamName = $"{detailKeyCol.ColumnName}_pk{updateRowIndex}";
|
||||
object detailPk = CoerceDapperParam(detailKeyVal, detailKeyCol);
|
||||
var parPk = new SugarParameter("@" + pkParamName, detailPk);
|
||||
var dbTypePk = GenericDbValidationExtensions.EffectiveDapperDbType(detailPk, detailKeyCol);
|
||||
if (dbTypePk != null)
|
||||
{
|
||||
parPk.DbType = (System.Data.DbType)dbTypePk;
|
||||
}
|
||||
updateParameters.Add(parPk);
|
||||
currentUpdateParamCount++;
|
||||
|
||||
string updateSql =
|
||||
$"UPDATE {LeftQuote}{detailDbTableName}{RightQuote} SET {string.Join(",", setList)} WHERE {LeftQuote}{detailKeyCol.ColumnName}{RightQuote} = @{pkParamName};";
|
||||
updateSqlBuilder.AppendLine(updateSql);
|
||||
|
||||
updateRowIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
if (addRows.Count > 0)
|
||||
{
|
||||
await InsertDetailListAsync(detailTableName, addRows, mainKeyColumn, mainKeyValue);
|
||||
}
|
||||
|
||||
await FlushUpdateAsync();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 按规则插入某一张明细表的数据列表(批量 SQL,控制单次参数数量)
|
||||
/// </summary>
|
||||
private async Task InsertDetailListAsync(string detailTableName, List<Dictionary<string, object>> rows, TableColumnField mainKeyColumn, object mainKeyValue)
|
||||
{
|
||||
if (string.IsNullOrEmpty(detailTableName) || rows == null || rows.Count == 0) return;
|
||||
|
||||
// 获取明细表字段配置
|
||||
var detailColumns = TableColumnContext.Data
|
||||
.Where(x => x.TableName == detailTableName && x.ReferenceField == 0)
|
||||
.ToList();
|
||||
if (detailColumns == null || detailColumns.Count == 0) return;
|
||||
|
||||
// 明细表主键
|
||||
var detailKeyCol = detailColumns.FirstOrDefault(c => c.IsKey == 1);
|
||||
if (detailKeyCol == null) return;
|
||||
|
||||
// 外键字段:与主表主键同名、同类型
|
||||
TableColumnField foreignCol = null;
|
||||
|
||||
if (mainKeyColumn != null)
|
||||
{
|
||||
foreignCol = detailColumns.FirstOrDefault(c => c.ColumnName == mainKeyColumn.ColumnName);
|
||||
if (foreignCol == null) return;
|
||||
}
|
||||
// 明细表真实库表名
|
||||
var detailTableInfo = TableColumnContext.TableInfo
|
||||
.FirstOrDefault(t => t.TableName == detailTableName);
|
||||
string detailDbTableName = string.IsNullOrEmpty(detailTableInfo?.TableTrueName)
|
||||
? detailTableName
|
||||
: detailTableInfo.TableTrueName;
|
||||
|
||||
// 忽略字段(修改人/修改时间)
|
||||
var ignoreFields = this.GetModifyFieldsToIgnore();
|
||||
bool keyIsIdentity = this.IsIdentity(detailKeyCol.ColumnType);
|
||||
|
||||
// 参与插入的列:自增时排除主键;非自增时包含主键(由代码生成)
|
||||
var insertColumns = detailColumns
|
||||
.Where(col =>
|
||||
{
|
||||
if (ignoreFields.Contains(col.ColumnName)) return false;
|
||||
if (keyIsIdentity && col.ColumnName.Equals(detailKeyCol.ColumnName, StringComparison.OrdinalIgnoreCase))
|
||||
return false;
|
||||
return true;
|
||||
})
|
||||
.ToList();
|
||||
if (insertColumns.Count == 0) return;
|
||||
|
||||
string columnList = string.Join(",", insertColumns.Select(c => $"{LeftQuote}{c.ColumnName}{RightQuote}"));
|
||||
// batch=true 时:自增返回 RETURNING/OUTPUT/;SELECT 等片段,非自增返回 null
|
||||
string identitySqlPart = keyIsIdentity ? BuildIdentitySql(detailKeyCol, true) : string.Empty;
|
||||
bool needReturnIds = !string.IsNullOrWhiteSpace(identitySqlPart);
|
||||
|
||||
int maxParams = DbParamsCount;
|
||||
var parameters = new List<SugarParameter>();
|
||||
var batchRows = new List<Dictionary<string, object>>();
|
||||
var batchRowIndexes = new List<int>();
|
||||
int currentParamCount = 0;
|
||||
int globalRowIndex = 0;
|
||||
|
||||
async Task FlushAsync()
|
||||
{
|
||||
if (batchRows.Count == 0) return;
|
||||
|
||||
var valuesClauses = new List<string>();
|
||||
for (int i = 0; i < batchRows.Count; i++)
|
||||
{
|
||||
int rowIdx = batchRowIndexes[i];
|
||||
var valueParams = insertColumns.Select(c => "@" + $"{c.ColumnName}_{rowIdx}").ToList();
|
||||
valuesClauses.Add($"({string.Join(",", valueParams)})");
|
||||
}
|
||||
|
||||
string sql;
|
||||
//sqlserver批量返回语法OUTPUT
|
||||
if (needReturnIds && identitySqlPart.TrimStart().StartsWith("OUTPUT", StringComparison.OrdinalIgnoreCase))
|
||||
sql = $"INSERT INTO {LeftQuote}{detailDbTableName}{RightQuote} ({columnList}) {identitySqlPart} VALUES {string.Join(",", valuesClauses)};";
|
||||
else
|
||||
sql = $"INSERT INTO {LeftQuote}{detailDbTableName}{RightQuote} ({columnList}) VALUES {string.Join(",", valuesClauses)}{identitySqlPart};";
|
||||
|
||||
if (needReturnIds)
|
||||
{
|
||||
var ids = (await QueryListAsync(sql, parameters))
|
||||
.Serialize()
|
||||
.DeserializeObject<List<Dictionary<string, long>>>()
|
||||
.SelectMany(x => x.Values)
|
||||
.ToList();
|
||||
if (this is GenericMySqlProvider && ids != null && ids.Count == 1 && batchRows.Count > 0)
|
||||
{
|
||||
long firstId = Convert.ToInt64(ids[0]);
|
||||
for (int i = 0; i < batchRows.Count; i++)
|
||||
batchRows[i][detailKeyCol.ColumnName] = firstId + i;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < batchRows.Count && i < ids.Count; i++)
|
||||
batchRows[i][detailKeyCol.ColumnName] = ids[i];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await ExcuteNonQueryAsync(sql, parameters);
|
||||
}
|
||||
|
||||
parameters = new List<SugarParameter>();
|
||||
batchRows.Clear();
|
||||
batchRowIndexes.Clear();
|
||||
currentParamCount = 0;
|
||||
}
|
||||
|
||||
foreach (var row in rows)
|
||||
{
|
||||
if (row == null) continue;
|
||||
if (foreignCol != null)
|
||||
{
|
||||
row[foreignCol.ColumnName] = mainKeyValue;
|
||||
}
|
||||
row.SetCreateDefaultVal();
|
||||
this.SetLogicDelDefault(row, detailColumns).SetAuditDefault(row, detailColumns);
|
||||
if (!keyIsIdentity)
|
||||
this.SetPrimaryKey(row, detailKeyCol);
|
||||
|
||||
int needed = insertColumns.Count;
|
||||
if (currentParamCount + needed > maxParams)
|
||||
await FlushAsync();
|
||||
|
||||
foreach (var col in insertColumns)
|
||||
{
|
||||
row.TryGetValue(col.ColumnName, out object value);
|
||||
if (value is DBNull) value = null;
|
||||
value = CoerceDapperParam(value, col);
|
||||
var par = new SugarParameter("@" + $"{col.ColumnName}_{globalRowIndex}", value);
|
||||
var dbType = GenericDbValidationExtensions.EffectiveDapperDbType(value, col);
|
||||
if (dbType != null)
|
||||
{
|
||||
par.DbType = (System.Data.DbType)dbType;
|
||||
}
|
||||
parameters.Add(par);
|
||||
currentParamCount++;
|
||||
}
|
||||
|
||||
batchRows.Add(row);
|
||||
batchRowIndexes.Add(globalRowIndex);
|
||||
globalRowIndex++;
|
||||
}
|
||||
|
||||
await FlushAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SqlServer / PostgreSQL:Guid 列须把 string、JsonElement 转为 Guid。MySql 等保持原样。
|
||||
/// PostgreSQL:元数据为 varchar(Text) 且值为 Guid 时转为字符串以绑 Text。
|
||||
/// </summary>
|
||||
private object CoerceDapperParam(object value, TableColumnField col)
|
||||
{
|
||||
if (col == null) return value;
|
||||
if (col.ColumnType == "long")
|
||||
{
|
||||
return Convert.ToInt64(value);
|
||||
}
|
||||
string db = DbRelativeCache.GetDbType(TableInfo.DBServer);
|
||||
|
||||
if (db == "PgSql" && !GenericDbValidationExtensions.IsGuidColumn(col) && col.GetDbType() ==System.Data.DbType.String && value is Guid pgGuid)
|
||||
return pgGuid.ToString();
|
||||
|
||||
if (db != "MsSql" && db != "PgSql") return value;
|
||||
if (!GenericDbValidationExtensions.IsGuidColumn(col)) return value;
|
||||
if (value is Guid) return value;
|
||||
if (value == null || value is DBNull) return null;
|
||||
string s;
|
||||
if (value is string str) s = str;
|
||||
else if (value is JsonElement je && je.ValueKind == JsonValueKind.String) s = je.GetString();
|
||||
else s = value?.ToString();
|
||||
if (string.IsNullOrWhiteSpace(s)) return null;
|
||||
return Guid.TryParse(s, out var g) ? (object)g : value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
411
api_sqlsugar/VolPro.Core/Generic/GenericDbProviderExtensions.cs
Normal file
411
api_sqlsugar/VolPro.Core/Generic/GenericDbProviderExtensions.cs
Normal file
@@ -0,0 +1,411 @@
|
||||
using SqlSugar;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using VolPro.Core.Configuration;
|
||||
using VolPro.Core.UserManager;
|
||||
using VolPro.Core.Utilities;
|
||||
using VolPro.Entity.DomainModels;
|
||||
|
||||
namespace VolPro.Core.Generic
|
||||
{
|
||||
public static class GenericDbProviderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 设置逻辑删除字段、审批字段默认值为0
|
||||
/// </summary>
|
||||
public static T SetLogicDelDefault<T>(this T provider,
|
||||
Dictionary<string, object> mainData,
|
||||
List<TableColumnField> columns)
|
||||
where T : GenericDbProviderBase
|
||||
{
|
||||
string LogicDelField = provider.GetLogicDelField(columns);
|
||||
if (LogicDelField != null)
|
||||
{
|
||||
mainData[LogicDelField] = 0;
|
||||
}
|
||||
return provider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取逻辑删除字段
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="columns"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetLogicDelField<T>(this T provider, List<TableColumnField> columns)
|
||||
where T : GenericDbProviderBase
|
||||
{
|
||||
if (!string.IsNullOrEmpty(AppSetting.LogicDelField))
|
||||
{
|
||||
var logicCol = columns.FirstOrDefault(c =>
|
||||
c.ColumnName.Equals(AppSetting.LogicDelField, StringComparison.OrdinalIgnoreCase));
|
||||
if (logicCol != null)
|
||||
{
|
||||
return logicCol.ColumnName;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static T SetAuditDefault<T>(this T provider,
|
||||
Dictionary<string, object> mainData,
|
||||
List<TableColumnField> columns)
|
||||
where T : GenericDbProviderBase
|
||||
{
|
||||
var auditCol = columns.FirstOrDefault(c =>
|
||||
c.ColumnName.Equals("AuditStatus", StringComparison.OrdinalIgnoreCase));
|
||||
if (auditCol != null)
|
||||
{
|
||||
if (!mainData.TryGetValue(auditCol.ColumnName, out object val)
|
||||
|| val == null
|
||||
|| (val is string s && string.IsNullOrWhiteSpace(s)))
|
||||
{
|
||||
mainData[auditCol.ColumnName] = 0;
|
||||
}
|
||||
}
|
||||
return provider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据主键类型自动生成主键值(雪花、Guid、string-Guid)
|
||||
/// </summary>
|
||||
public static T SetPrimaryKey<T>(this T provider,
|
||||
Dictionary<string, object> mainData,
|
||||
TableColumnField keyColumn)
|
||||
where T : GenericDbProviderBase
|
||||
{
|
||||
if (keyColumn == null) return provider;
|
||||
|
||||
string keyName = keyColumn.ColumnName;
|
||||
string type = (keyColumn.ColumnType ?? "").Trim().ToLower();
|
||||
|
||||
mainData.TryGetValue(keyName, out object keyVal);
|
||||
bool hasValue = !string.IsNullOrEmpty(keyVal?.ToString());
|
||||
|
||||
// bigint/long 雪花ID
|
||||
if ((type == "long" || type == "bigint") && AppSetting.UseSnow)
|
||||
{
|
||||
var worker = new IdWorker();
|
||||
long id = worker.NextId();
|
||||
mainData[keyName] = id;
|
||||
return provider;
|
||||
}
|
||||
|
||||
// Guid 主键,string 主键,且未传值时生成 Guid 字符串
|
||||
if (type == "guid" || (type == "string" && !hasValue))
|
||||
{
|
||||
mainData[keyName] = Guid.NewGuid();
|
||||
return provider;
|
||||
}
|
||||
return provider;
|
||||
}
|
||||
|
||||
private static List<string> ModifyFields { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取需要忽略修改人/修改时间字段
|
||||
/// </summary>
|
||||
public static List<string> GetModifyFieldsToIgnore<T>(this T provider)
|
||||
where T : GenericDbProviderBase
|
||||
{
|
||||
if (ModifyFields != null)
|
||||
{
|
||||
return ModifyFields;
|
||||
}
|
||||
var ignore = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(AppSetting.ModifyMember?.UserIdField))
|
||||
{
|
||||
ignore.Add(AppSetting.ModifyMember.UserIdField);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(AppSetting.ModifyMember?.UserNameField))
|
||||
{
|
||||
ignore.Add(AppSetting.ModifyMember.UserNameField);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(AppSetting.ModifyMember?.DateField))
|
||||
{
|
||||
ignore.Add(AppSetting.ModifyMember.DateField);
|
||||
}
|
||||
ModifyFields = ignore.ToList();
|
||||
return ModifyFields;
|
||||
}
|
||||
|
||||
private static HashSet<string> AddFields { get; set; }
|
||||
/// <summary>
|
||||
/// 获取需要忽略写入人
|
||||
/// </summary>
|
||||
public static HashSet<string> GetAddFieldsToIgnore<T>(this T provider)
|
||||
where T : GenericDbProviderBase
|
||||
{
|
||||
if (AddFields != null)
|
||||
{
|
||||
return AddFields;
|
||||
}
|
||||
var ignore = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(AppSetting.CreateMember?.UserIdField))
|
||||
{
|
||||
ignore.Add(AppSetting.CreateMember.UserIdField);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(AppSetting.CreateMember?.UserNameField))
|
||||
{
|
||||
ignore.Add(AppSetting.CreateMember.UserNameField);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(AppSetting.CreateMember?.DateField))
|
||||
{
|
||||
ignore.Add(AppSetting.CreateMember.DateField);
|
||||
}
|
||||
AddFields = ignore;
|
||||
return AddFields;
|
||||
}
|
||||
private static HashSet<string> AddAndModifyFields { get; set; }
|
||||
public static HashSet<string> GetAddAndModifyFieldsToIgnore<T>(this T provider)
|
||||
where T : GenericDbProviderBase
|
||||
{
|
||||
if (AddAndModifyFields == null)
|
||||
{
|
||||
AddAndModifyFields = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
GetAddFieldsToIgnore(provider).ToList().ForEach(x => AddAndModifyFields.Add(x));
|
||||
GetModifyFieldsToIgnore(provider).ToList().ForEach(x => AddAndModifyFields.Add(x));
|
||||
}
|
||||
return AddAndModifyFields;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 判断字段类型是否为整型(int/long/bigint)
|
||||
/// </summary>
|
||||
public static bool IsIntOrLong<T>(this T provider, string columnType)
|
||||
where T : GenericDbProviderBase
|
||||
{
|
||||
string type = (columnType ?? "").Trim().ToLower();
|
||||
return type == "int" || type == "long" || type == "bigint";
|
||||
}
|
||||
/// <summary>
|
||||
/// 是否自增主键
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="provider"></param>
|
||||
/// <param name="columnType"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsIdentity<T>(this T provider, string columnType)
|
||||
where T : GenericDbProviderBase
|
||||
{
|
||||
string type = (columnType ?? "").Trim().ToLower();
|
||||
if (type == "int")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return (type == "long" || type == "bigint") && !AppSetting.UseSnow;
|
||||
}
|
||||
|
||||
// GetDbType / IsZero 已移动到 GenericDbValidationExtensions.cs
|
||||
|
||||
private static void AddWhereSugarParameter(List<SugarParameter> parameters, string name, object value, TableColumnField col)
|
||||
{
|
||||
var par = new SugarParameter(name, value);
|
||||
var dbType = GenericDbValidationExtensions.EffectiveDapperDbType(value, col);
|
||||
if (dbType != null)
|
||||
{
|
||||
par.DbType = (System.Data.DbType)dbType;
|
||||
}
|
||||
parameters.Add(par);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造查询条件 where 语句
|
||||
/// </summary>
|
||||
public static T BuildWhere<T>(this T provider,
|
||||
List<SearchParameters> filters,
|
||||
List<TableColumnField> columns,
|
||||
List<string> whereList,
|
||||
List<SugarParameter> parameters,
|
||||
string LeftQuote,
|
||||
string RightQuote)
|
||||
where T : GenericDbProviderBase
|
||||
{
|
||||
if (filters == null || filters.Count == 0) return provider;
|
||||
|
||||
int index = 0;
|
||||
|
||||
foreach (var f in filters)
|
||||
{
|
||||
if (string.IsNullOrEmpty(f?.Name)) continue;
|
||||
|
||||
var col = columns.FirstOrDefault(c =>
|
||||
c.ColumnName.Equals(f.Name, StringComparison.OrdinalIgnoreCase));
|
||||
if (col == null) continue;
|
||||
|
||||
string op = (f.DisplayType ?? "").ToLower();
|
||||
|
||||
if (op == "isnull")
|
||||
{
|
||||
whereList.Add($"{LeftQuote}{col.ColumnName}{RightQuote} IS NULL");
|
||||
continue;
|
||||
}
|
||||
if (op == "isnotnull")
|
||||
{
|
||||
whereList.Add($"{LeftQuote}{col.ColumnName}{RightQuote} IS NOT NULL");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(f.Value)) continue;
|
||||
|
||||
object ConvertValue(string val)
|
||||
{
|
||||
string type = (col.ColumnType ?? "").ToLower();
|
||||
if (type == "long" || type == "bigint" || type == "int64")
|
||||
{
|
||||
if (long.TryParse(val, out var lv))
|
||||
return lv;
|
||||
return null;
|
||||
}
|
||||
if (type.Contains("int"))
|
||||
{
|
||||
if (int.TryParse(val,out var iv))
|
||||
return iv;
|
||||
return null;
|
||||
}
|
||||
if (type == "guid" || type == "uniqueidentifier")
|
||||
{
|
||||
if (Guid.TryParse(val, out var gv)) return gv;
|
||||
return null;
|
||||
}
|
||||
if (type.Contains("decimal") || type.Contains("numeric") || type.Contains("money"))
|
||||
{
|
||||
if (decimal.TryParse(val, out var dv)) return dv;
|
||||
return null;
|
||||
}
|
||||
if (type.Contains("float") || type.Contains("double"))
|
||||
{
|
||||
if (double.TryParse(val, out var fv)) return fv;
|
||||
return null;
|
||||
}
|
||||
if (type.Contains("date") || type.Contains("time"))
|
||||
{
|
||||
if (DateTime.TryParse(val, out var dt)) return dt;
|
||||
return null;
|
||||
}
|
||||
if (type.Contains("bit") || type.Contains("bool"))
|
||||
{
|
||||
if (val == "1" || val.Equals("true", StringComparison.OrdinalIgnoreCase)) return true;
|
||||
if (val == "0" || val.Equals("false", StringComparison.OrdinalIgnoreCase)) return false;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
bool isIn = op == "in" || op == "selectlist" || op == "checkbox" || op == "notin";
|
||||
if (isIn)
|
||||
{
|
||||
var arr = f.Value.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(v => v.Trim())
|
||||
.ToArray();
|
||||
if (arr.Length == 0) continue;
|
||||
|
||||
var paramNames = new List<string>();
|
||||
foreach (var v in arr)
|
||||
{
|
||||
var cv = ConvertValue(v);
|
||||
if (cv == null) continue;
|
||||
string name = $"@p{index++}";
|
||||
string placeholder = name;
|
||||
paramNames.Add(placeholder);
|
||||
AddWhereSugarParameter(parameters, name, cv, col);
|
||||
}
|
||||
if (paramNames.Count == 0) continue;
|
||||
|
||||
string inSql = string.Join(",", paramNames);
|
||||
whereList.Add($"{LeftQuote}{col.ColumnName}{RightQuote} {(op == "notin" ? "NOT IN" : "IN")} ({inSql})");
|
||||
continue;
|
||||
}
|
||||
|
||||
// LIKE / 模糊查询,统一在这里处理,保证三种数据库(PgSql/MySql/SqlServer)都能正常工作
|
||||
bool isLikeOp = op == "like" || op == "contains"
|
||||
|| op == "startwith" || op == "likestart"
|
||||
|| op == "endwith" || op == "likeend";
|
||||
if (isLikeOp)
|
||||
{
|
||||
string raw = f.Value;
|
||||
string pattern;
|
||||
switch (op)
|
||||
{
|
||||
case "startwith":
|
||||
case "likestart":
|
||||
pattern = raw + "%";
|
||||
break;
|
||||
case "endwith":
|
||||
case "likeend":
|
||||
pattern = "%" + raw;
|
||||
break;
|
||||
case "like":
|
||||
case "contains":
|
||||
default:
|
||||
pattern = "%" + raw + "%";
|
||||
break;
|
||||
}
|
||||
|
||||
string nameLike = $"p{index++}";
|
||||
string placeholderLike = $"@{nameLike}";
|
||||
AddWhereSugarParameter(parameters, placeholderLike, pattern, col);
|
||||
whereList.Add($"{LeftQuote}{col.ColumnName}{RightQuote} LIKE {placeholderLike}");
|
||||
continue;
|
||||
}
|
||||
|
||||
var valObj = ConvertValue(f.Value);
|
||||
if (valObj == null) continue;
|
||||
|
||||
string name1 = $"p{index++}";
|
||||
string placeholder1 = $"@{name1}";
|
||||
AddWhereSugarParameter(parameters, placeholder1, valObj, col);
|
||||
|
||||
switch (op)
|
||||
{
|
||||
case "gt":
|
||||
case ">":
|
||||
whereList.Add($"{LeftQuote}{col.ColumnName}{RightQuote} > {placeholder1}");
|
||||
break;
|
||||
case ">=":
|
||||
case "thanorequal":
|
||||
whereList.Add($"{LeftQuote}{col.ColumnName}{RightQuote} >= {placeholder1}");
|
||||
break;
|
||||
case "lt":
|
||||
case "<":
|
||||
whereList.Add($"{LeftQuote}{col.ColumnName}{RightQuote} < {placeholder1}");
|
||||
break;
|
||||
case "<=":
|
||||
case "lessorequal":
|
||||
whereList.Add($"{LeftQuote}{col.ColumnName}{RightQuote} <= {placeholder1}");
|
||||
break;
|
||||
case "neq":
|
||||
case "!=":
|
||||
case "<>":
|
||||
whereList.Add($"{LeftQuote}{col.ColumnName}{RightQuote} <> {placeholder1}");
|
||||
break;
|
||||
case "eq":
|
||||
case "==":
|
||||
case "=":
|
||||
default:
|
||||
whereList.Add($"{LeftQuote}{col.ColumnName}{RightQuote} = {placeholder1}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return provider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 列是否为 Guid / uniqueidentifier 语义
|
||||
/// </summary>
|
||||
internal static bool IsGuidColumn(TableColumnField col)
|
||||
{
|
||||
if (col == null) return false;
|
||||
if (col.GetDbType() == System.Data.DbType.Guid) return true;
|
||||
string t = (col.ColumnType ?? "").Trim().ToLowerInvariant();
|
||||
return t == "guid" || t == "uniqueidentifier";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
67
api_sqlsugar/VolPro.Core/Generic/GenericDbProviderFactory.cs
Normal file
67
api_sqlsugar/VolPro.Core/Generic/GenericDbProviderFactory.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using VolPro.Core.Configuration;
|
||||
using VolPro.Core.Const;
|
||||
using VolPro.Core.DBManager;
|
||||
using VolPro.Core.EFDbContext;
|
||||
using VolPro.Core.Extensions.AutofacManager;
|
||||
using VolPro.Core.UserManager;
|
||||
|
||||
namespace VolPro.Core.Generic
|
||||
{
|
||||
/// <summary>
|
||||
/// 通用数据库操作工厂,根据 DBServer/dbType 返回具体实现
|
||||
/// </summary>
|
||||
public interface IGenericDbProviderFactory
|
||||
{
|
||||
IGenericDbProvider GetProvider(string table = null);
|
||||
IGenericDbProvider Provider { get; }
|
||||
}
|
||||
|
||||
public class GenericDbProviderFactory : IGenericDbProviderFactory, IDependency
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public IGenericDbProvider Provider
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetProvider();
|
||||
}
|
||||
}
|
||||
public GenericDbProviderFactory(IServiceProvider serviceProvider)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public IGenericDbProvider GetProvider(string table = null)
|
||||
{
|
||||
table ??= GenericTableAsyncLocal.CurrentTableName;
|
||||
string dbServer = TableColumnContext.TableInfo
|
||||
.Where(x => x.TableName == table).Select(s => s.DBServer)
|
||||
.FirstOrDefault() ?? typeof(SysDbContext).Name;
|
||||
|
||||
string dbType = DbRelativeCache.GetDbType(dbServer) ?? DBType.Name;
|
||||
IGenericDbProvider provider = null;
|
||||
switch (dbType)
|
||||
{
|
||||
case "MySql":
|
||||
provider = new GenericMySqlProvider();
|
||||
break;
|
||||
case "PgSql":
|
||||
provider = new GenericPgSqlProvider();
|
||||
break;
|
||||
//case "Oracle":
|
||||
// provider = new GenericOracleProvider();
|
||||
// break;
|
||||
case "MsSql":
|
||||
provider = new GenericSqlServerProvider();
|
||||
break;
|
||||
default:
|
||||
throw new Exception("数据库暂未支持");
|
||||
}
|
||||
return provider;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,460 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using VolPro.Core.UserManager;
|
||||
using VolPro.Entity.DomainModels;
|
||||
|
||||
namespace VolPro.Core.Generic
|
||||
{
|
||||
/// <summary>
|
||||
/// 通用数据库字段与明细数据校验扩展
|
||||
/// </summary>
|
||||
public static class GenericDbValidationExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 字段校验:必填、长度、类型
|
||||
/// </summary>
|
||||
public static string ValidateColumns<T>(this T provider,
|
||||
Dictionary<string, object> mainData,
|
||||
List<TableColumnField> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理字段类型转换与校验
|
||||
/// </summary>
|
||||
public static bool CheckAndConvertValue<T>(this T provider,
|
||||
Dictionary<string, object> 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据表字段配置获取对应的 DbType,避免 PgSql 等数据库将参数推断为 text 导致类型不匹配
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 结合列元数据与运行时值确定参数 DbType(与 SugarParameter / Dapper 一致)
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 列是否为 Guid / uniqueidentifier 语义
|
||||
/// </summary>
|
||||
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";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断值是否等于0(用于判断主键是否有意义的值)
|
||||
/// </summary>
|
||||
public static bool IsZero<T>(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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add 时,对所有明细表做字段校验,且忽略外键字段(与主表主键同名字段)的必填要求
|
||||
/// </summary>
|
||||
public static string ValidateAllDetails<T>(this T provider, SaveModel saveModel, List<TableColumnField> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add 时,校验明细表的所有数据行(忽略外键必填)
|
||||
/// </summary>
|
||||
public static string ValidateDetailList<T>(this T provider, string detailTableName, List<Dictionary<string, object>> 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<string> ingroCols = new List<string>();
|
||||
if (foreignCol != null)
|
||||
{
|
||||
ingroCols.Add(foreignCol.ColumnName);
|
||||
}
|
||||
// 忽略外键必填,把外键字段的 IsNull 视为可空(1),即不参与必填校验
|
||||
List<TableColumnField> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update 时,对所有明细表做字段校验(只校验提交的字段,并忽略外键必填)
|
||||
/// </summary>
|
||||
public static string ValidateAllDetailsForUpdate<T>(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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update 时,校验某一张明细表的所有数据行(只校验提交的字段,并忽略外键必填)
|
||||
/// </summary>
|
||||
public static string ValidateDetailListForUpdate<T>(this T provider, string detailTableName, List<Dictionary<string, object>> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
301
api_sqlsugar/VolPro.Core/Generic/GenericExcelExportHelper.cs
Normal file
301
api_sqlsugar/VolPro.Core/Generic/GenericExcelExportHelper.cs
Normal file
@@ -0,0 +1,301 @@
|
||||
using OfficeOpenXml;
|
||||
using OfficeOpenXml.Style;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using VolPro.Core.Extensions;
|
||||
using VolPro.Core.Utilities;
|
||||
|
||||
namespace VolPro.Core.Generic
|
||||
{
|
||||
/// <summary>
|
||||
/// 通用导出 Excel 辅助类(参照 EPPlusHelper.Export 的行为,
|
||||
/// 实现字典数据源转换、字段类型显示、语言翻译等操作),仅返回内存字节,不落地文件。
|
||||
/// </summary>
|
||||
internal static class GenericExcelExportHelper
|
||||
{
|
||||
public static byte[] BuildExportBytes(
|
||||
string tableName,
|
||||
List<Dictionary<string, object>> rows,
|
||||
string[] exportColumns,
|
||||
List<string> ignoreColumns)
|
||||
{
|
||||
// 1. 列配置(CellOptions)
|
||||
var mi = typeof(EPPlusHelper).GetMethod("GetExportColumnInfo",
|
||||
BindingFlags.NonPublic | BindingFlags.Static);
|
||||
if (mi == null) return null;
|
||||
var cellOptions = mi.Invoke(null, new object[] { tableName, false, true, exportColumns }) as List<CellOptions>;
|
||||
if (cellOptions == null || cellOptions.Count == 0)
|
||||
return Array.Empty<byte>();
|
||||
|
||||
// 2. 最终要导出的列
|
||||
var exportCols = BuildExportColumns(cellOptions, ignoreColumns);
|
||||
if (exportCols.Count == 0)
|
||||
return [];
|
||||
|
||||
// 3. 字典列元数据
|
||||
var dicNoKeys = cellOptions
|
||||
.Where(x => !string.IsNullOrEmpty(x.DropNo) && x.KeyValues != null && x.KeyValues.Keys.Count > 0)
|
||||
.Select(x => (x.ColumnName, x.SearchType, x.EditType))
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
var multiSelectColumns = dicNoKeys
|
||||
.Where(x => x.SearchType == "checkbox" || x.SearchType == "selectList" || x.SearchType == "treeSelect"
|
||||
|| x.EditType == "checkbox" || x.EditType == "selectList" || x.EditType == "treeSelect")
|
||||
.Select(x => x.ColumnName)
|
||||
.ToArray();
|
||||
|
||||
using var package = new ExcelPackage();
|
||||
var sheet = package.Workbook.Worksheets.Add("sheet1");
|
||||
|
||||
// 4. 表头
|
||||
WriteHeaderRow(sheet, cellOptions, exportCols);
|
||||
|
||||
// 5. 数据行
|
||||
WriteDataRows(sheet, rows, exportCols, cellOptions, dicNoKeys, multiSelectColumns);
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
// 6. 自动列宽
|
||||
if (sheet.Dimension != null)
|
||||
sheet.Cells[sheet.Dimension.Address].AutoFitColumns();
|
||||
}
|
||||
|
||||
using var ms = new MemoryStream();
|
||||
package.SaveAs(ms);
|
||||
return ms.ToArray();
|
||||
}
|
||||
/// <summary>确定最终导出的列顺序,并应用忽略字段</summary>
|
||||
private static List<string> BuildExportColumns(List<CellOptions> cellOptions, List<string> ignoreColumns)
|
||||
{
|
||||
var cols = cellOptions.Select(c => c.ColumnName).ToList();
|
||||
if (ignoreColumns != null && ignoreColumns.Count > 0)
|
||||
{
|
||||
var ignoreSet = new HashSet<string>(ignoreColumns.Select(x => x.ToLower()));
|
||||
cols = cols.Where(c => !ignoreSet.Contains(c.ToLower())).ToList();
|
||||
}
|
||||
return cols;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>写表头行(颜色、宽度、翻译与 EPPlusHelper.Export 中模板=false 一致)</summary>
|
||||
private static void WriteHeaderRow(
|
||||
ExcelWorksheet sheet,
|
||||
List<CellOptions> cellOptions,
|
||||
List<string> exportCols)
|
||||
{
|
||||
for (int i = 0; i < exportCols.Count; i++)
|
||||
{
|
||||
string colName = exportCols[i];
|
||||
using (var cell = sheet.Cells[1, i + 1])
|
||||
{
|
||||
cell.Style.Fill.PatternType = ExcelFillStyle.Solid;
|
||||
cell.Style.Fill.BackgroundColor.SetColor(Color.Gray);
|
||||
cell.Style.Font.Color.SetColor(Color.White);
|
||||
}
|
||||
|
||||
var opt = cellOptions.FirstOrDefault(x => x.ColumnName == colName);
|
||||
if (opt == null)
|
||||
{
|
||||
sheet.Column(i + 1).Width = 15;
|
||||
sheet.Cells[1, i + 1].Value = colName.Translator();
|
||||
continue;
|
||||
}
|
||||
|
||||
sheet.Column(i + 1).Width = opt.ColumnWidth / 6.0;
|
||||
var header = opt.ColumnCNName;
|
||||
sheet.Cells[1, i + 1].Value = header.Translator();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>写数据行,包括日期格式、字典转换、图片及 long 文本处理</summary>
|
||||
private static void WriteDataRows(
|
||||
ExcelWorksheet sheet,
|
||||
List<Dictionary<string, object>> rows,
|
||||
List<string> exportCols,
|
||||
List<CellOptions> cellOptions,
|
||||
List<(string ColumnName, string SearchType, string EditType)> dicNoKeys,
|
||||
string[] multiSelectColumns)
|
||||
{
|
||||
if (rows == null || rows.Count == 0) return;
|
||||
|
||||
const long imageLimitBytes = 10 * 1024 * 1024;
|
||||
long embeddedBytes = 0;
|
||||
|
||||
for (int r = 0; r < rows.Count; r++)
|
||||
{
|
||||
var row = rows[r];
|
||||
for (int c = 0; c < exportCols.Count; c++)
|
||||
{
|
||||
string colName = exportCols[c];
|
||||
row.TryGetValue(colName, out object value);
|
||||
|
||||
int? viewType = cellOptions
|
||||
.Where(x => x.ColumnName == colName)
|
||||
.Select(x => x.ViewType)
|
||||
.FirstOrDefault();
|
||||
|
||||
// 日期格式
|
||||
value = FormatDate(value, viewType);
|
||||
|
||||
// 字典转换
|
||||
if (value != null && dicNoKeys.Any(x => x.ColumnName == colName))
|
||||
value = TranslateDictionary(cellOptions, multiSelectColumns, colName, value);
|
||||
|
||||
// 图片
|
||||
if (viewType == 1 && value != null)
|
||||
{
|
||||
embeddedBytes = HandleImageCell(sheet, r, c, value, embeddedBytes, imageLimitBytes);
|
||||
continue;
|
||||
}
|
||||
|
||||
// long 按文本导出
|
||||
if (value is long || value is long?)
|
||||
{
|
||||
sheet.Cells[r + 2, c + 1].Style.Numberformat.Format = "@";
|
||||
value = value?.ToString();
|
||||
}
|
||||
|
||||
sheet.Cells[r + 2, c + 1].Value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>按 viewType/类型格式化日期</summary>
|
||||
private static object FormatDate(object value, int? viewType)
|
||||
{
|
||||
if (value == null) return null;
|
||||
|
||||
// 6year年、5month年月、4date年月日
|
||||
if (viewType == 6 || viewType == 5 || viewType == 4)
|
||||
{
|
||||
if (value is DateTime dt)
|
||||
{
|
||||
return viewType switch
|
||||
{
|
||||
6 => (object)dt.Year,
|
||||
5 => dt.ToString("yyyy-MM"),
|
||||
_ => dt.ToString("yyyy-MM-dd")
|
||||
};
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
if (value is DateTime dt2)
|
||||
return dt2.ToString("yyyy-MM-dd HH:mm:sss");
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>字典数据源转换(多选 / 单选)</summary>
|
||||
private static object TranslateDictionary(
|
||||
List<CellOptions> cellOptions,
|
||||
string[] multiSelectColumns,
|
||||
string colName,
|
||||
object value)
|
||||
{
|
||||
if (value == null) return null;
|
||||
|
||||
if (multiSelectColumns.Contains(colName))
|
||||
{
|
||||
return string.Join(",", GetMultiDictValues(cellOptions, colName, value.ToString()));
|
||||
}
|
||||
|
||||
var map = cellOptions
|
||||
.Where(x => x.ColumnName == colName)
|
||||
.Select(x => x.KeyValues)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (map == null) return value;
|
||||
return map.TryGetValue(value.ToString(), out var show) ? (object)show : value;
|
||||
}
|
||||
|
||||
/// <summary>多选字典列:“1,2,3” => “名称1,名称2,名称3”</summary>
|
||||
private static IEnumerable<string> GetMultiDictValues(
|
||||
List<CellOptions> cellOptions,
|
||||
string colName,
|
||||
string raw)
|
||||
{
|
||||
var map = cellOptions
|
||||
.Where(x => x.ColumnName == colName)
|
||||
.Select(x => x.KeyValues)
|
||||
.FirstOrDefault();
|
||||
|
||||
var parts = raw.Split(',');
|
||||
if (map == null)
|
||||
{
|
||||
foreach (var p in parts) yield return p;
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (var p in parts)
|
||||
{
|
||||
yield return map.TryGetValue(p, out var show) ? show : p;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>图片导出:嵌入或超链接</summary>
|
||||
private static long HandleImageCell(
|
||||
ExcelWorksheet sheet,
|
||||
int rowIndex,
|
||||
int colIndex,
|
||||
object value,
|
||||
long embeddedBytes,
|
||||
long imageLimitBytes)
|
||||
{
|
||||
string imgPath = value.ToString();
|
||||
if (string.IsNullOrWhiteSpace(imgPath))
|
||||
return embeddedBytes;
|
||||
|
||||
bool isHttp = imgPath.StartsWith("http", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (!isHttp)
|
||||
{
|
||||
imgPath = ("".MapPath(true) + "\\" + imgPath).ReplacePath();
|
||||
if (!File.Exists(imgPath))
|
||||
{
|
||||
sheet.Cells[rowIndex + 2, colIndex + 1].Value = value;
|
||||
return embeddedBytes;
|
||||
}
|
||||
|
||||
var fi = new FileInfo(imgPath);
|
||||
long size = fi.Length;
|
||||
bool canEmbed = embeddedBytes + size <= imageLimitBytes;
|
||||
|
||||
if (canEmbed)
|
||||
{
|
||||
var pic = sheet.Drawings.AddPicture($"img_{rowIndex}_{colIndex}_{Guid.NewGuid():N}", fi);
|
||||
pic.SetPosition(rowIndex + 1, 1, colIndex, 1);
|
||||
pic.SetSize(80, 80);
|
||||
|
||||
if (sheet.Row(rowIndex + 2).Height < 60)
|
||||
sheet.Row(rowIndex + 2).Height = 60;
|
||||
if (sheet.Column(colIndex + 1).Width < 15)
|
||||
sheet.Column(colIndex + 1).Width = 15;
|
||||
|
||||
sheet.Cells[rowIndex + 2, colIndex + 1].Value = null;
|
||||
return embeddedBytes + size;
|
||||
}
|
||||
|
||||
var fileUri = new Uri(fi.FullName);
|
||||
sheet.Cells[rowIndex + 2, colIndex + 1].Hyperlink = fileUri;
|
||||
sheet.Cells[rowIndex + 2, colIndex + 1].Value = Path.GetFileName(imgPath);
|
||||
return embeddedBytes;
|
||||
}
|
||||
|
||||
if (Uri.TryCreate(imgPath, UriKind.Absolute, out var uri))
|
||||
{
|
||||
sheet.Cells[rowIndex + 2, colIndex + 1].Hyperlink = uri;
|
||||
sheet.Cells[rowIndex + 2, colIndex + 1].Value = Path.GetFileName(imgPath);
|
||||
}
|
||||
|
||||
return embeddedBytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
210
api_sqlsugar/VolPro.Core/Generic/GenericExcelImportHelper.cs
Normal file
210
api_sqlsugar/VolPro.Core/Generic/GenericExcelImportHelper.cs
Normal file
@@ -0,0 +1,210 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using OfficeOpenXml;
|
||||
using VolPro.Core.Extensions;
|
||||
using VolPro.Core.UserManager;
|
||||
using VolPro.Core.Utilities;
|
||||
|
||||
namespace VolPro.Core.Generic
|
||||
{
|
||||
internal static class GenericExcelImportHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 从 Excel 流读取数据,按 CellOptions 校验表头和字典,
|
||||
/// 返回 WebResponseContent,成功时 Data 为 List<Dictionary<string,object>>
|
||||
/// </summary>
|
||||
public static WebResponseContent ReadRowsByCellOptions(string tableName, Stream excelStream, int importStartRowIndex = 1,
|
||||
HashSet<string> ignoreFields=null)
|
||||
{
|
||||
var response = WebResponseContent.Instance;
|
||||
var resultRows = new List<Dictionary<string, object>>();
|
||||
if (excelStream == null || !excelStream.CanRead)
|
||||
return response.Error("未能读取上传的文件".Translator());
|
||||
|
||||
var cellOptions = GetCellOptions(tableName);
|
||||
if (cellOptions == null || cellOptions.Count == 0)
|
||||
return response.Error($"未找到表【{tableName}】的导出配置".Translator());
|
||||
|
||||
using var package = new ExcelPackage(excelStream);
|
||||
var sheet = package.Workbook.Worksheets.FirstOrDefault();
|
||||
if (sheet?.Dimension == null || sheet.Dimension.End.Row <= importStartRowIndex)
|
||||
return response.Error("导入文件中没有数据".Translator());
|
||||
if (ignoreFields!=null)
|
||||
{
|
||||
cellOptions = cellOptions.Where(x => !ignoreFields.Contains(x.ColumnName)).ToList();
|
||||
}
|
||||
|
||||
if (!BindHeaderIndexes(sheet, cellOptions, importStartRowIndex, out string headerError))
|
||||
return response.Error(headerError);
|
||||
|
||||
var dicNoKeys = cellOptions
|
||||
.Where(x => !string.IsNullOrEmpty(x.DropNo) && x.KeyValues != null && x.KeyValues.Keys.Count > 0)
|
||||
.Select(x => (x.ColumnName, x.SearchType, x.EditType))
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
var multiSelectColumns = dicNoKeys
|
||||
.Where(x => x.SearchType == "checkbox" || x.SearchType == "selectList" || x.SearchType == "treeSelect"
|
||||
|| x.EditType == "checkbox" || x.EditType == "selectList" || x.EditType == "treeSelect")
|
||||
.Select(x => x.ColumnName)
|
||||
.ToArray();
|
||||
|
||||
// 日期字段类型(用于 Excel 数字日期转换)
|
||||
var dateFields = TableColumnContext.Data
|
||||
.Where(x => x.TableName == tableName && (x.ColumnType == "Date" || x.ColumnType == "DateTime"))
|
||||
.Select(s => s.ColumnName)
|
||||
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
int rowStart = importStartRowIndex + 1;
|
||||
int rowEnd = sheet.Dimension.End.Row;
|
||||
int colStart = sheet.Dimension.Start.Column;
|
||||
int colEnd = sheet.Dimension.End.Column;
|
||||
|
||||
for (int r = rowStart; r <= rowEnd; r++)
|
||||
{
|
||||
var rowDict = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
||||
bool hasAnyValue = false;
|
||||
|
||||
for (int c = colStart; c <= colEnd; c++)
|
||||
{
|
||||
var opt = cellOptions.FirstOrDefault(x => x.Index == c);
|
||||
if (opt == null) continue;
|
||||
|
||||
string raw = sheet.Cells[r, c].Value?.ToString();
|
||||
raw = raw?.Trim();
|
||||
|
||||
if (string.IsNullOrEmpty(raw))
|
||||
{
|
||||
if (opt.Requierd)
|
||||
{
|
||||
string msg = "第[{$ts}]行,[{$ts}]验证未通过,不能为空"
|
||||
.TranslatorFormat(r, opt.ColumnCNName);
|
||||
return response.Error(msg);
|
||||
}
|
||||
|
||||
rowDict[opt.ColumnName] = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
hasAnyValue = true;
|
||||
|
||||
// 日期校验与转换(参考 EPPlusHelper.ReadToDataTable 中的日期处理):
|
||||
// 如果当前字段在 dateFields 中,且值为长度 5 的数字(Excel 序列号),则转换成 DateTime。
|
||||
if (dateFields.Contains(opt.ColumnName)
|
||||
&& raw.Length == 5
|
||||
&& int.TryParse(raw, out int days))
|
||||
{
|
||||
var dt = new DateTime(1900, 1, 1).AddDays(days - 2);
|
||||
rowDict[opt.ColumnName] = dt;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(opt.DropNo))
|
||||
{
|
||||
rowDict[opt.ColumnName] = raw;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (opt.KeyValues == null)
|
||||
{
|
||||
return response.Error("[{$ts}]数据字典缺失".TranslatorFormat(opt.ColumnCNName));
|
||||
}
|
||||
|
||||
string key = null;
|
||||
if (multiSelectColumns.Contains(opt.ColumnName))
|
||||
{
|
||||
var cellValues = raw.Replace(",", ",")
|
||||
.Split(",", StringSplitOptions.RemoveEmptyEntries);
|
||||
var keys = opt.KeyValues
|
||||
.Where(x => cellValues.Contains(x.Value))
|
||||
.Select(s => s.Key)
|
||||
.ToArray();
|
||||
if (cellValues.Length == keys.Length)
|
||||
{
|
||||
key = string.Join(",", keys);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
key = opt.KeyValues
|
||||
.Where(x => x.Value == raw)
|
||||
.Select(s => s.Key)
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
if (key == null)
|
||||
{
|
||||
string values = string.Join(",",
|
||||
opt.KeyValues
|
||||
.Take(300)
|
||||
.Select(s => s.Value.Translator()));
|
||||
string msg = "第[{$ts}]行,[{$ts}]验证未通过,只能填写[{$ts}]"
|
||||
.TranslatorFormat(r, opt.ColumnCNName, values);
|
||||
return response.Error(msg);
|
||||
}
|
||||
rowDict[opt.ColumnName] = key;
|
||||
}
|
||||
|
||||
if (!hasAnyValue) continue;
|
||||
resultRows.Add(rowDict);
|
||||
}
|
||||
|
||||
return response.OK(null, resultRows);
|
||||
}
|
||||
|
||||
private static List<CellOptions> GetCellOptions(string tableName)
|
||||
{
|
||||
var mi = typeof(EPPlusHelper).GetMethod(
|
||||
"GetExportColumnInfo",
|
||||
BindingFlags.NonPublic | BindingFlags.Static);
|
||||
if (mi == null) return null;
|
||||
|
||||
return mi.Invoke(null, new object[] { tableName, false, false, null }) as List<CellOptions>;
|
||||
}
|
||||
|
||||
private static bool BindHeaderIndexes(
|
||||
ExcelWorksheet sheet,
|
||||
List<CellOptions> cellOptions,
|
||||
int headerRow,
|
||||
out string error)
|
||||
{
|
||||
error = null;
|
||||
|
||||
for (int j = sheet.Dimension.Start.Column, k = sheet.Dimension.End.Column; j <= k; j++)
|
||||
{
|
||||
string columnCNName = sheet.Cells[headerRow, j].Value?.ToString()?.Trim();
|
||||
if (string.IsNullOrEmpty(columnCNName)) continue;
|
||||
|
||||
var options = cellOptions
|
||||
.FirstOrDefault(x => x.ColumnCNName.Translator() == columnCNName);
|
||||
|
||||
if (options == null)
|
||||
{
|
||||
error = "[{$ts}]不是模板中的列".TranslatorReplace(columnCNName, true);
|
||||
return false;
|
||||
}
|
||||
if (options.Index > 0)
|
||||
{
|
||||
error = "[{$ts}]列名重复".TranslatorReplace(columnCNName, true);
|
||||
return false;
|
||||
}
|
||||
options.Index = j;
|
||||
}
|
||||
|
||||
if (cellOptions.Exists(x => x.Index == 0))
|
||||
{
|
||||
var errorOps = cellOptions
|
||||
.Where(x => x.Index == 0)
|
||||
.Select(s => s.ColumnCNName.Translator() + "," + s.ColumnName);
|
||||
error = $"{"导入文件列未找到字段".Translator()}:{string.Join("; ", errorOps)}";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
using OfficeOpenXml;
|
||||
using OfficeOpenXml.FormulaParsing.Excel.Functions.Math;
|
||||
using OfficeOpenXml.Style;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using VolPro.Core.UserManager;
|
||||
using VolPro.Core.Utilities;
|
||||
|
||||
namespace VolPro.Core.Generic
|
||||
{
|
||||
/// <summary>
|
||||
/// 通用导入模板 Excel 生成辅助类(基于 TableColumnField 元数据)
|
||||
/// </summary>
|
||||
internal static class GenericExcelTemplateHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 根据表配置生成导入模板 Excel 的二进制内容
|
||||
/// </summary>
|
||||
/// <param name="tableDisplayName">表中文名/标题</param>
|
||||
/// <param name="columns">表字段配置</param>
|
||||
/// <returns>Excel 文件字节数组</returns>
|
||||
public static byte[] BuildTemplateBytes(string tableDisplayName, List<TableColumnField> columns)
|
||||
{
|
||||
if (columns == null || columns.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
//仅导出实际表字段
|
||||
var exportColumns = columns
|
||||
.Where(c => c.IsDisplay == 1 && c.ReferenceField == 0)
|
||||
.ToList();
|
||||
|
||||
if (exportColumns.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
using var package = new ExcelPackage();
|
||||
|
||||
string sheetName = string.IsNullOrWhiteSpace(tableDisplayName)
|
||||
? (exportColumns.First().TableName ?? "sheet1")
|
||||
: tableDisplayName;
|
||||
|
||||
var worksheet = package.Workbook.Worksheets.Add(sheetName);
|
||||
|
||||
int colIndex = 1;
|
||||
foreach (var col in exportColumns)
|
||||
{
|
||||
worksheet.Cells[1, colIndex].Style.Fill.PatternType = ExcelFillStyle.Solid;
|
||||
worksheet.Cells[1, colIndex].Style.Fill.BackgroundColor.SetColor(col.IsNull == 0 ? Color.Red : Color.White);
|
||||
string columnName = col.ColumnName;
|
||||
worksheet.Cells[1, colIndex].Value = string.IsNullOrWhiteSpace(col.ColumnCnName)
|
||||
? columnName
|
||||
: col.ColumnCnName.Translator();
|
||||
colIndex++;
|
||||
}
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
// 自动列宽
|
||||
if (worksheet.Dimension != null)
|
||||
{
|
||||
worksheet.Cells[worksheet.Dimension.Address].AutoFitColumns();
|
||||
}
|
||||
}
|
||||
return package.GetAsByteArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
26
api_sqlsugar/VolPro.Core/Generic/GenericMySqlProvider.cs
Normal file
26
api_sqlsugar/VolPro.Core/Generic/GenericMySqlProvider.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using Dapper;
|
||||
using VolPro.Core.EFDbContext;
|
||||
using VolPro.Core.UserManager;
|
||||
using VolPro.Entity.DomainModels;
|
||||
|
||||
namespace VolPro.Core.Generic
|
||||
{
|
||||
/// <summary>
|
||||
/// MySql 通用 CRUD 实现
|
||||
/// </summary>
|
||||
public class GenericMySqlProvider : GenericDbProviderBase
|
||||
{
|
||||
protected override string LeftQuote => "`";
|
||||
protected override string RightQuote => "`";
|
||||
|
||||
public GenericMySqlProvider() : base()
|
||||
{
|
||||
}
|
||||
protected override string BuildIdentitySql(TableColumnField keyColumn, bool batch = false)
|
||||
{
|
||||
// 单条插入和批量插入统一使用 LAST_INSERT_ID()
|
||||
// 批量场景下返回的是本次批量的起始自增值,上层根据行数自行推算每条的 Id
|
||||
return $" ;SELECT LAST_INSERT_ID();";
|
||||
}
|
||||
}
|
||||
}
|
||||
49
api_sqlsugar/VolPro.Core/Generic/GenericOracleProvider.cs
Normal file
49
api_sqlsugar/VolPro.Core/Generic/GenericOracleProvider.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using SqlSugar;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using VolPro.Core.EFDbContext;
|
||||
using VolPro.Core.Extensions.AutofacManager;
|
||||
using VolPro.Core.UserManager;
|
||||
using VolPro.Entity.DomainModels;
|
||||
|
||||
namespace VolPro.Core.Generic
|
||||
{
|
||||
/// <summary>
|
||||
/// Oracle 通用 CRUD 实现
|
||||
/// </summary>
|
||||
public class GenericOracleProvider : GenericDbProviderBase
|
||||
{
|
||||
protected override string LeftQuote => "\"";
|
||||
protected override string RightQuote => "\"";
|
||||
|
||||
public GenericOracleProvider() : base()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Oracle 12c+ 使用 RETURNING col INTO :outParam 获取自增/序列主键
|
||||
/// 批量插入时 Oracle 需 BULK COLLECT,暂不支持返回多行 id,返回 null
|
||||
/// </summary>
|
||||
protected override string BuildIdentitySql(TableColumnField keyColumn, bool batch = false)
|
||||
{
|
||||
if (batch)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return $"RETURNING {LeftQuote}{keyColumn.ColumnName}{RightQuote} INTO :newId";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Oracle 使用输出参数获取 RETURNING 值,需 Execute 后读取参数
|
||||
/// </summary>
|
||||
protected override async Task<object> ExecuteInsertWithIdentityAsync(string insertSql, string identitySql, List<SugarParameter> parameters, TableColumnField keyColumn)
|
||||
{
|
||||
// SqlSugar 输出参数:new SugarParameter(name, null, true)
|
||||
var outParam = new SugarParameter(":newId", null, true);
|
||||
parameters.Add(outParam);
|
||||
await ExcuteNonQueryAsync($"{insertSql} {identitySql}", parameters);
|
||||
return outParam.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
27
api_sqlsugar/VolPro.Core/Generic/GenericPgSqlProvider.cs
Normal file
27
api_sqlsugar/VolPro.Core/Generic/GenericPgSqlProvider.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using Dapper;
|
||||
using VolPro.Core.EFDbContext;
|
||||
using VolPro.Core.Extensions.AutofacManager;
|
||||
using VolPro.Core.UserManager;
|
||||
using VolPro.Entity.DomainModels;
|
||||
|
||||
namespace VolPro.Core.Generic
|
||||
{
|
||||
/// <summary>
|
||||
/// PostgreSql 通用 CRUD 实现
|
||||
/// </summary>
|
||||
public class GenericPgSqlProvider : GenericDbProviderBase
|
||||
{
|
||||
protected override string LeftQuote => "\"";
|
||||
protected override string RightQuote => "\"";
|
||||
|
||||
public GenericPgSqlProvider() : base()
|
||||
{
|
||||
}
|
||||
protected override string BuildIdentitySql(TableColumnField keyColumn, bool batch = false)
|
||||
{
|
||||
// PostgreSQL 单条与批量均使用 RETURNING 语法
|
||||
return $" RETURNING {LeftQuote}{keyColumn.ColumnName}{RightQuote}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
57
api_sqlsugar/VolPro.Core/Generic/GenericSqlServerProvider.cs
Normal file
57
api_sqlsugar/VolPro.Core/Generic/GenericSqlServerProvider.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using Dapper;
|
||||
using VolPro.Core.Configuration;
|
||||
using VolPro.Core.EFDbContext;
|
||||
using VolPro.Core.Extensions.AutofacManager;
|
||||
using VolPro.Core.UserManager;
|
||||
using VolPro.Entity.DomainModels;
|
||||
|
||||
namespace VolPro.Core.Generic
|
||||
{
|
||||
public class GenericSqlServerProvider : GenericDbProviderBase
|
||||
{
|
||||
protected override string LeftQuote => "[";
|
||||
protected override string RightQuote => "]";
|
||||
|
||||
public GenericSqlServerProvider() : base()
|
||||
{
|
||||
}
|
||||
|
||||
protected override string BuildPageSql(string baseWithWhereSql, string selectColumns, string orderBy, int page, int rows)
|
||||
{
|
||||
int offset = (page - 1) * rows;
|
||||
int end = page * rows;
|
||||
|
||||
if (AppSetting.UseSqlserver2008)
|
||||
{
|
||||
return $@"
|
||||
SELECT {selectColumns}
|
||||
FROM (
|
||||
SELECT ROW_NUMBER() OVER ({orderBy}) AS RowNum, {selectColumns}
|
||||
FROM (
|
||||
{baseWithWhereSql}
|
||||
) AS S
|
||||
) AS X
|
||||
WHERE X.RowNum BETWEEN {offset + 1} AND {end}
|
||||
ORDER BY X.RowNum";
|
||||
}
|
||||
return $@"
|
||||
SELECT {selectColumns}
|
||||
FROM (
|
||||
{baseWithWhereSql}
|
||||
) AS S
|
||||
{orderBy}
|
||||
OFFSET {offset} ROWS FETCH NEXT {rows} ROWS ONLY";
|
||||
}
|
||||
protected override string BuildIdentitySql(TableColumnField keyColumn, bool batch = false)
|
||||
{
|
||||
// 单条插入:使用 SCOPE_IDENTITY()
|
||||
if (!batch)
|
||||
{
|
||||
return $";SELECT SCOPE_IDENTITY();";
|
||||
}
|
||||
// 批量插入:使用 OUTPUT INSERTED.[Id],由上层拼接在 VALUES 之前
|
||||
return $" OUTPUT INSERTED.{LeftQuote}{keyColumn.ColumnName}{RightQuote}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
28
api_sqlsugar/VolPro.Core/Generic/GenericTableAsyncLocal.cs
Normal file
28
api_sqlsugar/VolPro.Core/Generic/GenericTableAsyncLocal.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System.Threading;
|
||||
|
||||
namespace VolPro.Core.Generic
|
||||
{
|
||||
public static class GenericTableAsyncLocal
|
||||
{
|
||||
private static readonly AsyncLocal<string> _currentTableName = new AsyncLocal<string>();
|
||||
|
||||
/// <summary>
|
||||
/// 当前请求对应的表名
|
||||
/// </summary>
|
||||
public static string CurrentTableName
|
||||
{
|
||||
get => _currentTableName.Value;
|
||||
set {
|
||||
if (_currentTableName.Value==null)
|
||||
{
|
||||
_currentTableName.Value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
public static void Clear()
|
||||
{
|
||||
_currentTableName.Value = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
82
api_sqlsugar/VolPro.Core/Generic/IGenericDbProvider.cs
Normal file
82
api_sqlsugar/VolPro.Core/Generic/IGenericDbProvider.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using VolPro.Core.EFDbContext;
|
||||
using VolPro.Core.Enums;
|
||||
using VolPro.Core.Filters;
|
||||
using VolPro.Core.Middleware;
|
||||
using VolPro.Core.UserManager;
|
||||
using VolPro.Core.Utilities;
|
||||
using VolPro.Entity.DomainModels;
|
||||
|
||||
namespace VolPro.Core.Generic
|
||||
{
|
||||
/// <summary>
|
||||
/// 不同数据库类型的通用 CRUD 提供接口
|
||||
/// </summary>
|
||||
public interface IGenericDbProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// 查询
|
||||
/// </summary>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="isDetail"></param>
|
||||
/// <returns></returns>
|
||||
Task<PageGridData<object>> GetPageDataAsync(PageDataOptions options, bool isDetail = false);
|
||||
/// <summary>
|
||||
/// 明细查询
|
||||
/// </summary>
|
||||
/// <param name="options"></param>
|
||||
/// <returns></returns>
|
||||
Task<PageGridData<object>> GetDetailPageAsync(PageDataOptions options);
|
||||
/// <summary>
|
||||
/// 添加
|
||||
/// </summary>
|
||||
/// <param name="saveModel"></param>
|
||||
/// <returns></returns>
|
||||
|
||||
Task<WebResponseContent> AddAsync(SaveModel saveModel);
|
||||
/// <summary>
|
||||
/// 编辑
|
||||
/// </summary>
|
||||
/// <param name="saveModel"></param>
|
||||
/// <returns></returns>
|
||||
Task<WebResponseContent> UpdateAsync(SaveModel saveModel);
|
||||
/// <summary>
|
||||
/// 删除
|
||||
/// </summary>
|
||||
/// <param name="keys"></param>
|
||||
/// <param name="delDetail"></param>
|
||||
/// <returns></returns>
|
||||
Task<WebResponseContent> DelAsync(List<object> keys, bool delDetail = true);
|
||||
/// <summary>
|
||||
/// 上传
|
||||
/// </summary>
|
||||
/// <param name="files"></param>
|
||||
/// <returns></returns>
|
||||
Task<WebResponseContent> UploadAsync(List<IFormFile> files);
|
||||
|
||||
/// <summary>
|
||||
/// 下载导入Excel模板
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
byte[] DownLoadTemplateAsync();
|
||||
/// <summary>
|
||||
/// 导入表数据Excel
|
||||
/// </summary>
|
||||
/// <param name="fileInput"></param>
|
||||
/// <returns></returns>
|
||||
Task<WebResponseContent> ImportAsync(List<IFormFile> fileInput);
|
||||
|
||||
/// <summary>
|
||||
/// 导出文件
|
||||
/// </summary>
|
||||
/// <param name="loadData"></param>
|
||||
/// <returns></returns>
|
||||
Task<byte[]> ExportAsync(PageDataOptions loadData);
|
||||
|
||||
Task<WebResponseContent> AuditAsync(object[] id, int? auditStatus, string auditReason);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user