Преглед изворни кода

✨feat(视图): 新增视图初始化功能

喵你个旺呀 пре 10 месеци
родитељ
комит
08e6aa2df2

+ 1 - 0
Admin.NET/Admin.NET.Application/Configuration/Database.json

@@ -28,6 +28,7 @@
         //],
         "DbSettings": {
           "EnableInitDb": true, // 启用库初始化(若实体没有变化建议关闭)
+          "EnableInitView": true, // 启用视图初始化(若实体和视图没有变化建议关闭)
           "EnableDiffLog": false, // 启用库表差异日志
           "EnableUnderLine": false, // 启用驼峰转下划线
           "EnableConnEncrypt": false // 启用数据库连接串加密(国密SM2加解密)

+ 67 - 0
Admin.NET/Admin.NET.Application/Entity/TestViewSysUser.cs

@@ -0,0 +1,67 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+using SqlSugar;
+
+namespace Admin.NET.Application;
+
+/// <summary>
+/// 用户表视图(必须加IgnoreTable,防止被生成为表)
+/// </summary>
+[SugarTable(null, "用户表视图"), IgnoreTable]
+public class TestViewSysUser : EntityBase, ISqlSugarView
+{
+    /// <summary>
+    /// 账号
+    /// </summary>
+    [SugarColumn(ColumnDescription = "账号")]
+    public virtual string Account { get; set; }
+
+    /// <summary>
+    /// 真实姓名
+    /// </summary>
+    [SugarColumn(ColumnDescription = "真实姓名")]
+    public virtual string RealName { get; set; }
+
+    /// <summary>
+    /// 昵称
+    /// </summary>
+    [SugarColumn(ColumnDescription = "昵称")]
+    public string? NickName { get; set; }
+
+    /// <summary>
+    /// 机构名称
+    /// </summary>
+    [SugarColumn(ColumnDescription = "机构名称")]
+    public string? OrgName { get; set; }
+
+    /// <summary>
+    /// 职位名称
+    /// </summary>
+    [SugarColumn(ColumnDescription = "职位名称")]
+    public string? PosName { get; set; }
+
+    /// <summary>
+    /// 查询实例
+    /// </summary>
+    /// <param name="db"></param>
+    /// <returns></returns>
+    public string GetQueryableSqlString(SqlSugarScopeProvider db)
+    {
+        return db.Queryable<SysUser>()
+            .LeftJoin<SysOrg>((u, a) => u.OrgId == a.Id)
+            .LeftJoin<SysPos>((u, a, b) => u.PosId == b.Id)
+            .Select((u, a, b) => new TestViewSysUser
+            {
+                Id = u.Id,
+                Account = u.Account,
+                RealName = u.RealName,
+                NickName = u.NickName,
+                OrgName = a.Name,
+                PosName = b.Name,
+            }).ToMappedSqlString();
+    }
+}

+ 33 - 0
Admin.NET/Admin.NET.Core/Extension/SqlSugarExtension.cs

@@ -291,4 +291,37 @@ public static class SqlSugarExtension
 
         return Expression.Call(selectorExpr, method, constant);
     }
+    
+    #region 视图操作
+
+    /// <summary>
+    /// 获取映射SQL语句, 用于创建视图
+    /// </summary>
+    /// <param name="queryable"></param>
+    /// <typeparam name="T"></typeparam>
+    /// <returns></returns>
+    public static string ToMappedSqlString<T>(this ISugarQueryable<T> queryable) where T : class
+    {
+        ArgumentNullException.ThrowIfNull(queryable);
+
+        // 获取实体映射信息
+        var entityInfo = queryable.Context.EntityMaintenance.GetEntityInfo(typeof(T));
+        if (entityInfo?.Columns == null || entityInfo.Columns.Count == 0) return queryable.ToSqlString();
+
+        // 构建需要替换的字段名映射(只处理实际有差异的字段)
+        var nameMap = entityInfo.Columns
+            .Where(c => !string.Equals(c.PropertyName, c.DbColumnName, StringComparison.OrdinalIgnoreCase))
+            .ToDictionary(k => k.PropertyName.ToLower(), v => v.DbColumnName, StringComparer.OrdinalIgnoreCase);
+        if (nameMap.Count == 0) return queryable.ToSqlString();
+
+        // 预编译正则表达式提升性能
+        var sql = queryable.ToSqlString();
+        foreach (var kv in nameMap)
+        {
+            sql = Regex.Replace(sql, $@"\b{kv.Key}\b", kv.Value ?? kv.Key, RegexOptions.IgnoreCase | RegexOptions.Compiled); // 单词边界匹配
+        }
+        return sql;
+    }
+
+    #endregion 视图操作
 }

+ 5 - 0
Admin.NET/Admin.NET.Core/Option/DbConnectionOptions.cs

@@ -81,6 +81,11 @@ public sealed class DbSettings
     /// 启用库表初始化
     /// </summary>
     public bool EnableInitDb { get; set; }
+    
+    /// <summary>
+    /// 启用视图初始化
+    /// </summary>
+    public bool EnableInitView { get; set; }
 
     /// <summary>
     /// 启用库表差异日志

+ 20 - 0
Admin.NET/Admin.NET.Core/SqlSugar/ISqlSugarView.cs

@@ -0,0 +1,20 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+// 
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+// 
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Core;
+
+/// <summary>
+/// 视图实体接口
+/// </summary>
+public interface ISqlSugarView
+{
+    /// <summary>
+    /// 获取视图查询sql语句
+    /// </summary>
+    /// <param name="db"></param>
+    /// <returns></returns>
+    public string GetQueryableSqlString(SqlSugarScopeProvider db);
+}

+ 54 - 0
Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarSetup.cs

@@ -325,6 +325,48 @@ public static class SqlSugarSetup
         db.Aop.OnDiffLogEvent = AopOnDiffLogEvent;
     }
 
+    /// <summary>
+    /// 初始化视图
+    /// </summary>
+    /// <param name="dbProvider"></param>
+    private static void InitView(SqlSugarScopeProvider dbProvider)
+    {
+        var totalWatch = Stopwatch.StartNew(); // 开始总计时
+        Log.Information($"初始化视图 {dbProvider.CurrentConnectionConfig.DbType} - {dbProvider.CurrentConnectionConfig.ConfigId}");
+        var viewTypeList = App.EffectiveTypes.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.GetInterfaces().Any(i => i.HasImplementedRawGeneric(typeof(ISqlSugarView)))).ToList();
+
+        int taskIndex = 0, size = viewTypeList.Count;
+        var taskList = viewTypeList.Select(viewType => Task.Run(() =>
+        {
+            // 开始计时
+            var stopWatch = Stopwatch.StartNew();
+
+            // 获取视图实体和配置信息
+            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);
+
+            // 获取初始化视图查询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);
+
+            // 停止计时
+            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)");
+        }));
+        Task.WaitAll(taskList.ToArray());
+
+        totalWatch.Stop(); // 停止总计时
+        Console.ForegroundColor = ConsoleColor.Green;
+        Console.WriteLine($"初始化视图 {dbProvider.CurrentConnectionConfig.DbType} - {dbProvider.CurrentConnectionConfig.ConfigId} 总耗时:{totalWatch.ElapsedMilliseconds:N0} ms");
+    }
+
     /// <summary>
     /// 初始化数据库
     /// </summary>
@@ -349,6 +391,9 @@ public static class SqlSugarSetup
             InitializeTables(dbProvider, entityTypes, config);
         }
 
+        // 初始化视图
+        if (config.DbSettings.EnableInitView) InitView(dbProvider);
+
         // 初始化种子数据
         if (config.SeedSettings.EnableInitSeed) InitSeedData(db, config);
     }
@@ -402,6 +447,15 @@ public static class SqlSugarSetup
     /// <param name="config">数据库连接配置</param>
     private static void InitializeTables(SqlSugarScopeProvider dbProvider, List<Type> entityTypes, DbConnectionConfig config)
     {
+        // 删除视图再初始化表结构,防止因为视图导致无法同步表结构
+        var viewTypeList = App.EffectiveTypes.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.GetInterfaces().Any(i => i.HasImplementedRawGeneric(typeof(ISqlSugarView)))).ToList();
+        foreach (var viewType in viewTypeList)
+        {
+            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);
+        }
+
         int count = 0, sum = entityTypes.Count;
         var tasks = entityTypes.Select(entityType => Task.Run(() =>
         {