Merge pull request 'feat: 抽离并重构表结构初始化、种子初始化逻辑,执行效率翻倍' (#336) from jasondom/Admin.NET.Pro:v2 into v2
Reviewed-on: https://code.adminnet.top/Admin.NET/Admin.NET.Pro/pulls/336
This commit is contained in:
commit
23ab3f9c2c
@ -22,6 +22,11 @@ public class CommonConst
|
||||
/// </summary>
|
||||
public const string SysLogCategoryName = "System.Logging.LoggingMonitor";
|
||||
|
||||
/// <summary>
|
||||
/// 最大并发数
|
||||
/// </summary>
|
||||
public const int MaxConcurrent = 8;
|
||||
|
||||
/// <summary>
|
||||
/// 事件-增加异常日志
|
||||
/// </summary>
|
||||
|
||||
@ -444,4 +444,146 @@ public static class RepositoryExtension
|
||||
throw Oops.Oh(error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化表实体
|
||||
/// </summary>
|
||||
/// <param name="dbProvider"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public static void InitTable<T>(this SqlSugarScopeProvider dbProvider) where T : class, new()
|
||||
{
|
||||
InitTable(dbProvider, typeof(T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化表实体
|
||||
/// </summary>
|
||||
/// <param name="entityType"></param>
|
||||
/// <param name="dbProvider"></param>
|
||||
/// <returns></returns>
|
||||
public static void InitTable(this SqlSugarScopeProvider dbProvider, Type entityType)
|
||||
{
|
||||
// 初始化表实体,如果存在分表特性,需要额外处理
|
||||
if (entityType.GetCustomAttribute<SplitTableAttribute>() == null)
|
||||
dbProvider.CodeFirst.InitTables(entityType);
|
||||
else
|
||||
dbProvider.CodeFirst.SplitTables().InitTables(entityType);
|
||||
|
||||
// 将不存在实体中的字段改为可空
|
||||
var entityInfo = dbProvider.EntityMaintenance.GetEntityInfo(entityType);
|
||||
var dbColumnInfos = dbProvider.DbMaintenance.GetColumnInfosByTableName(entityInfo.DbTableName) ?? [];
|
||||
foreach (var dbColumnInfo in dbColumnInfos.Where(dbColumnInfo => !dbColumnInfo.IsPrimarykey && entityInfo.Columns.All(u => u.DbColumnName != dbColumnInfo.DbColumnName)))
|
||||
{
|
||||
dbColumnInfo.IsNullable = true;
|
||||
dbProvider.DbMaintenance.UpdateColumn(entityInfo.DbTableName, dbColumnInfo);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化表种子数据
|
||||
/// </summary>
|
||||
/// <param name="db"></param>
|
||||
/// <param name="handleBefore"></param>
|
||||
/// <returns></returns>
|
||||
public static (int, int, int)? InitTableSeedData<T>(this SqlSugarScope db, Action<object> handleBefore = null)
|
||||
{
|
||||
return InitTableSeedData(db, typeof(T), handleBefore);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化表种子数据
|
||||
/// </summary>
|
||||
/// <param name="db"></param>
|
||||
/// <param name="seedType"></param>
|
||||
/// <param name="handleBefore"></param>
|
||||
/// <returns></returns>
|
||||
public static (int, int, int)? InitTableSeedData(this SqlSugarScope db, Type seedType, Action<object> handleBefore = null)
|
||||
{
|
||||
var config = db.CurrentConnectionConfig;
|
||||
var dbProvider = db.GetConnectionScope(config.ConfigId);
|
||||
|
||||
// 获取表实体类型
|
||||
var entityType = seedType.GetInterfaces().First().GetGenericArguments().First();
|
||||
|
||||
if (config.ConfigId.ToString() == SqlSugarConst.MainConfigId) // 默认库(有系统表特性、没有日志表和租户表特性)
|
||||
{
|
||||
if (entityType.GetCustomAttribute<SysTableAttribute>() == null &&
|
||||
(entityType.GetCustomAttribute<LogTableAttribute>() != null ||
|
||||
entityType.GetCustomAttribute<TenantAttribute>() != null))
|
||||
return default;
|
||||
}
|
||||
else if (config.ConfigId.ToString() == SqlSugarConst.LogConfigId) // 日志库
|
||||
{
|
||||
if (entityType.GetCustomAttribute<LogTableAttribute>() == null) return default;
|
||||
}
|
||||
else
|
||||
{
|
||||
var att = entityType.GetCustomAttribute<TenantAttribute>(); // 自定义的库
|
||||
if (att == null || att.configId.ToString() != config.ConfigId.ToString()) return default;
|
||||
}
|
||||
|
||||
var instance = Activator.CreateInstance(seedType);
|
||||
var hasDataMethod = seedType.GetMethod("HasData");
|
||||
var seedData = ((IEnumerable)hasDataMethod?.Invoke(instance, null))?.Cast<object>().ToArray() ?? [];
|
||||
if (!seedData.Any()) return default;
|
||||
|
||||
// 若实体包含Id字段,则设置为当前租户Id递增1
|
||||
var idProp = entityType.GetProperty(nameof(EntityBaseId.Id));
|
||||
var entityInfo = dbProvider.EntityMaintenance.GetEntityInfo(entityType);
|
||||
if (idProp != null && entityInfo.Columns.Any(u => u.PropertyName == nameof(EntityBaseId.Id)))
|
||||
{
|
||||
var seedId = config.ConfigId.ToLong();
|
||||
foreach (var sd in seedData)
|
||||
{
|
||||
var id = idProp!.GetValue(sd, null);
|
||||
if (id == null || id.ToString() == "0" || string.IsNullOrWhiteSpace(id.ToString()))
|
||||
idProp.SetValue(sd, ++seedId);
|
||||
}
|
||||
}
|
||||
|
||||
// 执行前处理种子数据
|
||||
if (handleBefore != null) foreach (var sd in seedData) handleBefore(sd);
|
||||
|
||||
int total, insertCount = 0, updateCount = 0;
|
||||
if (entityType.GetCustomAttribute<SplitTableAttribute>(true) != null)
|
||||
{
|
||||
// 拆分表的操作需要实体类型,而通过反射很难实现
|
||||
// 所以,这里将Init方法写在“种子数据类”内部,再传入 db 反射调用
|
||||
var hasInitMethod = seedType.GetMethod("Init");
|
||||
var parameters = new object[] { db };
|
||||
var result = hasInitMethod?.Invoke(instance, parameters) as (int, int, int)?;
|
||||
total = result?.Item1 ?? 0;
|
||||
insertCount = result?.Item2 ?? 0;
|
||||
updateCount = result?.Item3 ?? 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
var seedDataList = seedData.ToList();
|
||||
total = seedDataList.Count;
|
||||
|
||||
// 按主键进行批量增加和更新
|
||||
if (entityInfo.Columns.Any(u => u.IsPrimarykey))
|
||||
{
|
||||
// 先修改再插入,否则会更新修改时间字段
|
||||
var storage = dbProvider.StorageableByObject(seedDataList).ToStorage();
|
||||
if (seedType.GetCustomAttribute<IgnoreUpdateSeedAttribute>() == null) // 有忽略更新种子特性时则不更新
|
||||
{
|
||||
updateCount = storage.AsUpdateable.IgnoreColumns(entityInfo.Columns.Where(u => u.PropertyInfo.GetCustomAttribute<IgnoreUpdateSeedColumnAttribute>() != null)
|
||||
.Select(u => u.PropertyName).ToArray()).ExecuteCommand();
|
||||
}
|
||||
insertCount = storage.AsInsertable.ExecuteCommand();
|
||||
}
|
||||
// 无主键则只进行插入
|
||||
else
|
||||
{
|
||||
if (!dbProvider.Queryable(entityInfo.DbTableName, entityInfo.DbTableName).Any())
|
||||
{
|
||||
insertCount = seedDataList.Count;
|
||||
dbProvider.InsertableByObject(seedDataList).ExecuteCommand();
|
||||
}
|
||||
}
|
||||
}
|
||||
return (total, insertCount, updateCount);
|
||||
}
|
||||
}
|
||||
@ -391,26 +391,26 @@ public static class SqlSugarSetup
|
||||
else
|
||||
entityTypes = entityTypes.Where(u => u.GetCustomAttribute<TenantAttribute>()?.configId.ToString() == config.ConfigId.ToString()).ToList(); // 自定义的库
|
||||
|
||||
int taskIndex = 0, entityTypeCount = entityTypes.Count;
|
||||
var semaphore = new SemaphoreSlim(CommonConst.MaxConcurrent); // 并发限制数量
|
||||
int taskIndex = 0, size = entityTypes.Count;
|
||||
var taskList = entityTypes.Select(entityType => Task.Run(() =>
|
||||
{
|
||||
DateTime st = DateTime.Now;
|
||||
if (entityType.GetCustomAttribute<SplitTableAttribute>() == null)
|
||||
dbProvider.CodeFirst.InitTables(entityType);
|
||||
else
|
||||
dbProvider.CodeFirst.SplitTables().InitTables(entityType);
|
||||
// 将不存在实体中的字段改为可空
|
||||
var entityInfo = dbProvider.EntityMaintenance.GetEntityInfo(entityType);
|
||||
var dbColumnInfos = dbProvider.DbMaintenance.GetColumnInfosByTableName(entityInfo.DbTableName) ?? [];
|
||||
var tempDbColumnInfo = dbColumnInfos.Where(dbColumnInfo => !dbColumnInfo.IsPrimarykey && entityInfo.Columns.All(u => u.DbColumnName != null && u.DbColumnName?.ToLower() != dbColumnInfo.DbColumnName?.ToLower()));
|
||||
foreach (var dbColumnInfo in tempDbColumnInfo)
|
||||
semaphore.Wait(); // 获取信号量许可
|
||||
try
|
||||
{
|
||||
dbColumnInfo.IsNullable = true;
|
||||
dbProvider.DbMaintenance.UpdateColumn(entityInfo.DbTableName, dbColumnInfo);
|
||||
var stopWatch = Stopwatch.StartNew(); // 开始计时
|
||||
|
||||
dbProvider.InitTable(entityType); // 同步表结构
|
||||
|
||||
stopWatch.Stop(); // 停止计时
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.WriteLine($"初始化表 {entityType,-64} ({config.ConfigId} - {Interlocked.Increment(ref taskIndex):D003}/{size:D003}) 耗时:{stopWatch.ElapsedMilliseconds} ms");
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphore.Release(); // 释放信号量许可
|
||||
}
|
||||
DateTime et = DateTime.Now;
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.WriteLine($"初始化表 {entityType,-64} ({config.ConfigId} - {Interlocked.Increment(ref taskIndex):D003}/{entityTypeCount:D003}) 用时:{et.Subtract(st).TotalMilliseconds}ms");
|
||||
}));
|
||||
Task.WaitAll(taskList.ToArray());
|
||||
}
|
||||
@ -519,89 +519,37 @@ public static class SqlSugarSetup
|
||||
/// <param name="config"></param>
|
||||
private static void InitSeedData(SqlSugarScope db, DbConnectionConfig config)
|
||||
{
|
||||
SqlSugarScopeProvider dbProvider = db.GetConnectionScope(config.ConfigId);
|
||||
|
||||
Log.Information($"初始化种子数据 {config.DbType} - {config.ConfigId}");
|
||||
var seedDataTypes = App.EffectiveTypes.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.GetInterfaces().Any(i => i.HasImplementedRawGeneric(typeof(ISqlSugarEntitySeedData<>))))
|
||||
.Where(u => !u.IsDefined(typeof(TenantSeedAttribute), false))
|
||||
.WhereIF(config.SeedSettings.EnableIncreSeed, u => u.IsDefined(typeof(IncreSeedAttribute), false))
|
||||
.OrderBy(u => u.GetCustomAttributes(typeof(SeedDataAttribute), false).Length > 0 ? ((SeedDataAttribute)u.GetCustomAttributes(typeof(SeedDataAttribute), false)[0]).Order : 0).ToList();
|
||||
|
||||
int taskIndex = 0, seedDataTypeCount = seedDataTypes.Count;
|
||||
foreach (var seedType in seedDataTypes)
|
||||
|
||||
var semaphore = new SemaphoreSlim(CommonConst.MaxConcurrent); // 并发限制数量
|
||||
int taskIndex = 0, size = seedDataTypes.Count;
|
||||
var taskList = seedDataTypes.Select(seedType => Task.Run(() =>
|
||||
{
|
||||
var entityType = seedType.GetInterfaces().First().GetGenericArguments().First();
|
||||
if (config.ConfigId.ToString() == SqlSugarConst.MainConfigId) // 默认库(有系统表特性、没有日志表和租户表特性)
|
||||
semaphore.Wait(); // 获取信号量许可
|
||||
try
|
||||
{
|
||||
if (entityType.GetCustomAttribute<SysTableAttribute>() == null && (entityType.GetCustomAttribute<LogTableAttribute>() != null || entityType.GetCustomAttribute<TenantAttribute>() != null))
|
||||
return;
|
||||
}
|
||||
else if (config.ConfigId.ToString() == SqlSugarConst.LogConfigId) // 日志库
|
||||
{
|
||||
if (entityType.GetCustomAttribute<LogTableAttribute>() == null)
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
var att = entityType.GetCustomAttribute<TenantAttribute>(); // 自定义的库
|
||||
if (att == null || att.configId.ToString() != config.ConfigId.ToString()) return;
|
||||
}
|
||||
var stopWatch = Stopwatch.StartNew(); // 开始计时
|
||||
|
||||
var instance = Activator.CreateInstance(seedType);
|
||||
var hasDataMethod = seedType.GetMethod("HasData");
|
||||
var seedData = ((IEnumerable)hasDataMethod?.Invoke(instance, null))?.Cast<object>();
|
||||
if (seedData == null) return;
|
||||
// 种子数据初始化
|
||||
var tuple = db.InitTableSeedData(seedType);
|
||||
if (tuple == null) return;
|
||||
|
||||
// 若实体包含Id字段,则设置为当前租户Id递增1
|
||||
var entityInfo = dbProvider.EntityMaintenance.GetEntityInfo(entityType);
|
||||
if (entityInfo.Columns.Any(u => u.PropertyName == nameof(EntityBaseId.Id)))
|
||||
{
|
||||
var seedId = config.ConfigId.ToLong();
|
||||
foreach (var sd in seedData)
|
||||
{
|
||||
var id = sd.GetType().GetProperty(nameof(EntityBaseId.Id))!.GetValue(sd, null);
|
||||
if (id != null && (id.ToString() == "0" || string.IsNullOrWhiteSpace(id.ToString())))
|
||||
sd.GetType().GetProperty(nameof(EntityBaseId.Id))!.SetValue(sd, ++seedId);
|
||||
}
|
||||
}
|
||||
stopWatch.Stop(); // 停止计时
|
||||
|
||||
if (entityType.GetCustomAttribute<SplitTableAttribute>(true) != null)
|
||||
{
|
||||
// 拆分表的操作需要实体类型,而通过反射很难实现
|
||||
// 所以,这里将Init方法写在“种子数据类”内部,再传入 db 反射调用
|
||||
var hasInitMethod = seedType.GetMethod("Init");
|
||||
var parameters = new object[] { db };
|
||||
hasInitMethod?.Invoke(instance, parameters);
|
||||
}
|
||||
else
|
||||
{
|
||||
var seedDataList = seedData.ToList();
|
||||
int updateCount = 0, insertCount = 0;
|
||||
// 按主键进行批量增加和更新
|
||||
if (entityInfo.Columns.Any(u => u.IsPrimarykey))
|
||||
{
|
||||
// 先修改再插入,否则会更新修改时间字段
|
||||
var storage = dbProvider.StorageableByObject(seedDataList).ToStorage();
|
||||
if (seedType.GetCustomAttribute<IgnoreUpdateSeedAttribute>() == null) // 有忽略更新种子特性时则不更新
|
||||
{
|
||||
updateCount = storage.AsUpdateable.IgnoreColumns(entityInfo.Columns.Where(u => u.PropertyInfo.GetCustomAttribute<IgnoreUpdateSeedColumnAttribute>() != null)
|
||||
.Select(u => u.PropertyName).ToArray()).ExecuteCommand();
|
||||
}
|
||||
insertCount = storage.AsInsertable.ExecuteCommand();
|
||||
}
|
||||
// 无主键则只进行插入
|
||||
else
|
||||
{
|
||||
if (!dbProvider.Queryable(entityInfo.DbTableName, entityInfo.DbTableName).Any())
|
||||
{
|
||||
insertCount = seedDataList.Count;
|
||||
dbProvider.InsertableByObject(seedDataList).ExecuteCommand();
|
||||
}
|
||||
}
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.WriteLine($"初始化种子数据 {seedType.FullName,-58} ({config.ConfigId} - {Interlocked.Increment(ref taskIndex):D003}/{seedDataTypeCount:D003},数据量:{seedDataList.Count:D003},新增 {insertCount:D003} 条记录,更新 {updateCount:D003} 条记录)");
|
||||
Console.WriteLine($"初始化种子数据 {seedType.FullName,-58} ({config.ConfigId} - {Interlocked.Increment(ref taskIndex):D003}/{size:D003},数据量:{tuple.Value.Item1:D003},新增 {tuple.Value.Item2:D003} 条记录,更新 {tuple.Value.Item3:D003} 条记录,耗时:{stopWatch.ElapsedMilliseconds:N0} ms)");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphore.Release(); // 释放信号量许可
|
||||
}
|
||||
}));
|
||||
Task.WaitAll(taskList.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -649,7 +597,7 @@ public static class SqlSugarSetup
|
||||
var seedDataTypes = App.EffectiveTypes.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.GetInterfaces().Any(i => i.HasImplementedRawGeneric(typeof(ISqlSugarEntitySeedData<>))))
|
||||
.Where(u => u.IsDefined(typeof(TenantSeedAttribute), false))
|
||||
.OrderBy(u => u.GetCustomAttributes(typeof(SeedDataAttribute), false).Length > 0 ? ((SeedDataAttribute)u.GetCustomAttributes(typeof(SeedDataAttribute), false)[0]).Order : 0).ToList();
|
||||
if (seedDataTypes == null || seedDataTypes.Count < 1) return;
|
||||
if (seedDataTypes.Count < 1) return;
|
||||
|
||||
var db = iTenant.GetConnectionScope(dbConfigId);
|
||||
foreach (var seedType in seedDataTypes)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user