|
|
@@ -4,6 +4,7 @@
|
|
|
//
|
|
|
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
|
|
|
|
|
+using System.Reflection;
|
|
|
using Microsoft.Data.Sqlite;
|
|
|
using DbType = SqlSugar.DbType;
|
|
|
|
|
|
@@ -109,7 +110,16 @@ public static class SqlSugarSetup
|
|
|
if (new NullabilityInfoContext().Create(type).WriteState is NullabilityState.Nullable)
|
|
|
column.IsNullable = true;
|
|
|
if (config.DbSettings.EnableUnderLine && !column.IsIgnore && !column.DbColumnName.Contains('_'))
|
|
|
- column.DbColumnName = column.DbColumnName.ToUnderLine(); // 驼峰转下划线
|
|
|
+ {
|
|
|
+ // 遗留表 NbrControl:库中为 PascalCase 审计列。若再转下划线会变成 create_time 等,
|
|
|
+ // CodeFirst 会误判缺列并执行 ADD;在要求「不改表」的部署下应保持 Pascal 物理列名。
|
|
|
+ const string nbrControlTypeFullName = "Admin.NET.Plugin.AiDOP.Entity.S0.Warehouse.AdoS0NbrControl";
|
|
|
+ var skipUnderline = type is PropertyInfo pi
|
|
|
+ && string.Equals(pi.DeclaringType?.FullName, nbrControlTypeFullName, StringComparison.Ordinal)
|
|
|
+ && (pi.Name == "CreateTime" || pi.Name == "UpdateTime" || pi.Name == "CreateUser" || pi.Name == "UpdateUser");
|
|
|
+ if (!skipUnderline)
|
|
|
+ column.DbColumnName = column.DbColumnName.ToUnderLine();
|
|
|
+ }
|
|
|
},
|
|
|
DataInfoCacheService = new SqlSugarCache(),
|
|
|
};
|
|
|
@@ -367,27 +377,43 @@ public static class SqlSugarSetup
|
|
|
int taskIndex = 0, size = viewTypeList.Count;
|
|
|
var taskList = viewTypeList.Select(viewType => Task.Run(() =>
|
|
|
{
|
|
|
- // 开始计时
|
|
|
- var stopWatch = Stopwatch.StartNew();
|
|
|
+ try
|
|
|
+ {
|
|
|
+ // 开始计时
|
|
|
+ var stopWatch = Stopwatch.StartNew();
|
|
|
|
|
|
- // 获取视图实体和配置信息
|
|
|
- var entityInfo = dbProvider.EntityMaintenance.GetEntityInfo(viewType) ?? throw new Exception("获取视图实体配置有误");
|
|
|
+ // 获取视图实体和配置信息
|
|
|
+ var entityInfo = dbProvider.EntityMaintenance.GetEntityInfo(viewType) ?? throw new Exception("获取视图实体配置有误");
|
|
|
|
|
|
- // 如果视图存在,则删除视图
|
|
|
- if (dbProvider.DbMaintenance.GetViewInfoList(false).Any(it => it.Name.EqualIgnoreCase(entityInfo.DbTableName)))
|
|
|
- dbProvider.DbMaintenance.DropView(entityInfo.DbTableName);
|
|
|
+ // 如果视图存在,则删除视图
|
|
|
+ if (dbProvider.DbMaintenance.GetViewInfoList(false).Any(it => it.Name.EqualIgnoreCase(entityInfo.DbTableName)))
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ dbProvider.DbMaintenance.DropView(entityInfo.DbTableName);
|
|
|
+ }
|
|
|
+ catch (Exception dropEx)
|
|
|
+ {
|
|
|
+ Log.Warning($"删除视图失败(可能无权限),跳过:{entityInfo.DbTableName} — {dropEx.Message}");
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- // 获取初始化视图查询SQL
|
|
|
- var sql = viewType.GetMethod(nameof(ISqlSugarView.GetQueryableSqlString))?.Invoke(Activator.CreateInstance(viewType), [dbProvider]) as string;
|
|
|
- if (string.IsNullOrWhiteSpace(sql)) throw new Exception("视图初始化Sql语句不能为空");
|
|
|
+ // 获取初始化视图查询SQL
|
|
|
+ var sql = viewType.GetMethod(nameof(ISqlSugarView.GetQueryableSqlString))?.Invoke(Activator.CreateInstance(viewType), [dbProvider]) as string;
|
|
|
+ if (string.IsNullOrWhiteSpace(sql)) throw new Exception("视图初始化Sql语句不能为空");
|
|
|
|
|
|
- // 创建视图
|
|
|
- dbProvider.Ado.ExecuteCommand($"CREATE VIEW {entityInfo.DbTableName} AS " + Environment.NewLine + " " + sql);
|
|
|
+ // 创建视图
|
|
|
+ dbProvider.Ado.ExecuteCommand($"CREATE VIEW {entityInfo.DbTableName} AS " + Environment.NewLine + " " + sql);
|
|
|
|
|
|
- // 停止计时
|
|
|
- stopWatch.Stop();
|
|
|
- Console.ForegroundColor = ConsoleColor.Green;
|
|
|
- Console.WriteLine($"初始化视图 {viewType.FullName,-58} ({dbProvider.CurrentConnectionConfig.ConfigId} - {Interlocked.Increment(ref taskIndex):D003}/{size:D003},耗时:{stopWatch.ElapsedMilliseconds:N0} ms)");
|
|
|
+ // 停止计时
|
|
|
+ stopWatch.Stop();
|
|
|
+ Console.ForegroundColor = ConsoleColor.Green;
|
|
|
+ Console.WriteLine($"初始化视图 {viewType.FullName,-58} ({dbProvider.CurrentConnectionConfig.ConfigId} - {Interlocked.Increment(ref taskIndex):D003}/{size:D003},耗时:{stopWatch.ElapsedMilliseconds:N0} ms)");
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ Log.Warning($"初始化视图失败已跳过:{viewType.FullName} — {ex.Message}");
|
|
|
+ }
|
|
|
}));
|
|
|
Task.WaitAll(taskList.ToArray());
|
|
|
|
|
|
@@ -402,8 +428,18 @@ public static class SqlSugarSetup
|
|
|
/// <param name="dbProvider"></param>
|
|
|
private static void WaitForDatabaseReady(SqlSugarScopeProvider dbProvider)
|
|
|
{
|
|
|
+ var maxWaitSeconds = 0;
|
|
|
+ _ = int.TryParse(Environment.GetEnvironmentVariable("AIDOP_DB_WAIT_MAX_SECONDS"), out maxWaitSeconds);
|
|
|
+ var deadline = maxWaitSeconds > 0 ? DateTime.UtcNow.AddSeconds(maxWaitSeconds) : DateTime.MaxValue;
|
|
|
+
|
|
|
do
|
|
|
{
|
|
|
+ if (maxWaitSeconds > 0 && DateTime.UtcNow > deadline)
|
|
|
+ {
|
|
|
+ throw new TimeoutException(
|
|
|
+ $"等待数据库就绪已超过 {maxWaitSeconds} 秒(环境变量 AIDOP_DB_WAIT_MAX_SECONDS)。请检查连接串、网络与账号权限;不需要超时限制时请移除此环境变量。");
|
|
|
+ }
|
|
|
+
|
|
|
try
|
|
|
{
|
|
|
if (dbProvider.Ado.Connection.State != ConnectionState.Open)
|
|
|
@@ -494,7 +530,15 @@ public static class SqlSugarSetup
|
|
|
if (migrateMethod == null)
|
|
|
return;
|
|
|
|
|
|
- migrateMethod.Invoke(null, [dbProvider]);
|
|
|
+ try
|
|
|
+ {
|
|
|
+ migrateMethod.Invoke(null, [dbProvider]);
|
|
|
+ }
|
|
|
+ catch (TargetInvocationException tie) when (tie.InnerException != null)
|
|
|
+ {
|
|
|
+ Log.Error($"AidopS0ExcelColumnMigration.Migrate 执行失败:{tie.InnerException.Message}", tie.InnerException);
|
|
|
+ throw tie.InnerException;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
@@ -553,11 +597,41 @@ public static class SqlSugarSetup
|
|
|
var maxDegree = ResolveInitTableMaxDegreeOfParallelism(config);
|
|
|
int count = 0, sum = entityTypes.Count;
|
|
|
|
|
|
+ var continueOnEntityFailure = ResolveContinueInitTableOnEntityFailure(config);
|
|
|
+
|
|
|
void InitOne(Type entityType)
|
|
|
{
|
|
|
+ var label = entityType.FullName ?? entityType.Name;
|
|
|
+ string tableName = "";
|
|
|
+ try
|
|
|
+ {
|
|
|
+ tableName = dbProvider.EntityMaintenance.GetEntityInfo(entityType).DbTableName;
|
|
|
+ }
|
|
|
+ catch
|
|
|
+ {
|
|
|
+ // 仅用于日志;后续 UpdateNullableColumns 仍会抛出更具体错误
|
|
|
+ }
|
|
|
+
|
|
|
Console.WriteLine($"初始化表结构 {entityType.FullName,-64} ({config.ConfigId} - {Interlocked.Increment(ref count):D003}/{sum:D003})");
|
|
|
- UpdateNullableColumns(dbProvider, entityType);
|
|
|
- InitializeTable(dbProvider, entityType, config);
|
|
|
+ try
|
|
|
+ {
|
|
|
+ UpdateNullableColumns(dbProvider, entityType);
|
|
|
+ InitializeTable(dbProvider, entityType, config);
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ Log.Error($"初始化表结构失败 Entity={label} Table={tableName} ConfigId={config.ConfigId}: {ex.Message}", ex);
|
|
|
+ if (continueOnEntityFailure)
|
|
|
+ {
|
|
|
+ Log.Warning(
|
|
|
+ "已启用跳过单表失败(TableSettings:ContinueInitTableOnEntityFailure 或环境变量 AIDOP_CODEFIRST_CONTINUE_ON_ENTITY_ERROR=1),将继续初始化其余实体;库结构可能不完整。");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ throw new InvalidOperationException(
|
|
|
+ $"表结构初始化失败: {label}(表 {tableName})。请检查上方日志中的 DDL/SQL 异常;可临时关闭 TableSettings:EnableInitTable,或仅在联调时开启 ContinueInitTableOnEntityFailure / AIDOP_CODEFIRST_CONTINUE_ON_ENTITY_ERROR=1。",
|
|
|
+ ex);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
if (maxDegree <= 1)
|
|
|
@@ -582,11 +656,19 @@ public static class SqlSugarSetup
|
|
|
|
|
|
foreach (var dbColumn in dbColumns.Where(c => !c.IsPrimarykey && entityInfo.Columns.All(u => u.DbColumnName != c.DbColumnName)))
|
|
|
{
|
|
|
- dbColumn.IsNullable = true;
|
|
|
- Retry(() =>
|
|
|
+ try
|
|
|
{
|
|
|
- dbProvider.DbMaintenance.UpdateColumn(entityInfo.DbTableName, dbColumn);
|
|
|
- }, maxRetry: 3, retryIntervalMs: 1000);
|
|
|
+ dbColumn.IsNullable = true;
|
|
|
+ Retry(() =>
|
|
|
+ {
|
|
|
+ dbProvider.DbMaintenance.UpdateColumn(entityInfo.DbTableName, dbColumn);
|
|
|
+ }, maxRetry: 3, retryIntervalMs: 1000);
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ // 低权限或列类型不支持等:不应阻塞整张表 CodeFirst
|
|
|
+ Log.Warning($"将多余列改为可空失败,已跳过:{entityInfo.DbTableName}.{dbColumn.DbColumnName} — {ex.Message}");
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -828,6 +910,16 @@ public static class SqlSugarSetup
|
|
|
return value <= 0 ? 120 : value;
|
|
|
}
|
|
|
|
|
|
+ /// <summary>
|
|
|
+ /// 单实体 CodeFirst 失败时是否跳过并继续(环境变量优先)。
|
|
|
+ /// </summary>
|
|
|
+ private static bool ResolveContinueInitTableOnEntityFailure(DbConnectionConfig config)
|
|
|
+ {
|
|
|
+ if (string.Equals(Environment.GetEnvironmentVariable("AIDOP_CODEFIRST_CONTINUE_ON_ENTITY_ERROR"), "1", StringComparison.OrdinalIgnoreCase))
|
|
|
+ return true;
|
|
|
+ return config.TableSettings?.ContinueInitTableOnEntityFailure == true;
|
|
|
+ }
|
|
|
+
|
|
|
private static bool IsRetryableDatabaseInitException(Exception ex)
|
|
|
{
|
|
|
if (ex is SqliteException sqliteException && sqliteException.SqliteErrorCode == 5)
|