Sfoglia il codice sorgente

!1353 :blush: 1、代码生成=>编辑,保存的时候更新配置。 2、代码生成=>配置=>外键,添加自定义链接字段
Merge pull request !1353 from CyrusZhou/next

zuohuaijun 1 anno fa
parent
commit
63c4c917b3

+ 7 - 0
Admin.NET/Admin.NET.Core/Entity/SysCodeGenConfig.cs

@@ -81,6 +81,13 @@ public partial class SysCodeGenConfig : EntityBase
     [MaxLength(64)]
     public string? FkColumnName { get; set; }
 
+    /// <summary>
+    /// 外键显示字段
+    /// </summary>
+    [SugarColumn(ColumnDescription = "外键链接字段", Length = 64)]
+    [MaxLength(64)]
+    public string? FkLinkColumnName { get; set; }
+    
     /// <summary>
     /// 外键显示字段.NET类型
     /// </summary>

+ 5 - 0
Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/CodeGenConfig.cs

@@ -76,6 +76,11 @@ public class CodeGenConfig
     /// 外键显示字段
     /// </summary>
     public string FkColumnName { get; set; }
+    
+    /// <summary>
+    /// 外键链接字段
+    /// </summary>
+    public string FkLinkColumnName { get; set; }
 
     /// <summary>
     /// 外键显示字段(首字母小写)

+ 181 - 181
Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/CodeGenInput.cs

@@ -1,182 +1,182 @@
-// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
-//
-// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
-//
-// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
-
-namespace Admin.NET.Core.Service;
-
-/// <summary>
-/// 代码生成参数类
-/// </summary>
-public class CodeGenInput : BasePageInput
-{
-    /// <summary>
-    /// 作者姓名
-    /// </summary>
-    public virtual string AuthorName { get; set; }
-
-    /// <summary>
-    /// 类名
-    /// </summary>
-    public virtual string ClassName { get; set; }
-
-    /// <summary>
-    /// 是否移除表前缀
-    /// </summary>
-    public virtual string TablePrefix { get; set; }
-
-    /// <summary>
-    /// 库定位器名
-    /// </summary>
-    public virtual string ConfigId { get; set; }
-
-    /// <summary>
-    /// 数据库名(保留字段)
-    /// </summary>
-    public virtual string DbName { get; set; }
-
-    /// <summary>
-    /// 数据库类型
-    /// </summary>
-    public virtual string DbType { get; set; }
-
-    /// <summary>
-    /// 数据库链接
-    /// </summary>
-    public virtual string ConnectionString { get; set; }
-
-    /// <summary>
-    /// 生成方式
-    /// </summary>
-    public virtual string GenerateType { get; set; }
-
-    /// <summary>
-    /// 数据库表名
-    /// </summary>
-    public virtual string TableName { get; set; }
-
-    /// <summary>
-    /// 命名空间
-    /// </summary>
-    public virtual string NameSpace { get; set; }
-
-    /// <summary>
-    /// 业务名(业务代码包名称)
-    /// </summary>
-    public virtual string BusName { get; set; }
-
-    /// <summary>
-    /// 功能名(数据库表名称)
-    /// </summary>
-    public virtual string TableComment { get; set; }
-
-    /// <summary>
-    /// 菜单应用分类(应用编码)
-    /// </summary>
-    public virtual string MenuApplication { get; set; }
-
-    /// <summary>
-    /// 是否生成菜单
-    /// </summary>
-    public virtual bool GenerateMenu { get; set; }
-
-    /// <summary>
-    /// 菜单父级
-    /// </summary>
-    public virtual long? MenuPid { get; set; }
-
-    /// <summary>
-    /// 页面目录
-    /// </summary>
-    public virtual string PagePath { get; set; }
-
-    /// <summary>
-    /// 支持打印类型
-    /// </summary>
-    public virtual string PrintType { get; set; }
-
-    /// <summary>
-    /// 打印模版名称
-    /// </summary>
-    public virtual string PrintName { get; set; }
-}
-
-public class AddCodeGenInput : CodeGenInput
-{
-    /// <summary>
-    /// 数据库表名
-    /// </summary>
-    [Required(ErrorMessage = "数据库表名不能为空")]
-    public override string TableName { get; set; }
-
-    /// <summary>
-    /// 业务名(业务代码包名称)
-    /// </summary>
-    [Required(ErrorMessage = "业务名不能为空")]
-    public override string BusName { get; set; }
-
-    /// <summary>
-    /// 命名空间
-    /// </summary>
-    [Required(ErrorMessage = "命名空间不能为空")]
-    public override string NameSpace { get; set; }
-
-    /// <summary>
-    /// 作者姓名
-    /// </summary>
-    [Required(ErrorMessage = "作者姓名不能为空")]
-    public override string AuthorName { get; set; }
-
-    ///// <summary>
-    ///// 类名
-    ///// </summary>
-    //[Required(ErrorMessage = "类名不能为空")]
-    //public override string ClassName { get; set; }
-
-    ///// <summary>
-    ///// 是否移除表前缀
-    ///// </summary>
-    //[Required(ErrorMessage = "是否移除表前缀不能为空")]
-    //public override string TablePrefix { get; set; }
-
-    /// <summary>
-    /// 生成方式
-    /// </summary>
-    [Required(ErrorMessage = "生成方式不能为空")]
-    public override string GenerateType { get; set; }
-
-    ///// <summary>
-    ///// 功能名(数据库表名称)
-    ///// </summary>
-    //[Required(ErrorMessage = "数据库表名不能为空")]
-    //public override string TableComment { get; set; }
-
-    /// <summary>
-    /// 是否生成菜单
-    /// </summary>
-    [Required(ErrorMessage = "是否生成菜单不能为空")]
-    public override bool GenerateMenu { get; set; }
-}
-
-public class DeleteCodeGenInput
-{
-    /// <summary>
-    /// 代码生成器Id
-    /// </summary>
-    [Required(ErrorMessage = "代码生成器Id不能为空")]
-    public long Id { get; set; }
-}
-
-public class UpdateCodeGenInput : CodeGenInput
-{
-    /// <summary>
-    /// 代码生成器Id
-    /// </summary>
-    [Required(ErrorMessage = "代码生成器Id不能为空")]
-    public long Id { get; set; }
-}
-
-public class QueryCodeGenInput : DeleteCodeGenInput
-{
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Core.Service;
+
+/// <summary>
+/// 代码生成参数类
+/// </summary>
+public class CodeGenInput : BasePageInput
+{
+    /// <summary>
+    /// 作者姓名
+    /// </summary>
+    public virtual string AuthorName { get; set; }
+
+    /// <summary>
+    /// 类名
+    /// </summary>
+    public virtual string ClassName { get; set; }
+
+    /// <summary>
+    /// 是否移除表前缀
+    /// </summary>
+    public virtual string TablePrefix { get; set; }
+
+    /// <summary>
+    /// 库定位器名
+    /// </summary>
+    public virtual string ConfigId { get; set; }
+
+    /// <summary>
+    /// 数据库名(保留字段)
+    /// </summary>
+    public virtual string DbName { get; set; }
+
+    /// <summary>
+    /// 数据库类型
+    /// </summary>
+    public virtual string DbType { get; set; }
+
+    /// <summary>
+    /// 数据库链接
+    /// </summary>
+    public virtual string ConnectionString { get; set; }
+
+    /// <summary>
+    /// 生成方式
+    /// </summary>
+    public virtual string GenerateType { get; set; }
+
+    /// <summary>
+    /// 数据库表名
+    /// </summary>
+    public virtual string TableName { get; set; }
+
+    /// <summary>
+    /// 命名空间
+    /// </summary>
+    public virtual string NameSpace { get; set; }
+
+    /// <summary>
+    /// 业务名(业务代码包名称)
+    /// </summary>
+    public virtual string BusName { get; set; }
+
+    /// <summary>
+    /// 功能名(数据库表名称)
+    /// </summary>
+    public virtual string TableComment { get; set; }
+
+    /// <summary>
+    /// 菜单应用分类(应用编码)
+    /// </summary>
+    public virtual string MenuApplication { get; set; }
+
+    /// <summary>
+    /// 是否生成菜单
+    /// </summary>
+    public virtual bool GenerateMenu { get; set; }
+
+    /// <summary>
+    /// 菜单父级
+    /// </summary>
+    public virtual long? MenuPid { get; set; }
+
+    /// <summary>
+    /// 页面目录
+    /// </summary>
+    public virtual string PagePath { get; set; }
+
+    /// <summary>
+    /// 支持打印类型
+    /// </summary>
+    public virtual string PrintType { get; set; }
+
+    /// <summary>
+    /// 打印模版名称
+    /// </summary>
+    public virtual string PrintName { get; set; }
+}
+
+public class AddCodeGenInput : CodeGenInput
+{
+    /// <summary>
+    /// 数据库表名
+    /// </summary>
+    [Required(ErrorMessage = "数据库表名不能为空")]
+    public override string TableName { get; set; }
+
+    /// <summary>
+    /// 业务名(业务代码包名称)
+    /// </summary>
+    [Required(ErrorMessage = "业务名不能为空")]
+    public override string BusName { get; set; }
+
+    /// <summary>
+    /// 命名空间
+    /// </summary>
+    [Required(ErrorMessage = "命名空间不能为空")]
+    public override string NameSpace { get; set; }
+
+    /// <summary>
+    /// 作者姓名
+    /// </summary>
+    [Required(ErrorMessage = "作者姓名不能为空")]
+    public override string AuthorName { get; set; }
+
+    ///// <summary>
+    ///// 类名
+    ///// </summary>
+    //[Required(ErrorMessage = "类名不能为空")]
+    //public override string ClassName { get; set; }
+
+    ///// <summary>
+    ///// 是否移除表前缀
+    ///// </summary>
+    //[Required(ErrorMessage = "是否移除表前缀不能为空")]
+    //public override string TablePrefix { get; set; }
+
+    /// <summary>
+    /// 生成方式
+    /// </summary>
+    [Required(ErrorMessage = "生成方式不能为空")]
+    public override string GenerateType { get; set; }
+
+    ///// <summary>
+    ///// 功能名(数据库表名称)
+    ///// </summary>
+    //[Required(ErrorMessage = "数据库表名不能为空")]
+    //public override string TableComment { get; set; }
+
+    /// <summary>
+    /// 是否生成菜单
+    /// </summary>
+    [Required(ErrorMessage = "是否生成菜单不能为空")]
+    public override bool GenerateMenu { get; set; }
+}
+
+public class DeleteCodeGenInput
+{
+    /// <summary>
+    /// 代码生成器Id
+    /// </summary>
+    [Required(ErrorMessage = "代码生成器Id不能为空")]
+    public long Id { get; set; }
+}
+
+public class UpdateCodeGenInput : AddCodeGenInput
+{
+    /// <summary>
+    /// 代码生成器Id
+    /// </summary>
+    [Required(ErrorMessage = "代码生成器Id不能为空")]
+    public long Id { get; set; }
+}
+
+public class QueryCodeGenInput : DeleteCodeGenInput
+{
 }

+ 5 - 1
Admin.NET/Admin.NET.Core/Service/CodeGen/SysCodeGenService.cs

@@ -77,7 +77,11 @@ public class SysCodeGenService : IDynamicApiController, ITransient
         if (isExist)
             throw Oops.Oh(ErrorCodeEnum.D1400);
 
-        await _db.Updateable(input.Adapt<SysCodeGen>()).ExecuteCommandAsync();
+        var codeGen = input.Adapt<SysCodeGen>();
+        await _db.Updateable(codeGen).ExecuteCommandAsync();
+        // 加入配置表中
+        await _codeGenConfigService.DeleteCodeGenConfig(codeGen.Id);
+        _codeGenConfigService.AddList(GetColumnList(input), codeGen);
     }
 
     /// <summary>

+ 423 - 417
Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarSetup.cs

@@ -1,418 +1,424 @@
-// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
-//
-// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
-//
-// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
-
-namespace Admin.NET.Core;
-
-public static class SqlSugarSetup
-{
-    // 多租户实例
-    public static ITenant ITenant { get; set; }
-
-    /// <summary>
-    /// SqlSugar 上下文初始化
-    /// </summary>
-    /// <param name="services"></param>
-    public static void AddSqlSugar(this IServiceCollection services)
-    {
-        // 注册雪花Id
-        var snowIdOpt = App.GetConfig<SnowIdOptions>("SnowId", true);
-        YitIdHelper.SetIdGenerator(snowIdOpt);
-
-        // 自定义 SqlSugar 雪花ID算法
-        SnowFlakeSingle.WorkId = snowIdOpt.WorkerId;
-        StaticConfig.CustomSnowFlakeFunc = () =>
-        {
-            return YitIdHelper.NextId();
-        };
-
-        var dbOptions = App.GetConfig<DbConnectionOptions>("DbConnection", true);
-        dbOptions.ConnectionConfigs.ForEach(SetDbConfig);
-
-        SqlSugarScope sqlSugar = new(dbOptions.ConnectionConfigs.Adapt<List<ConnectionConfig>>(), db =>
-        {
-            dbOptions.ConnectionConfigs.ForEach(config =>
-            {
-                var dbProvider = db.GetConnectionScope(config.ConfigId);
-                SetDbAop(dbProvider, dbOptions.EnableConsoleSql);
-                SetDbDiffLog(dbProvider, config);
-            });
-        });
-        ITenant = sqlSugar;
-
-        services.AddSingleton<ISqlSugarClient>(sqlSugar); // 单例注册
-        services.AddScoped(typeof(SqlSugarRepository<>)); // 仓储注册
-        services.AddUnitOfWork<SqlSugarUnitOfWork>(); // 事务与工作单元注册
-
-        // 初始化数据库表结构及种子数据
-        dbOptions.ConnectionConfigs.ForEach(config =>
-        {
-            InitDatabase(sqlSugar, config);
-        });
-    }
-
-    /// <summary>
-    /// 配置连接属性
-    /// </summary>
-    /// <param name="config"></param>
-    public static void SetDbConfig(DbConnectionConfig config)
-    {
-        var configureExternalServices = new ConfigureExternalServices
-        {
-            EntityNameService = (type, entity) => // 处理表
-            {
-                entity.IsDisabledDelete = true; // 禁止删除非 sqlsugar 创建的列
-                // 只处理贴了特性[SugarTable]表
-                if (!type.GetCustomAttributes<SugarTable>().Any())
-                    return;
-                if (config.DbSettings.EnableUnderLine && !entity.DbTableName.Contains('_'))
-                    entity.DbTableName = UtilMethods.ToUnderLine(entity.DbTableName); // 驼峰转下划线
-            },
-            EntityService = (type, column) => // 处理列
-            {
-                // 只处理贴了特性[SugarColumn]列
-                if (!type.GetCustomAttributes<SugarColumn>().Any())
-                    return;
-                if (new NullabilityInfoContext().Create(type).WriteState is NullabilityState.Nullable)
-                    column.IsNullable = true;
-                if (config.DbSettings.EnableUnderLine && !column.IsIgnore && !column.DbColumnName.Contains('_'))
-                    column.DbColumnName = UtilMethods.ToUnderLine(column.DbColumnName); // 驼峰转下划线
-            },
-            DataInfoCacheService = new SqlSugarCache(),
-        };
-        config.ConfigureExternalServices = configureExternalServices;
-        config.InitKeyType = InitKeyType.Attribute;
-        config.IsAutoCloseConnection = true;
-        config.MoreSettings = new ConnMoreSettings
-        {
-            IsAutoRemoveDataCache = true, // 启用自动删除缓存,所有增删改会自动调用.RemoveDataCache()
-            IsAutoDeleteQueryFilter = true, // 启用删除查询过滤器
-            IsAutoUpdateQueryFilter = true, // 启用更新查询过滤器
-            SqlServerCodeFirstNvarchar = true // 采用Nvarchar
-        };
-    }
-
-    /// <summary>
-    /// 配置Aop
-    /// </summary>
-    /// <param name="db"></param>
-    /// <param name="enableConsoleSql"></param>
-    public static void SetDbAop(SqlSugarScopeProvider db, bool enableConsoleSql)
-    {
-        // 设置超时时间
-        db.Ado.CommandTimeOut = 30;
-
-        // 打印SQL语句
-        if (enableConsoleSql)
-        {
-            db.Aop.OnLogExecuting = (sql, pars) =>
-            {
-                //// 若参数值超过100个字符则进行截取
-                //foreach (var par in pars)
-                //{
-                //    if (par.DbType != System.Data.DbType.String || par.Value == null) continue;
-                //    if (par.Value.ToString().Length > 100)
-                //        par.Value = string.Concat(par.Value.ToString()[..100], "......");
-                //}
-
-                var log = $"【{DateTime.Now}——执行SQL】\r\n{UtilMethods.GetNativeSql(sql, pars)}\r\n";
-                var originColor = Console.ForegroundColor;
-                if (sql.StartsWith("SELECT", StringComparison.OrdinalIgnoreCase))
-                    Console.ForegroundColor = ConsoleColor.Green;
-                if (sql.StartsWith("UPDATE", StringComparison.OrdinalIgnoreCase) || sql.StartsWith("INSERT", StringComparison.OrdinalIgnoreCase))
-                    Console.ForegroundColor = ConsoleColor.Yellow;
-                if (sql.StartsWith("DELETE", StringComparison.OrdinalIgnoreCase))
-                    Console.ForegroundColor = ConsoleColor.Red;
-                Console.WriteLine(log);
-                Console.ForegroundColor = originColor;
-                App.PrintToMiniProfiler("SqlSugar", "Info", log);
-            };
-            db.Aop.OnError = ex =>
-            {
-                if (ex.Parametres == null) return;
-                var log = $"【{DateTime.Now}——错误SQL】\r\n{UtilMethods.GetNativeSql(ex.Sql, (SugarParameter[])ex.Parametres)}\r\n";
-                Log.Error(log, ex);
-                App.PrintToMiniProfiler("SqlSugar", "Error", log);
-            };
-            db.Aop.OnLogExecuted = (sql, pars) =>
-            {
-                //// 若参数值超过100个字符则进行截取
-                //foreach (var par in pars)
-                //{
-                //    if (par.DbType != System.Data.DbType.String || par.Value == null) continue;
-                //    if (par.Value.ToString().Length > 100)
-                //        par.Value = string.Concat(par.Value.ToString()[..100], "......");
-                //}
-
-                // 执行时间超过5秒时
-                if (db.Ado.SqlExecutionTime.TotalSeconds > 5)
-                {
-                    var fileName = db.Ado.SqlStackTrace.FirstFileName; // 文件名
-                    var fileLine = db.Ado.SqlStackTrace.FirstLine; // 行号
-                    var firstMethodName = db.Ado.SqlStackTrace.FirstMethodName; // 方法名
-                    var log = $"【{DateTime.Now}——超时SQL】\r\n【所在文件名】:{fileName}\r\n【代码行数】:{fileLine}\r\n【方法名】:{firstMethodName}\r\n" + $"【SQL语句】:{UtilMethods.GetNativeSql(sql, pars)}";
-                    Log.Warning(log);
-                    App.PrintToMiniProfiler("SqlSugar", "Slow", log);
-                }
-            };
-        }
-        // 数据审计
-        db.Aop.DataExecuting = (oldValue, entityInfo) =>
-        {
-            // 新增/插入
-            if (entityInfo.OperationType == DataFilterType.InsertByObject)
-            {
-                // 若主键是长整型且空则赋值雪花Id
-                if (entityInfo.EntityColumnInfo.IsPrimarykey && !entityInfo.EntityColumnInfo.IsIdentity && entityInfo.EntityColumnInfo.PropertyInfo.PropertyType == typeof(long))
-                {
-                    var id = entityInfo.EntityColumnInfo.PropertyInfo.GetValue(entityInfo.EntityValue);
-                    if (id == null || (long)id == 0)
-                        entityInfo.SetValue(YitIdHelper.NextId());
-                }
-                // 若创建时间为空则赋值当前时间
-                else if (entityInfo.PropertyName == nameof(EntityBase.CreateTime))
-                {
-                    var createTime = entityInfo.EntityColumnInfo.PropertyInfo.GetValue(entityInfo.EntityValue)!;
-                    if (createTime == null || createTime.Equals(DateTime.MinValue))
-                        entityInfo.SetValue(DateTime.Now);
-                }
-                // 若当前用户非空(web线程时)
-                if (App.User != null)
-                {
-                    dynamic entityValue = entityInfo.EntityValue;
-                    if (entityInfo.PropertyName == nameof(EntityTenantId.TenantId))
-                    {
-                        var tenantId = entityValue.TenantId;
-                        if (tenantId == null || tenantId == 0)
-                            entityInfo.SetValue(App.User.FindFirst(ClaimConst.TenantId)?.Value);
-                    }
-                    else if (entityInfo.PropertyName == nameof(EntityBase.CreateUserId))
-                    {
-                        var createUserId = entityValue.CreateUserId;
-                        if (createUserId == 0 || createUserId == null)
-                            entityInfo.SetValue(App.User.FindFirst(ClaimConst.UserId)?.Value);
-                    }
-                    else if (entityInfo.PropertyName == nameof(EntityBase.CreateUserName))
-                    {
-                        var createUserName = entityValue.CreateUserName;
-                        if (string.IsNullOrEmpty(createUserName))
-                            entityInfo.SetValue(App.User.FindFirst(ClaimConst.RealName)?.Value);
-                    }
-                    else if (entityInfo.PropertyName == nameof(EntityBaseData.CreateOrgId))
-                    {
-                        var createOrgId = entityValue.CreateOrgId;
-                        if (createOrgId == 0 || createOrgId == null)
-                            entityInfo.SetValue(App.User.FindFirst(ClaimConst.OrgId)?.Value);
-                    }
-                    else if (entityInfo.PropertyName == nameof(EntityBaseData.CreateOrgName))
-                    {
-                        var createOrgName = entityValue.CreateOrgName;
-                        if (string.IsNullOrEmpty(createOrgName))
-                            entityInfo.SetValue(App.User.FindFirst(ClaimConst.OrgName)?.Value);
-                    }
-                }
-            }
-            // 编辑/更新
-            else if (entityInfo.OperationType == DataFilterType.UpdateByObject)
-            {
-                if (entityInfo.PropertyName == nameof(EntityBase.UpdateTime))
-                    entityInfo.SetValue(DateTime.Now);
-                else if (entityInfo.PropertyName == nameof(EntityBase.UpdateUserId))
-                    entityInfo.SetValue(App.User?.FindFirst(ClaimConst.UserId)?.Value);
-                else if (entityInfo.PropertyName == nameof(EntityBase.UpdateUserName))
-                    entityInfo.SetValue(App.User?.FindFirst(ClaimConst.RealName)?.Value);
-            }
-        };
-
-        // 超管排除其他过滤器
-        if (App.User?.FindFirst(ClaimConst.AccountType)?.Value == ((int)AccountTypeEnum.SuperAdmin).ToString())
-            return;
-
-        // 配置假删除过滤器
-        db.QueryFilter.AddTableFilter<IDeletedFilter>(u => u.IsDelete == false);
-
-        // 配置租户过滤器
-        var tenantId = App.User?.FindFirst(ClaimConst.TenantId)?.Value;
-        if (!string.IsNullOrWhiteSpace(tenantId))
-            db.QueryFilter.AddTableFilter<ITenantIdFilter>(u => u.TenantId == long.Parse(tenantId));
-
-        // 配置用户机构(数据范围)过滤器
-        SqlSugarFilter.SetOrgEntityFilter(db);
-
-        // 配置自定义过滤器
-        SqlSugarFilter.SetCustomEntityFilter(db);
-    }
-
-    /// <summary>
-    /// 开启库表差异化日志
-    /// </summary>
-    /// <param name="db"></param>
-    /// <param name="config"></param>
-    private static void SetDbDiffLog(SqlSugarScopeProvider db, DbConnectionConfig config)
-    {
-        if (!config.DbSettings.EnableDiffLog) return;
-
-        db.Aop.OnDiffLogEvent = async u =>
-        {
-            var logDiff = new SysLogDiff
-            {
-                // 操作后记录(字段描述、列名、值、表名、表描述)
-                AfterData = JSON.Serialize(u.AfterData),
-                // 操作前记录(字段描述、列名、值、表名、表描述)
-                BeforeData = JSON.Serialize(u.BeforeData),
-                // 传进来的对象(如果对象为空,则使用首个数据的表名作为业务对象)
-                BusinessData = u.BusinessData == null ? u.AfterData.FirstOrDefault()?.TableName : JSON.Serialize(u.BusinessData),
-                // 枚举(insert、update、delete)
-                DiffType = u.DiffType.ToString(),
-                Sql = UtilMethods.GetNativeSql(u.Sql, u.Parameters),
-                Parameters = JSON.Serialize(u.Parameters),
-                Elapsed = u.Time == null ? 0 : (long)u.Time.Value.TotalMilliseconds
-            };
-            var logDb = ITenant.IsAnyConnection(SqlSugarConst.LogConfigId) ? ITenant.GetConnectionScope(SqlSugarConst.LogConfigId) : db;
-            await logDb.CopyNew().Insertable(logDiff).ExecuteCommandAsync();
-            Console.ForegroundColor = ConsoleColor.Red;
-            Console.WriteLine(DateTime.Now + $"\r\n*****开始差异日志*****\r\n{Environment.NewLine}{JSON.Serialize(logDiff)}{Environment.NewLine}*****结束差异日志*****\r\n");
-        };
-    }
-
-    /// <summary>
-    /// 初始化数据库
-    /// </summary>
-    /// <param name="db"></param>
-    /// <param name="config"></param>
-    private static void InitDatabase(SqlSugarScope db, DbConnectionConfig config)
-    {
-        SqlSugarScopeProvider dbProvider = db.GetConnectionScope(config.ConfigId);
-
-        // 初始化/创建数据库
-        if (config.DbSettings.EnableInitDb)
-        {
-            Log.Information($"初始化数据库 {config.DbType} - {config.ConfigId} - {config.ConnectionString}");
-            if (config.DbType != SqlSugar.DbType.Oracle)
-                dbProvider.DbMaintenance.CreateDatabase();
-        }
-
-        // 初始化表结构
-        if (config.TableSettings.EnableInitTable)
-        {
-            Log.Information($"初始化表结构 {config.DbType} - {config.ConfigId}");
-            var entityTypes = App.EffectiveTypes.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.IsDefined(typeof(SugarTable), false))
-                .Where(u => !u.GetCustomAttributes<IgnoreTableAttribute>().Any())
-                .WhereIF(config.TableSettings.EnableIncreTable, u => u.IsDefined(typeof(IncreTableAttribute), false)).ToList();
-
-            if (config.ConfigId.ToString() == SqlSugarConst.MainConfigId) // 默认库(有系统表特性、没有日志表和租户表特性)
-                entityTypes = entityTypes.Where(u => u.GetCustomAttributes<SysTableAttribute>().Any() || (!u.GetCustomAttributes<LogTableAttribute>().Any() && !u.GetCustomAttributes<TenantAttribute>().Any())).ToList();
-            else if (config.ConfigId.ToString() == SqlSugarConst.LogConfigId) // 日志库
-                entityTypes = entityTypes.Where(u => u.GetCustomAttributes<LogTableAttribute>().Any()).ToList();
-            else
-                entityTypes = entityTypes.Where(u => u.GetCustomAttribute<TenantAttribute>()?.configId.ToString() == config.ConfigId.ToString()).ToList(); // 自定义的库
-
-            int count = 0, sum = entityTypes.Count;
-            foreach (var entityType in entityTypes)
-            {
-                Console.WriteLine($"创建表 {entityType} ({config.ConfigId} - {++count}/{sum})");
-                if (entityType.GetCustomAttribute<SplitTableAttribute>() == null)
-                    dbProvider.CodeFirst.InitTables(entityType);
-                else
-                    dbProvider.CodeFirst.SplitTables().InitTables(entityType);
-            }
-        }
-
-        // 初始化种子数据
-        if (config.SeedSettings.EnableInitSeed)
-        {
-            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<>))))
-                .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 count = 0, sum = seedDataTypes.Count;
-            foreach (var seedType in seedDataTypes)
-            {
-                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))
-                        continue;
-                }
-                else if (config.ConfigId.ToString() == SqlSugarConst.LogConfigId) // 日志库
-                {
-                    if (entityType.GetCustomAttribute<LogTableAttribute>() == null)
-                        continue;
-                }
-                else
-                {
-                    var att = entityType.GetCustomAttribute<TenantAttribute>(); // 自定义的库
-                    if (att == null || att.configId.ToString() != config.ConfigId.ToString()) continue;
-                }
-
-                var instance = Activator.CreateInstance(seedType);
-                var hasDataMethod = seedType.GetMethod("HasData");
-                var seedData = ((IEnumerable)hasDataMethod?.Invoke(instance, null))?.Cast<object>();
-                if (seedData == null) continue;
-
-                var entityInfo = dbProvider.EntityMaintenance.GetEntityInfo(entityType);
-                Console.WriteLine($"添加数据 {entityInfo.DbTableName} ({config.ConfigId} - {++count}/{sum})");
-
-                if (entityType.GetCustomAttribute<SplitTableAttribute>(true) != null)
-                {
-                    //拆分表的操作需要实体类型,而通过反射很难实现
-                    //所以,这里将Init方法写在“种子数据类”内部,再传入 db 反射调用
-                    var hasInitMethod = seedType.GetMethod("Init");
-                    var parameters = new object[] { db };
-                    hasInitMethod?.Invoke(instance, parameters);
-                }
-                else
-                {
-                    if (entityInfo.Columns.Any(u => u.IsPrimarykey))
-                    {
-                        // 按主键进行批量增加和更新
-                        var storage = dbProvider.StorageableByObject(seedData.ToList()).ToStorage();
-                        storage.AsInsertable.ExecuteCommand();
-                        if (seedType.GetCustomAttribute<IgnoreUpdateSeedAttribute>() == null) // 有忽略更新种子特性时则不更新
-                            storage.AsUpdateable.IgnoreColumns(entityInfo.Columns.Where(u => u.PropertyInfo.GetCustomAttribute<IgnoreUpdateSeedColumnAttribute>() != null).Select(u => u.PropertyName).ToArray()).ExecuteCommand();
-                    }
-                    else
-                    {
-                        // 无主键则只进行插入
-                        if (!dbProvider.Queryable(entityInfo.DbTableName, entityInfo.DbTableName).Any())
-                            dbProvider.InsertableByObject(seedData.ToList()).ExecuteCommand();
-                    }
-                }
-            }
-        }
-    }
-
-    /// <summary>
-    /// 初始化租户业务数据库
-    /// </summary>
-    /// <param name="iTenant"></param>
-    /// <param name="config"></param>
-    public static void InitTenantDatabase(ITenant iTenant, DbConnectionConfig config)
-    {
-        SetDbConfig(config);
-
-        if (!iTenant.IsAnyConnection(config.ConfigId.ToString()))
-            iTenant.AddConnection(config);
-        var db = iTenant.GetConnectionScope(config.ConfigId.ToString());
-        db.DbMaintenance.CreateDatabase();
-
-        // 获取所有业务表-初始化租户库表结构(排除系统表、日志表、特定库表)
-        var entityTypes = App.EffectiveTypes
-            .Where(u => !u.GetCustomAttributes<IgnoreTableAttribute>().Any())
-            .Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.IsDefined(typeof(SugarTable), false) &&
-            !u.IsDefined(typeof(SysTableAttribute), false) && !u.IsDefined(typeof(LogTableAttribute), false) && !u.IsDefined(typeof(TenantAttribute), false)).ToList();
-        if (entityTypes.Count == 0) return;
-
-        foreach (var entityType in entityTypes)
-        {
-            var splitTable = entityType.GetCustomAttribute<SplitTableAttribute>();
-            if (splitTable == null)
-                db.CodeFirst.InitTables(entityType);
-            else
-                db.CodeFirst.SplitTables().InitTables(entityType);
-        }
-    }
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Core;
+
+public static class SqlSugarSetup
+{
+    // 多租户实例
+    public static ITenant ITenant { get; set; }
+
+    /// <summary>
+    /// SqlSugar 上下文初始化
+    /// </summary>
+    /// <param name="services"></param>
+    public static void AddSqlSugar(this IServiceCollection services)
+    {
+        // 注册雪花Id
+        var snowIdOpt = App.GetConfig<SnowIdOptions>("SnowId", true);
+        YitIdHelper.SetIdGenerator(snowIdOpt);
+
+        // 自定义 SqlSugar 雪花ID算法
+        SnowFlakeSingle.WorkId = snowIdOpt.WorkerId;
+        StaticConfig.CustomSnowFlakeFunc = () =>
+        {
+            return YitIdHelper.NextId();
+        };
+
+        var dbOptions = App.GetConfig<DbConnectionOptions>("DbConnection", true);
+        dbOptions.ConnectionConfigs.ForEach(SetDbConfig);
+
+        SqlSugarScope sqlSugar = new(dbOptions.ConnectionConfigs.Adapt<List<ConnectionConfig>>(), db =>
+        {
+            dbOptions.ConnectionConfigs.ForEach(config =>
+            {
+                var dbProvider = db.GetConnectionScope(config.ConfigId);
+                SetDbAop(dbProvider, dbOptions.EnableConsoleSql);
+                SetDbDiffLog(dbProvider, config);
+            });
+        });
+        ITenant = sqlSugar;
+
+        services.AddSingleton<ISqlSugarClient>(sqlSugar); // 单例注册
+        services.AddScoped(typeof(SqlSugarRepository<>)); // 仓储注册
+        services.AddUnitOfWork<SqlSugarUnitOfWork>(); // 事务与工作单元注册
+
+        // 初始化数据库表结构及种子数据
+        dbOptions.ConnectionConfigs.ForEach(config =>
+        {
+            InitDatabase(sqlSugar, config);
+        });
+    }
+
+    /// <summary>
+    /// 配置连接属性
+    /// </summary>
+    /// <param name="config"></param>
+    public static void SetDbConfig(DbConnectionConfig config)
+    {
+        var configureExternalServices = new ConfigureExternalServices
+        {
+            EntityNameService = (type, entity) => // 处理表
+            {
+                entity.IsDisabledDelete = true; // 禁止删除非 sqlsugar 创建的列
+                // 只处理贴了特性[SugarTable]表
+                if (!type.GetCustomAttributes<SugarTable>().Any())
+                    return;
+                if (config.DbSettings.EnableUnderLine && !entity.DbTableName.Contains('_'))
+                    entity.DbTableName = UtilMethods.ToUnderLine(entity.DbTableName); // 驼峰转下划线
+            },
+            EntityService = (type, column) => // 处理列
+            {
+                // 只处理贴了特性[SugarColumn]列
+                if (!type.GetCustomAttributes<SugarColumn>().Any())
+                    return;
+                if (new NullabilityInfoContext().Create(type).WriteState is NullabilityState.Nullable)
+                    column.IsNullable = true;
+                if (config.DbSettings.EnableUnderLine && !column.IsIgnore && !column.DbColumnName.Contains('_'))
+                    column.DbColumnName = UtilMethods.ToUnderLine(column.DbColumnName); // 驼峰转下划线
+            },
+            DataInfoCacheService = new SqlSugarCache(),
+        };
+        config.ConfigureExternalServices = configureExternalServices;
+        config.InitKeyType = InitKeyType.Attribute;
+        config.IsAutoCloseConnection = true;
+        config.MoreSettings = new ConnMoreSettings
+        {
+            IsAutoRemoveDataCache = true, // 启用自动删除缓存,所有增删改会自动调用.RemoveDataCache()
+            IsAutoDeleteQueryFilter = true, // 启用删除查询过滤器
+            IsAutoUpdateQueryFilter = true, // 启用更新查询过滤器
+            SqlServerCodeFirstNvarchar = true // 采用Nvarchar
+        };
+    }
+
+    /// <summary>
+    /// 配置Aop
+    /// </summary>
+    /// <param name="db"></param>
+    /// <param name="enableConsoleSql"></param>
+    public static void SetDbAop(SqlSugarScopeProvider db, bool enableConsoleSql)
+    {
+        // 设置超时时间
+        db.Ado.CommandTimeOut = 30;
+
+        // 打印SQL语句
+        if (enableConsoleSql)
+        {
+            db.Aop.OnLogExecuting = (sql, pars) =>
+            {
+                //// 若参数值超过100个字符则进行截取
+                //foreach (var par in pars)
+                //{
+                //    if (par.DbType != System.Data.DbType.String || par.Value == null) continue;
+                //    if (par.Value.ToString().Length > 100)
+                //        par.Value = string.Concat(par.Value.ToString()[..100], "......");
+                //}
+
+                var log = $"【{DateTime.Now}——执行SQL】\r\n{UtilMethods.GetNativeSql(sql, pars)}\r\n";
+                var originColor = Console.ForegroundColor;
+                if (sql.StartsWith("SELECT", StringComparison.OrdinalIgnoreCase))
+                    Console.ForegroundColor = ConsoleColor.Green;
+                if (sql.StartsWith("UPDATE", StringComparison.OrdinalIgnoreCase) || sql.StartsWith("INSERT", StringComparison.OrdinalIgnoreCase))
+                    Console.ForegroundColor = ConsoleColor.Yellow;
+                if (sql.StartsWith("DELETE", StringComparison.OrdinalIgnoreCase))
+                    Console.ForegroundColor = ConsoleColor.Red;
+                Console.WriteLine(log);
+                Console.ForegroundColor = originColor;
+                App.PrintToMiniProfiler("SqlSugar", "Info", log);
+            };
+            db.Aop.OnError = ex =>
+            {
+                if (ex.Parametres == null) return;
+                var log = $"【{DateTime.Now}——错误SQL】\r\n{UtilMethods.GetNativeSql(ex.Sql, (SugarParameter[])ex.Parametres)}\r\n";
+                Log.Error(log, ex);
+                App.PrintToMiniProfiler("SqlSugar", "Error", log);
+            };
+            db.Aop.OnLogExecuted = (sql, pars) =>
+            {
+                //// 若参数值超过100个字符则进行截取
+                //foreach (var par in pars)
+                //{
+                //    if (par.DbType != System.Data.DbType.String || par.Value == null) continue;
+                //    if (par.Value.ToString().Length > 100)
+                //        par.Value = string.Concat(par.Value.ToString()[..100], "......");
+                //}
+
+                // 执行时间超过5秒时
+                if (db.Ado.SqlExecutionTime.TotalSeconds > 5)
+                {
+                    var fileName = db.Ado.SqlStackTrace.FirstFileName; // 文件名
+                    var fileLine = db.Ado.SqlStackTrace.FirstLine; // 行号
+                    var firstMethodName = db.Ado.SqlStackTrace.FirstMethodName; // 方法名
+                    var log = $"【{DateTime.Now}——超时SQL】\r\n【所在文件名】:{fileName}\r\n【代码行数】:{fileLine}\r\n【方法名】:{firstMethodName}\r\n" + $"【SQL语句】:{UtilMethods.GetNativeSql(sql, pars)}";
+                    Log.Warning(log);
+                    App.PrintToMiniProfiler("SqlSugar", "Slow", log);
+                }
+            };
+        }
+        // 数据审计
+        db.Aop.DataExecuting = (oldValue, entityInfo) =>
+        {
+            // 新增/插入
+            if (entityInfo.OperationType == DataFilterType.InsertByObject)
+            {
+                // 若主键是长整型且空则赋值雪花Id
+                if (entityInfo.EntityColumnInfo.IsPrimarykey && !entityInfo.EntityColumnInfo.IsIdentity && entityInfo.EntityColumnInfo.PropertyInfo.PropertyType == typeof(long))
+                {
+                    var id = entityInfo.EntityColumnInfo.PropertyInfo.GetValue(entityInfo.EntityValue);
+                    if (id == null || (long)id == 0)
+                    {
+                        if (!entityInfo.EntityColumnInfo.IsIdentity)
+                        {
+                            // 设置雪花Id
+                            entityInfo.SetValue(YitIdHelper.NextId());
+                        }
+                    }
+                }
+                // 若创建时间为空则赋值当前时间
+                else if (entityInfo.PropertyName == nameof(EntityBase.CreateTime))
+                {
+                    var createTime = entityInfo.EntityColumnInfo.PropertyInfo.GetValue(entityInfo.EntityValue)!;
+                    if (createTime == null || createTime.Equals(DateTime.MinValue))
+                        entityInfo.SetValue(DateTime.Now);
+                }
+                // 若当前用户非空(web线程时)
+                if (App.User != null)
+                {
+                    dynamic entityValue = entityInfo.EntityValue;
+                    if (entityInfo.PropertyName == nameof(EntityTenantId.TenantId))
+                    {
+                        var tenantId = entityValue.TenantId;
+                        if (tenantId == null || tenantId == 0)
+                            entityInfo.SetValue(App.User.FindFirst(ClaimConst.TenantId)?.Value);
+                    }
+                    else if (entityInfo.PropertyName == nameof(EntityBase.CreateUserId))
+                    {
+                        var createUserId = entityValue.CreateUserId;
+                        if (createUserId == 0 || createUserId == null)
+                            entityInfo.SetValue(App.User.FindFirst(ClaimConst.UserId)?.Value);
+                    }
+                    else if (entityInfo.PropertyName == nameof(EntityBase.CreateUserName))
+                    {
+                        var createUserName = entityValue.CreateUserName;
+                        if (string.IsNullOrEmpty(createUserName))
+                            entityInfo.SetValue(App.User.FindFirst(ClaimConst.RealName)?.Value);
+                    }
+                    else if (entityInfo.PropertyName == nameof(EntityBaseData.CreateOrgId))
+                    {
+                        var createOrgId = entityValue.CreateOrgId;
+                        if (createOrgId == 0 || createOrgId == null)
+                            entityInfo.SetValue(App.User.FindFirst(ClaimConst.OrgId)?.Value);
+                    }
+                    else if (entityInfo.PropertyName == nameof(EntityBaseData.CreateOrgName))
+                    {
+                        var createOrgName = entityValue.CreateOrgName;
+                        if (string.IsNullOrEmpty(createOrgName))
+                            entityInfo.SetValue(App.User.FindFirst(ClaimConst.OrgName)?.Value);
+                    }
+                }
+            }
+            // 编辑/更新
+            else if (entityInfo.OperationType == DataFilterType.UpdateByObject)
+            {
+                if (entityInfo.PropertyName == nameof(EntityBase.UpdateTime))
+                    entityInfo.SetValue(DateTime.Now);
+                else if (entityInfo.PropertyName == nameof(EntityBase.UpdateUserId))
+                    entityInfo.SetValue(App.User?.FindFirst(ClaimConst.UserId)?.Value);
+                else if (entityInfo.PropertyName == nameof(EntityBase.UpdateUserName))
+                    entityInfo.SetValue(App.User?.FindFirst(ClaimConst.RealName)?.Value);
+            }
+        };
+
+        // 超管排除其他过滤器
+        if (App.User?.FindFirst(ClaimConst.AccountType)?.Value == ((int)AccountTypeEnum.SuperAdmin).ToString())
+            return;
+
+        // 配置假删除过滤器
+        db.QueryFilter.AddTableFilter<IDeletedFilter>(u => u.IsDelete == false);
+
+        // 配置租户过滤器
+        var tenantId = App.User?.FindFirst(ClaimConst.TenantId)?.Value;
+        if (!string.IsNullOrWhiteSpace(tenantId))
+            db.QueryFilter.AddTableFilter<ITenantIdFilter>(u => u.TenantId == long.Parse(tenantId));
+
+        // 配置用户机构(数据范围)过滤器
+        SqlSugarFilter.SetOrgEntityFilter(db);
+
+        // 配置自定义过滤器
+        SqlSugarFilter.SetCustomEntityFilter(db);
+    }
+
+    /// <summary>
+    /// 开启库表差异化日志
+    /// </summary>
+    /// <param name="db"></param>
+    /// <param name="config"></param>
+    private static void SetDbDiffLog(SqlSugarScopeProvider db, DbConnectionConfig config)
+    {
+        if (!config.DbSettings.EnableDiffLog) return;
+
+        db.Aop.OnDiffLogEvent = async u =>
+        {
+            var logDiff = new SysLogDiff
+            {
+                // 操作后记录(字段描述、列名、值、表名、表描述)
+                AfterData = JSON.Serialize(u.AfterData),
+                // 操作前记录(字段描述、列名、值、表名、表描述)
+                BeforeData = JSON.Serialize(u.BeforeData),
+                // 传进来的对象(如果对象为空,则使用首个数据的表名作为业务对象)
+                BusinessData = u.BusinessData == null ? u.AfterData.FirstOrDefault()?.TableName : JSON.Serialize(u.BusinessData),
+                // 枚举(insert、update、delete)
+                DiffType = u.DiffType.ToString(),
+                Sql = UtilMethods.GetNativeSql(u.Sql, u.Parameters),
+                Parameters = JSON.Serialize(u.Parameters),
+                Elapsed = u.Time == null ? 0 : (long)u.Time.Value.TotalMilliseconds
+            };
+            var logDb = ITenant.IsAnyConnection(SqlSugarConst.LogConfigId) ? ITenant.GetConnectionScope(SqlSugarConst.LogConfigId) : db;
+            await logDb.CopyNew().Insertable(logDiff).ExecuteCommandAsync();
+            Console.ForegroundColor = ConsoleColor.Red;
+            Console.WriteLine(DateTime.Now + $"\r\n*****开始差异日志*****\r\n{Environment.NewLine}{JSON.Serialize(logDiff)}{Environment.NewLine}*****结束差异日志*****\r\n");
+        };
+    }
+
+    /// <summary>
+    /// 初始化数据库
+    /// </summary>
+    /// <param name="db"></param>
+    /// <param name="config"></param>
+    private static void InitDatabase(SqlSugarScope db, DbConnectionConfig config)
+    {
+        SqlSugarScopeProvider dbProvider = db.GetConnectionScope(config.ConfigId);
+
+        // 初始化/创建数据库
+        if (config.DbSettings.EnableInitDb)
+        {
+            Log.Information($"初始化数据库 {config.DbType} - {config.ConfigId} - {config.ConnectionString}");
+            if (config.DbType != SqlSugar.DbType.Oracle)
+                dbProvider.DbMaintenance.CreateDatabase();
+        }
+
+        // 初始化表结构
+        if (config.TableSettings.EnableInitTable)
+        {
+            Log.Information($"初始化表结构 {config.DbType} - {config.ConfigId}");
+            var entityTypes = App.EffectiveTypes.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.IsDefined(typeof(SugarTable), false))
+                .Where(u => !u.GetCustomAttributes<IgnoreTableAttribute>().Any())
+                .WhereIF(config.TableSettings.EnableIncreTable, u => u.IsDefined(typeof(IncreTableAttribute), false)).ToList();
+
+            if (config.ConfigId.ToString() == SqlSugarConst.MainConfigId) // 默认库(有系统表特性、没有日志表和租户表特性)
+                entityTypes = entityTypes.Where(u => u.GetCustomAttributes<SysTableAttribute>().Any() || (!u.GetCustomAttributes<LogTableAttribute>().Any() && !u.GetCustomAttributes<TenantAttribute>().Any())).ToList();
+            else if (config.ConfigId.ToString() == SqlSugarConst.LogConfigId) // 日志库
+                entityTypes = entityTypes.Where(u => u.GetCustomAttributes<LogTableAttribute>().Any()).ToList();
+            else
+                entityTypes = entityTypes.Where(u => u.GetCustomAttribute<TenantAttribute>()?.configId.ToString() == config.ConfigId.ToString()).ToList(); // 自定义的库
+
+            int count = 0, sum = entityTypes.Count;
+            foreach (var entityType in entityTypes)
+            {
+                Console.WriteLine($"创建表 {entityType} ({config.ConfigId} - {++count}/{sum})");
+                if (entityType.GetCustomAttribute<SplitTableAttribute>() == null)
+                    dbProvider.CodeFirst.InitTables(entityType);
+                else
+                    dbProvider.CodeFirst.SplitTables().InitTables(entityType);
+            }
+        }
+
+        // 初始化种子数据
+        if (config.SeedSettings.EnableInitSeed)
+        {
+            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<>))))
+                .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 count = 0, sum = seedDataTypes.Count;
+            foreach (var seedType in seedDataTypes)
+            {
+                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))
+                        continue;
+                }
+                else if (config.ConfigId.ToString() == SqlSugarConst.LogConfigId) // 日志库
+                {
+                    if (entityType.GetCustomAttribute<LogTableAttribute>() == null)
+                        continue;
+                }
+                else
+                {
+                    var att = entityType.GetCustomAttribute<TenantAttribute>(); // 自定义的库
+                    if (att == null || att.configId.ToString() != config.ConfigId.ToString()) continue;
+                }
+
+                var instance = Activator.CreateInstance(seedType);
+                var hasDataMethod = seedType.GetMethod("HasData");
+                var seedData = ((IEnumerable)hasDataMethod?.Invoke(instance, null))?.Cast<object>();
+                if (seedData == null) continue;
+
+                var entityInfo = dbProvider.EntityMaintenance.GetEntityInfo(entityType);
+                Console.WriteLine($"添加数据 {entityInfo.DbTableName} ({config.ConfigId} - {++count}/{sum})");
+
+                if (entityType.GetCustomAttribute<SplitTableAttribute>(true) != null)
+                {
+                    //拆分表的操作需要实体类型,而通过反射很难实现
+                    //所以,这里将Init方法写在“种子数据类”内部,再传入 db 反射调用
+                    var hasInitMethod = seedType.GetMethod("Init");
+                    var parameters = new object[] { db };
+                    hasInitMethod?.Invoke(instance, parameters);
+                }
+                else
+                {
+                    if (entityInfo.Columns.Any(u => u.IsPrimarykey))
+                    {
+                        // 按主键进行批量增加和更新
+                        var storage = dbProvider.StorageableByObject(seedData.ToList()).ToStorage();
+                        storage.AsInsertable.ExecuteCommand();
+                        if (seedType.GetCustomAttribute<IgnoreUpdateSeedAttribute>() == null) // 有忽略更新种子特性时则不更新
+                            storage.AsUpdateable.IgnoreColumns(entityInfo.Columns.Where(u => u.PropertyInfo.GetCustomAttribute<IgnoreUpdateSeedColumnAttribute>() != null).Select(u => u.PropertyName).ToArray()).ExecuteCommand();
+                    }
+                    else
+                    {
+                        // 无主键则只进行插入
+                        if (!dbProvider.Queryable(entityInfo.DbTableName, entityInfo.DbTableName).Any())
+                            dbProvider.InsertableByObject(seedData.ToList()).ExecuteCommand();
+                    }
+                }
+            }
+        }
+    }
+
+    /// <summary>
+    /// 初始化租户业务数据库
+    /// </summary>
+    /// <param name="iTenant"></param>
+    /// <param name="config"></param>
+    public static void InitTenantDatabase(ITenant iTenant, DbConnectionConfig config)
+    {
+        SetDbConfig(config);
+
+        if (!iTenant.IsAnyConnection(config.ConfigId.ToString()))
+            iTenant.AddConnection(config);
+        var db = iTenant.GetConnectionScope(config.ConfigId.ToString());
+        db.DbMaintenance.CreateDatabase();
+
+        // 获取所有业务表-初始化租户库表结构(排除系统表、日志表、特定库表)
+        var entityTypes = App.EffectiveTypes
+            .Where(u => !u.GetCustomAttributes<IgnoreTableAttribute>().Any())
+            .Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.IsDefined(typeof(SugarTable), false) &&
+            !u.IsDefined(typeof(SysTableAttribute), false) && !u.IsDefined(typeof(LogTableAttribute), false) && !u.IsDefined(typeof(TenantAttribute), false)).ToList();
+        if (entityTypes.Count == 0) return;
+
+        foreach (var entityType in entityTypes)
+        {
+            var splitTable = entityType.GetCustomAttribute<SplitTableAttribute>();
+            if (splitTable == null)
+                db.CodeFirst.InitTables(entityType);
+            else
+                db.CodeFirst.SplitTables().InitTables(entityType);
+        }
+    }
 }

+ 2 - 2
Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/Service.cs.vm

@@ -78,7 +78,7 @@ if (@column.QueryWhether == "Y"){
     @foreach (var column in Model.TableField){
         if(@column.EffectType == "fk"){
             joinTableName += ", " + column.PropertyName.ToLower();
-            @:.LeftJoin<@(@column.FkEntityName)>((@(@joinTableName)) => u.@(@column.PropertyName) == @(@column.PropertyName.ToLower()).Id )
+            @:.LeftJoin<@(@column.FkEntityName)>((@(@joinTableName)) => u.@(@column.PropertyName) == @(@column.PropertyName.ToLower()).@(@column.FkLinkColumnName) )
         } else if(@column.EffectType == "ApiTreeSelect"){
             joinTableName += ", " + column.PropertyName.ToLower();
             @:.LeftJoin<@(@column.FkEntityName)>((@(@joinTableName)) => u.@(@column.PropertyName) == @(@column.PropertyName.ToLower()).@(@column.ValueColumn) )
@@ -205,7 +205,7 @@ if(@column.EffectType == "fk" && (@column.WhetherAddUpdate == "Y" || column.Quer
                 @:.Select(u => new
                 @:{
                     @:Label = u.@(@column.FkColumnName),
-                    @:Value = u.Id
+                    @:Value = u.@(@column.FkLinkColumnName)
                 @:}
                 @:).ToListAsync();
     @:}

+ 8 - 0
Web/src/views/system/codeGen/component/fkDialog.vue

@@ -30,6 +30,13 @@
 							</el-select>
 						</el-form-item>
 					</el-col>
+					<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
+						<el-form-item label="链接字段" prop="LinkColumnName">
+							<el-select v-model="state.ruleForm.LinkColumnName" class="w100">
+								<el-option v-for="item in state.columnData" :key="item.columnName" :label="item.columnName + ' [' + item.columnComment + ']'" :value="item.columnName" />
+							</el-select>
+						</el-form-item>
+					</el-col>
 				</el-row>
 			</el-form>
 			<template #footer>
@@ -108,6 +115,7 @@ const closeDialog = () => {
 	let tableData = state.tableData.filter((x) => x.tableName == state.ruleForm.tableName);
 	rowdata.fkEntityName = tableData.length == 0 ? '' : tableData[0].entityName;
 	rowdata.fkColumnName = state.ruleForm.columnName;
+	rowdata.fkLinkColumnName = state.ruleForm.LinkColumnName;
 	let columnData = state.columnData.filter((x) => x.columnName == state.ruleForm.columnName);
 	rowdata.fkColumnNetType = columnData.length == 0 ? '' : columnData[0].netType;
 	emits('submitRefreshFk', rowdata);