<|start_filename|>src/Basic/Senparc.Ncf.XncfBase/Functions/FunctionResult.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.Text;

namespace Senparc.Ncf.XncfBase.Functions
{
    /// <summary>
    /// 方法返回结果
    /// </summary>
    public class FunctionResult
    {
        /// <summary>
        /// 是否成功
        /// </summary>
        public bool Success { get; set; }
        /// <summary>
        /// 日志
        /// </summary>
        public string Log { get; set; }
        /// <summary>
        /// 消息结果
        /// </summary>
        public string Message { get; set; }
        /// <summary>
        /// 异常信息（此时 Success 一般为 false）
        /// </summary>
        public XncfFunctionException Exception { get; set; }
    }
}


<|start_filename|>src/Extensions/Senparc.Xncf.DatabaseToolkit/Senparc.Xncf.DatabaseToolkit/Models/DataBaseModel/DbConfig.cs<|end_filename|>
﻿using Senparc.Ncf.Core.Models;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text;

namespace Senparc.Xncf.DatabaseToolkit
{
    [Table(Register.DATABASE_PREFIX + nameof(DbConfig))]//必须添加前缀，防止全系统中发生冲突
    [Serializable]
    public class DbConfig : EntityBase<int>
    {
        /// <summary>
        /// 备份间隔时间
        /// </summary>
        [Required]
        public int BackupCycleMinutes { get; private set; }
        /// <summary>
        /// 备份物理路径
        /// </summary>
        [MaxLength(300)]
        public string BackupPath { get; private set; }

        /// <summary>
        /// 上次备份时间
        /// </summary>
        public DateTime LastBackupTime { get; private set; }

        private DbConfig() { }

        public DbConfig(int backupCycleMinutes, string backupPath)
        {
            BackupCycleMinutes = backupCycleMinutes;
            BackupPath = backupPath;
            LastBackupTime = SystemTime.Now.AddDays(-1).DateTime;
        }

        public void RecordBackupTime()
        {
            LastBackupTime = SystemTime.Now.DateTime;
        }
    }
}


<|start_filename|>src/Extensions/Senparc.Xncf.ChangeNamespace/Functions/ChangeNameSpace.cs<|end_filename|>
﻿using Senparc.CO2NET.Extensions;
using Senparc.Ncf.XncfBase;
using Senparc.Ncf.XncfBase.Functions;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Text;

namespace Senparc.Xncf.ChangeNamespace.Functions
{
    public class ChangeNamespace : FunctionBase
    {
        public class MatchNamespace
        {
            public string Prefix { get; set; }
            public string OldNamespace { get; set; }
            public string NewNamespace { get; set; }
        }

        public class ChangeNamespace_Parameters : IFunctionParameter
        {
            [Required]
            [MaxLength(300)]
            [Description("路径||本地物理路径，如：E:\\Senparc\\Ncf\\")]
            public string Path { get; set; }
            [Required]
            [MaxLength(100)]
            [Description("新命名空间||命名空间根，必须以.结尾，用于替换[Senparc.Ncf.]")]
            public string NewNamespace { get; set; }
        }


        //注意：Name 必须在单个 Xncf 模块中唯一！
        public override string Name => "修改命名空间";

        public override string Description => "修改所有源码在 .cs, .cshtml 中的命名空间";

        public override Type FunctionParameterType => typeof(ChangeNamespace_Parameters);

        public ChangeNamespace(IServiceProvider serviceProvider) : base(serviceProvider)
        {
        }

        public string OldNamespaceKeyword { get; set; } = "Senparc.";

        /// <summary>
        /// 运行
        /// </summary>
        /// <param name="param"></param>
        /// <returns></returns>
        public override FunctionResult Run(IFunctionParameter param)
        {
            return FunctionHelper.RunFunction<ChangeNamespace_Parameters>(param, (typeParam, sb, result) =>
            {
                base.RecordLog(sb, "开始运行 ChangeNamespace");

                var path = typeParam.Path;
                var newNamespace = typeParam.NewNamespace;

                if (!Directory.Exists(path))
                {
                    base.RecordLog(sb, $"path:{path} not exist");
                    result.Success = false;
                    result.Message = "路径不存在！";
                    return;
                }

                base.RecordLog(sb, $"path:{path} newNamespace:{newNamespace}");

                var meetRules = new List<MeetRule>() {
                new MeetRule("namespace",OldNamespaceKeyword,$"{newNamespace}","*.cs"),
                new MeetRule("@model",OldNamespaceKeyword,$"{newNamespace}","*.cshtml"),
                new MeetRule("@addTagHelper *,",OldNamespaceKeyword,$"{newNamespace}","*.cshtml"),
            };

                //TODO:使用正则记录，并全局修改

                Dictionary<string, List<MatchNamespace>> namespaceCollection = new Dictionary<string, List<MatchNamespace>>(StringComparer.OrdinalIgnoreCase);

                //扫描所有规则
                foreach (var item in meetRules)
                {
                    var files = Directory.GetFiles(path, item.FileType, SearchOption.AllDirectories);

                    //扫描所有文件，将满足这一条规则替换条件的对象记录下来
                    foreach (var file in files)
                    {
                        base.RecordLog(sb, $"扫描文件类型:{item.FileType} 数量:{files.Length}");

                        //string content = null;
                        using (var fs = new FileStream(file, FileMode.Open, FileAccess.Read))
                        {
                            using (var sr = new StreamReader(fs))
                            {
                                var line = sr.ReadLine();
                                while (null != line)
                                {
                                    line = sr.ReadLine()?.Trim();
                                    var oldNamespaceFull = $"{item.Prefix} {item.OrignalKeyword}";
                                    if (line != null && line.StartsWith(oldNamespaceFull))
                                    {
                                        if (!namespaceCollection.ContainsKey(file))
                                        {
                                            namespaceCollection[file] = new List<MatchNamespace>();
                                        }

                                        //不能使用Split，中间可能还有空格
                                        //var oldNamespaceArr = line.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries);
                                        var getOld = line.Replace(item.Prefix + " ", "");//也可以用IndexOf+Length来做
                                        var getNew = getOld.Replace(item.OrignalKeyword, item.ReplaceWord);
                                        namespaceCollection[file].Add(new MatchNamespace()
                                        {
                                            Prefix = item.Prefix,//prefix
                                            OldNamespace = getOld,
                                            NewNamespace = getNew
                                        });

                                        namespaceCollection[file].Add(new MatchNamespace()
                                        {
                                            Prefix = "using",
                                            OldNamespace = getOld,
                                            NewNamespace = getNew
                                        });
                                    }

                                    //content += Environment.NewLine + line;
                                }
                                sr.ReadLine();
                            }
                            fs.Close();
                        }
                    }

                    //遍历所有文件，替换已经解锁出来的旧命名空间
                    foreach (var file in files)
                    {
                        string content = null;
                        using (var fs = new FileStream(file, FileMode.Open, FileAccess.Read))
                        {
                            using (var sr = new StreamReader(fs))
                            {
                                content = sr.ReadToEnd();
                            }
                            fs.Close();
                        }

                        foreach (var namespaceInfos in namespaceCollection)
                        {
                            foreach (var namespaceInfo in namespaceInfos.Value)
                            {
                                var oldNamespaceFull = $"{namespaceInfo.Prefix} {namespaceInfo.OldNamespace}";

                                //替换旧的NameSpace
                                if (content.IndexOf(oldNamespaceFull) > -1)
                                {
                                    base.RecordLog(sb, $"文件命中:{file} -> {oldNamespaceFull}");
                                    var newNameSpaceFull = $"{namespaceInfo.Prefix} {namespaceInfo.NewNamespace}";
                                    content = content.Replace(oldNamespaceFull, newNameSpaceFull);
                                }
                            }
                        }

                        using (var fs = new FileStream(file, FileMode.Truncate, FileAccess.ReadWrite))
                        {
                            using (var sw = new StreamWriter(fs))
                            {
                                sw.Write(content);
                                sw.Flush();
                            }
                            fs.Close();
                        }
                    }

                }

                result.Log = sb.ToString();
                result.Message = "更新成功！您还可以使用【还原命名空间】功能进行还原！";
            });
        }
    }
}


<|start_filename|>src/Extensions/Senparc.Xncf.Swagger/Register.cs<|end_filename|>
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Versioning;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Senparc.CO2NET.RegisterServices;
using Senparc.CO2NET.WebApi;
using Senparc.CO2NET.WebApi.WebApiEngines;
using Senparc.Ncf.XncfBase;
using Senparc.Xncf.Swagger.Builder;
using Senparc.Xncf.Swagger.Models;
using Senparc.Xncf.Swagger.Utils;

namespace Senparc.Xncf.Swagger
{
    [XncfRegister]
    public partial class Register : XncfRegisterBase, IXncfRegister
    {
        /// <summary>
        /// Api 默认文档地址，返回 null 则不生成 XML 文档
        /// </summary>
        public static Func<IWebHostEnvironment, string> ApiDocXmlPathFunc = (env) => Path.Combine(env.ContentRootPath, "App_Data", "ApiDocXml");

        #region IRegister 接口

        public override string Name => "Senparc.Xncf.Swagger";

        public override string Uid => "712d56f6-989e-4b5f-b769-86a870543e8d";//必须确保全局唯一，生成后必须固定，已自动生成，也可自行修改

        public override string Version => "0.1.3";//必须填写版本号

        public override string MenuName => "接口说明文档";

        public override string Icon => "fa fa-file-code-o";

        public override string Description => "接口说明文档";

        public override IList<Type> Functions => new Type[] {  /*typeof(BuildXncf)*/ };

        #endregion

        public override IServiceCollection AddXncfModule(IServiceCollection services, IConfiguration configuration)
        {
            using (var scope = services.BuildServiceProvider().CreateScope())
            {
                var serviceProvider = scope.ServiceProvider;
                var env = serviceProvider.GetService<IWebHostEnvironment>();

                #region 配置动态 API（必须在 Swagger 配置之前）

                var docXmlPath = ApiDocXmlPathFunc?.Invoke(env);// Path.Combine(env.ContentRootPath, "App_Data", "ApiDocXml");
                var builder = services.AddMvcCore().AddApiExplorer().SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
                services.AddAndInitDynamicApi(builder, options =>
                {
                    options.DocXmlPath = docXmlPath;
                    options.DefaultRequestMethod = ApiRequestMethod.Get;
                    options.BaseApiControllerType = null;
                    options.CopyCustomAttributes = true;
                    options.TaskCount = Environment.ProcessorCount * 4;
                    options.ShowDetailApiLog = true;
                    options.AdditionalAttributeFunc = null;
                    options.ForbiddenExternalAccess = true;
                });

                #endregion

                ConfigurationHelper.Configuration = configuration;
                ConfigurationHelper.HostEnvironment = env;
                ConfigurationHelper.WebHostEnvironment = env;
                ConfigurationHelper.SwaggerConfiguration = new ConfigurationBuilder().AddJsonFile("appsettings.json", optional: true, reloadOnChange: true).Build();

                services.Configure<CustsomSwaggerOptions>(ConfigurationHelper.SwaggerConfiguration.GetSection("Swagger"));

                ConfigurationHelper.CustsomSwaggerOptions = ConfigurationHelper.SwaggerConfiguration.GetSection("Swagger")
                                                                .Get<CustsomSwaggerOptions>() ?? new CustsomSwaggerOptions();
                ConfigurationHelper.CustsomSwaggerOptions.AddSwaggerGenAction = c =>
                {
                    var xmlList = Directory.GetFiles(AppContext.BaseDirectory, "*.xml", SearchOption.AllDirectories);
                    foreach (var xml in xmlList)
                    {
                        c.IncludeXmlComments(xml, true);
                    }
                };
                ConfigurationHelper.CustsomSwaggerOptions.UseSwaggerAction = c => { };
                ConfigurationHelper.CustsomSwaggerOptions.UseSwaggerUIAction = c => { };

                //接口文档
                #region Swagger

                services.AddApiVersioning(x =>
                    {
                        x.DefaultApiVersion = new ApiVersion(1, 0);
                        x.AssumeDefaultVersionWhenUnspecified = true;
                        x.ReportApiVersions = true;
                        x.ApiVersionReader = new HeaderApiVersionReader("x-api-version");
                    });
                services.AddSwaggerCustom(docXmlPath);

                #endregion
            }

            return base.AddXncfModule(services, configuration);
        }

        public override IApplicationBuilder UseXncfModule(IApplicationBuilder app, IRegisterService registerService)
        {
            app.UseSwaggerCustom();
            return base.UseXncfModule(app, registerService);
        }

    }
}


<|start_filename|>src/Extensions/Senparc.Xncf.DatabaseToolkit/Senparc.Xncf.DatabaseToolkit/Functions/SetConfig.cs<|end_filename|>
﻿using Microsoft.EntityFrameworkCore;
using Senparc.Ncf.Core.Models;
using Senparc.Ncf.XncfBase;
using Senparc.Ncf.XncfBase.Functions;
using System;
using Microsoft.Extensions.DependencyInjection;
using Senparc.Ncf.Service;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel;
using Senparc.CO2NET.Trace;

namespace Senparc.Xncf.DatabaseToolkit.Functions
{
    public class SetConfig : FunctionBase
    {
        public class SetConfig_Parameters : FunctionParameterLoadDataBase, IFunctionParameter
        {
            [Required]
            [MaxLength(300)]
            [Description("自动备份周期（分钟）||0 则为不自动备份")]
            public int BackupCycleMinutes { get; set; }
            [Required]
            [MaxLength(300)]
            [Description("备份路径||本地物理路径，如：E:\\Senparc\\Ncf\\NCF.bak")]
            public string BackupPath { get; set; }

            public override async Task LoadData(IServiceProvider serviceProvider)
            {
                var configService = serviceProvider.GetService<ServiceBase<DbConfig>>();
                var config = await configService.GetObjectAsync(z => true);
                if (config != null)
                {
                    BackupCycleMinutes = config.BackupCycleMinutes;
                    BackupPath = config.BackupPath;
                }
            }
        }

        //注意：Name 必须在单个 Xncf 模块中唯一！
        public override string Name => "设置参数";

        public override string Description => "设置备份间隔时间、备份文件路径等参数";

        public override Type FunctionParameterType => typeof(SetConfig_Parameters);

        public SetConfig(IServiceProvider serviceProvider) : base(serviceProvider)
        {
        }

        /// <summary>
        /// 运行
        /// </summary>
        /// <param name="param"></param>
        /// <returns></returns>
        public override FunctionResult Run(IFunctionParameter param)
        {
            return FunctionHelper.RunFunction<SetConfig_Parameters>(param, (typeParam, sb, result) =>
             {
                 //RecordLog(sb, "开始获取 ISenparcEntities 对象");
                 //var senparcEntities = ServiceProvider.GetService(typeof(ISenparcEntities)) as SenparcEntitiesBase;
                 //RecordLog(sb, "获取 ISenparcEntities 对象成功");

                 var configService = base.ServiceProvider.GetService<ServiceBase<DbConfig>>();
                 var config = configService.GetObject(z => true);
                 if (config == null)
                 {
                     config = new DbConfig(typeParam.BackupCycleMinutes, typeParam.BackupPath);
                 }
                 else
                 {
                     configService.Mapper.Map(typeParam, config);
                 }
                 configService.SaveObject(config);

                 result.Message = "设置已保存！";
             });
        }
    }
}


<|start_filename|>src/Basic/Senparc.Ncf.Core/Cache/EntityCache/FullTenantInfoCache.cs<|end_filename|>
﻿using AutoMapper;
using Microsoft.EntityFrameworkCore;
using Senparc.CO2NET;
using Senparc.Ncf.Core.DI;
using Senparc.Ncf.Core.Models;
using Senparc.Ncf.Core.Models.DataBaseModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Senparc.Ncf.Core.Cache
{
    /* 需要关注及优化：
     * 1、当前依赖注入的对象 INcfDbData 仍然会注入 SenparcEntitiesBase，
     *  而 SenparcEntitiesBase 的 OnModelCreating() 一旦被执行，会尝试读取此处的缓存，如果缓存未被初始化，就会陷入死循环。
     *  因此当前缓存内禁止调用 INcfDbData 下的查询。
     * 2、避免使用 INcfDbData，或者避免使用 BaseCache（因为 INcfDbData 默认注入对象为 NCF 模板中的 SenparcEntities，继承自 SenparcEntitiesBase）
     */

    /// <summary>
    /// 多租户信息缓存
    /// </summary>
    [AutoDIType(DILifecycleType.Scoped)]
    public class FullTenantInfoCache : BaseCache<Dictionary<string, TenantInfoDto>>
    {
        public const string CACHE_KEY = "FullTenantInfoCache";


        private IMapper _mapper;
        private SenparcEntitiesMultiTenant _senparcEntitiesMultiTenant => base._db.BaseDataContext as SenparcEntitiesMultiTenant;
        //private SenparcEntitiesBase _senparcEntitiesBase => base._db.BaseDataContext as SenparcEntitiesBase;

        /// <summary>
        /// 获取当前启用状态的 TenantInfo
        /// </summary>
        /// <returns></returns>
        private async Task<List<TenantInfo>> GetEnabledListAsync()
        {
            List<TenantInfo> list = null;
            try
            {
                _senparcEntitiesMultiTenant.SetMultiTenantEnable(false);
                list = await _senparcEntitiesMultiTenant.TenantInfos.Where(z => z.Enable).ToListAsync();
            }
            finally
            {
                _senparcEntitiesMultiTenant.ResetMultiTenantEnable();
            }
            return list;
        }


        public FullTenantInfoCache(TenantInfoDbData db, IMapper mapper, SenparcEntitiesMultiTenant senparcEntitiesMultiTenantBase)
            : this(CACHE_KEY, db, 1440)
        {
            _mapper = mapper;
        }

        public FullTenantInfoCache(string CACHE_KEY, INcfDbData db, int timeOut) : base(CACHE_KEY, db)
        {
            base.TimeOut = timeOut;
        }

        public override Dictionary<string, TenantInfoDto> Update()
        {
            throw new Senparc.Ncf.Core.Exceptions.NcfTenantException("FullTenantInfoCache 不支持同步更新方法，请使用异步方法 UpdateAsync()");
        }

        public override async Task<Dictionary<string, TenantInfoDto>> UpdateAsync()
        {
            List<TenantInfo> fullList = null;

            try
            {
                fullList = await GetEnabledListAsync();
            }
            catch (Exception)
            {
                //当前可能正在 Middleware 的获取过程中，还没有完成多租户获取
                //如果从旧版本升级，表不存在，则需要更新

                //TODO:_senparcEntitiesMultiTenant 当前没有做 Migration，所有实际上无法进行更新
                _senparcEntitiesMultiTenant.ResetMigrate();
                _senparcEntitiesMultiTenant.Migrate();

                //var HttpContextAccessor = _serviceProvider.GetRequiredService<IHttpContextAccessor>();//注意：这里不能使用 TenantInfoService.SetScopedRequestTenantInfoAsync()， 否则会引发死循环
            }
            finally
            {
                fullList = fullList ?? await GetEnabledListAsync();
            }

            var tenantInfoCollection = new Dictionary<string, TenantInfoDto>(StringComparer.OrdinalIgnoreCase);
            fullList.ForEach(z => tenantInfoCollection[z.TenantKey.ToUpper()/*全部大写*/] = _mapper.Map<TenantInfoDto>(z));
            await base.SetDataAsync(tenantInfoCollection, base.TimeOut, null);

            return await GetDataAsync();
        }
    }
}


<|start_filename|>src/Basic/Senparc.Ncf.XncfBase/XncfRegisterBase.cs<|end_filename|>
﻿using AutoMapper;
using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Senparc.CO2NET.Extensions;
using Senparc.CO2NET.RegisterServices;
using Senparc.CO2NET.Trace;
using Senparc.Ncf.Core.Areas;
using Senparc.Ncf.Core.Enums;
using Senparc.Ncf.Core.Exceptions;
using Senparc.Ncf.Core.Models;
using Senparc.Ncf.Database;
using Senparc.Ncf.XncfBase.Threads;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace Senparc.Ncf.XncfBase
{
    /// <summary>
    /// 所有 XNCF 模块注册的基类
    /// </summary>
    public abstract class XncfRegisterBase : IXncfRegister
    {
        /// <summary>
        /// 是否忽略安装（但不影响执行注册代码），默认为 false
        /// </summary>
        public virtual bool IgnoreInstall { get; }

        /// <summary>
        /// 模块名称，要求全局唯一
        /// </summary>
        public abstract string Name { get; }
        /// <summary>
        /// 编号，要求全局唯一
        /// </summary>
        public abstract string Uid { get; }
        /// <summary>
        /// 版本号
        /// </summary>
        public abstract string Version { get; }
        /// <summary>
        /// 菜单名称
        /// </summary>
        public abstract string MenuName { get; }
        /// <summary>
        /// Icon图标
        /// </summary>
        public abstract string Icon { get; }
        /// <summary>
        /// 说明
        /// </summary>
        public abstract string Description { get; }
        /// <summary>
        /// 注册方法，注册的顺序决定了界面中排列的顺序
        /// </summary>
        public abstract IList<Type> Functions { get; }

        /// <summary>
        /// 添加 AutoMap 映射
        /// </summary>
        public virtual ConcurrentBag<Action<Profile>> AutoMapMappingConfigs { get; set; }
        /// <summary>
        /// 获取当前模块的已注册线程信息
        /// </summary>
        public IEnumerable<KeyValuePair<ThreadInfo, Thread>> RegisteredThreadInfo => Register.ThreadCollection.Where(z => z.Value.Name.StartsWith(Uid));

        #region 执行 Migrate 更新数据 MigrateDatabaseAsync()

        /// <summary>
        /// 执行 Migrate 更新数据
        /// </summary>
        /// <param name="serviceProvider"></param>
        /// <param name="dbContextType"></param>
        /// <param name="checkdbContextType">是否需要对 dbContextType 类型进行检查</param>
        /// <returns></returns>
        protected virtual async Task MigrateDatabaseAsync(IServiceProvider serviceProvider, Type dbContextType, bool checkdbContextType = true)
        {
            if (checkdbContextType && !dbContextType.IsSubclassOf(typeof(DbContext)))
            {
                throw new NcfDatabaseException("dbContextType 参数必须继承自 DbContext", null, dbContextType);
            }

            var mySenparcEntities = serviceProvider.GetService(dbContextType) as DbContext;
            await mySenparcEntities.Database.MigrateAsync().ConfigureAwait(false);//更新数据库
        }


        /// <summary>
        /// 执行 Migrate 更新数据
        /// </summary>
        /// <param name="serviceProvider"></param>
        /// <returns></returns>
        protected virtual async Task MigrateDatabaseAsync(IServiceProvider serviceProvider)
        {
            //当前Register对应的数据库上下文（DbContext）类型
            var dbContextType = MultipleDatabasePool.Instance.GetXncfDbContextType(this.GetType());
            await MigrateDatabaseAsync(serviceProvider, dbContextType, false);
        }

        /// <summary>
        /// 执行 Migrate 更新数据
        /// </summary>
        /// <typeparam name="TSenparcEntities"></typeparam>
        /// <param name="serviceProvider"></param>
        /// <returns></returns>
        protected virtual async Task MigrateDatabaseAsync<TSenparcEntities>(IServiceProvider serviceProvider)
            where TSenparcEntities : DbContext
        {
            await MigrateDatabaseAsync(serviceProvider, typeof(TSenparcEntities));
        }

        #endregion

        /// <summary>
        /// 安装代码
        /// </summary>
        public virtual Task InstallOrUpdateAsync(IServiceProvider serviceProvider, InstallOrUpdate installOrUpdate)
        {
            return Task.CompletedTask;
        }
        /// <summary>
        /// 卸载代码
        /// </summary>
        public virtual async Task UninstallAsync(IServiceProvider serviceProvider, Func<Task> unsinstallFunc)
        {
            await unsinstallFunc().ConfigureAwait(false);
        }

        /// <summary>
        /// 删除表（此方法请慎重使用！）
        /// </summary>
        /// <param name="serviceProvider"></param>
        /// <param name="databaseDbContext"></param>
        /// <param name="entityType">需要删除的表所对应的实体类型</param>
        /// <returns></returns>
        protected virtual async Task DropTablesAsync(IServiceProvider serviceProvider, DbContext databaseDbContext, Type[] entityType)
        {
            SenparcTrace.SendCustomLog("开始删除应用表格", MenuName + ", " + Name);
            var appliedMigrations = databaseDbContext.Database.GetAppliedMigrations();
            if (appliedMigrations.Count() > 0)
            {
                using (await databaseDbContext.Database.BeginTransactionAsync())
                {
                    //mySenparcEntities.Database.GetService<>
                }
                //var databaseCreator = mySenparcEntities.Database.GetService<IRelationalDatabaseCreator>();


                var currentDatabaseConfiguration = DatabaseConfigurationFactory.Instance.Current;
                foreach (var type in entityType)
                {
                    try
                    {

                        var tableName = databaseDbContext.Model.FindEntityType(type).GetTableName();

                        #region 适用于 SQL Server
                        //SenparcTrace.SendCustomLog("开始删除表格", $"[schma].[tableName]：[{schma}].[{tableName}]");
                        ////mySenparcEntities.Colors.FromSqlRaw($"DELETE FROM [{key}]");

                        //string fullTableName = $"[{tableName}]";
                        //if (!schma.IsNullOrEmpty())
                        //{
                        //    fullTableName = $"[{schma}].{fullTableName}";
                        //}
                        #endregion

                        SenparcTrace.SendCustomLog("开始删除表格", $"tableName：{tableName}");

                        string dropTableSql = currentDatabaseConfiguration.GetDropTableSql(databaseDbContext, tableName);
                        if (!dropTableSql.IsNullOrEmpty())
                        {
                            int keyExeCount = await databaseDbContext.Database.ExecuteSqlRawAsync(dropTableSql);
                            SenparcTrace.SendCustomLog("影响行数", keyExeCount + " 行");
                        }
                        else
                        {
                            SenparcTrace.SendCustomLog("执行结果", $"{currentDatabaseConfiguration.GetType().Name} 内部已处理，无需单独执行 SQL");
                        }
                    }
                    catch (Exception ex)
                    {
                        SenparcTrace.BaseExceptionLog(new NcfDatabaseException(ex.Message, currentDatabaseConfiguration.GetType(), databaseDbContext.GetType(), ex));
                    }

                }

                //删除 Migration 记录
                if (this is IXncfDatabase databaseRegister)
                {
                    var migrationHistoryTableName = NcfDatabaseHelper.GetDatabaseMigrationHistoryTableName(databaseRegister);
                    SenparcTrace.SendCustomLog("开始删除 DatabaseMigrationHistory 表格", $"[{migrationHistoryTableName}]");
                    string sqlStr = currentDatabaseConfiguration.GetDropTableSql(databaseDbContext, migrationHistoryTableName);
                    if (!sqlStr.IsNullOrEmpty())
                    {
                        int historyExeCount = await databaseDbContext.Database.ExecuteSqlRawAsync(sqlStr);
                        SenparcTrace.SendCustomLog("影响行数", historyExeCount + " 行");
                    }
                    else
                    {
                        SenparcTrace.SendCustomLog("执行结果", $"{currentDatabaseConfiguration.GetType().Name} 内部已处理，无需单独执行 SQL");
                    }

                }
            }
        }

        /// <summary>
        /// 获取首页Url
        /// <para>仅限实现了 IAreaRegister 接口之后的 Register，否则将返回 null</para>
        /// </summary>
        /// <returns></returns>
        public virtual string GetAreaHomeUrl()
        {
            if (this is IAreaRegister)
            {
                var homeUrl = (this as IAreaRegister).HomeUrl;
                return GetAreaUrl(homeUrl);
            }
            return null;
        }
        /// <summary>
        /// 获取指定网页的Url
        /// <para>仅限实现了 IAreaRegister 接口之后的 Register，否则将返回 null</para>
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public virtual string GetAreaUrl(string path)
        {
            if (this is IAreaRegister)
            {
                if (path == null)
                {
                    return "/";
                }

                path += path.Contains("?") ? "&" : "?";
                path += $"uid={Uid}";
                return path;
            }
            return null;
        }

        /// <summary>
        /// 添加模块
        /// </summary>
        /// <param name="services"></param>
        /// <param name="configuration"></param>
        /// <returns></returns>
        public virtual IServiceCollection AddXncfModule(IServiceCollection services, IConfiguration configuration)
        {
            if (this is IXncfDatabase databaseRegister)
            {
                //遍历所有Register中的数据库进行注册
                if (XncfDatabaseDbContextPool.Instance.ContainsKey(this.GetType()))
                {
                    var dbContextTypes = XncfDatabaseDbContextPool.Instance[this.GetType()];

                    foreach (var dbContextType in dbContextTypes.Values)
                    {
                        var dbOptionBuilderType = dbContextType.GetConstructors()
                                            .First().GetParameters().First().ParameterType;

                        //定义 XncfSenparcEntities 实例生成
                        Func<IServiceProvider, DbContext> implementationFactory = s =>
                        {
                            DbContextOptionsBuilder dbOptionBuilder;
                            if (dbOptionBuilderType.GenericTypeArguments.Length > 0)
                            {
                                //带泛型
                                ////准备创建 DbContextOptionsBuilder 实例，定义类型
                                dbOptionBuilderType = typeof(DbContextOptionsBuilder<>);
                                //dbOptionBuilderType = typeof(RelationalDbContextOptionsBuilder<,>);
                                //获取泛型对象类型，如：DbContextOptionsBuilder<SenparcEntities>
                                dbOptionBuilderType = dbOptionBuilderType.MakeGenericType(dbContextType);

                                //创建 DbContextOptionsBuilder 实例
                                dbOptionBuilder = Activator.CreateInstance(dbOptionBuilderType) as DbContextOptionsBuilder;
                            }
                            else
                            {
                                //不带泛型
                                dbOptionBuilder = new DbContextOptionsBuilder();
                            }

                            //获取当前数据库配置
                            var currentDatabaseConfiguration = DatabaseConfigurationFactory.Instance.Current;

                            //使用数据库
                            currentDatabaseConfiguration.UseDatabase(dbOptionBuilder, Ncf.Core.Config.SenparcDatabaseConnectionConfigs.ClientConnectionString, new XncfDatabaseData(databaseRegister, null /*默认使用当前 Register 程序集*/), (b, xncfDatabaseData) =>
                                {
                                    ////进行附加配置
                                    //this.DbContextOptionsAction?.Invoke(b);

                                    //执行 DatabaseConfiguration 中的 DbContextOptionsActionBase，进行基础配置;
                                    currentDatabaseConfiguration.DbContextOptionsActionBase(b, xncfDatabaseData);

                                    //其他需要进行的配置，如对于 SQL Server：
                                    //b.EnableRetryOnFailure(
                                    //    maxRetryCount: 5,
                                    //    maxRetryDelay: TimeSpan.FromSeconds(5),
                                    //    errorNumbersToAdd: new int[] { 2 });
                                });

                            //创建 SenparcEntities 实例
                            var xncfSenparcEntities = Activator.CreateInstance(dbContextType, dbOptionBuilder.Options);
                            return xncfSenparcEntities as DbContext;
                        };
                        //添加 XncfSenparcEntities 依赖注入配置
                        services.AddScoped(dbContextType, implementationFactory);
                        //注册当前数据库的对象（必须）
                        EntitySetKeys.TryLoadSetInfo(dbContextType);
                    }
                }
                else
                {
                    var errMsg = $"{databaseRegister.GetType().FullName} 未注册任何数据库 DbContext！";
                    SenparcTrace.BaseExceptionLog(new NcfDatabaseException(errMsg, null, null));
                    Console.WriteLine(errMsg);
                }

                //添加数据库相关注册过程
                databaseRegister.AddXncfDatabaseModule(services);
            }
            return services;
        }

        /// <summary>
        /// 在 startup.cs 的 Configure() 方法中执行配置
        /// </summary>
        /// <param name="app"></param>
        /// <param name="registerService"></param>
        /// <returns></returns>
        public virtual IApplicationBuilder UseXncfModule(IApplicationBuilder app, IRegisterService registerService)
        {
            return app;
        }

        public static object AddAutoMapMappingLock = new object();
        public virtual void AddAutoMapMapping(Action<Profile> mapping)
        {
            if (AutoMapMappingConfigs == null)
            {
                lock (AddAutoMapMappingLock)
                {
                    if (AutoMapMappingConfigs == null)
                    {
                        AutoMapMappingConfigs = new ConcurrentBag<Action<Profile>>();
                    }
                }
            }
            AutoMapMappingConfigs.Add(mapping);
        }

        ///// <summary>
        ///// 数据库 DbContext 选项配置（附加配置）
        ///// <para>第1个参数：IRelationalDbContextOptionsBuilderInfrastructure</para>
        ///// <para>第2个参数：AssemblyName</para>
        ///// </summary>
        //public Action<IRelationalDbContextOptionsBuilderInfrastructure, XncfDatabaseData> DbContextOptionsAction => (builder, xncfDatabaseData) =>
        //{
        //    if (xncfDatabaseData!=null && !xncfDatabaseData.AssemblyName.IsNullOrEmpty())
        //    {

        //    }
        //};
    }
}


<|start_filename|>src/Basic/Senparc.Ncf.XncfBase/Functions/FunctionBase.cs<|end_filename|>
﻿using Senparc.CO2NET.Trace;
using Senparc.Ncf.XncfBase.Functions;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace Senparc.Ncf.XncfBase
{
    public abstract class FunctionBase : IXncfFunction
    {
        /// <summary>
        /// 方法名称
        /// <para>注意：Name 必须在单个 Xncf 模块中唯一！</para>
        /// </summary>
        public abstract string Name { get; }

        //TODO:检查 name 冲突的情况

        /// <summary>
        /// 说明
        /// </summary>
        public abstract string Description { get; }

        /// <summary>
        /// FunctionParameter 类型
        /// </summary>
        public abstract Type FunctionParameterType { get; }

        public virtual IFunctionParameter GenerateParameterInstance()
        {
            var obj = FunctionParameterType.Assembly.CreateInstance(FunctionParameterType.FullName) as IFunctionParameter;
            return obj;
        }

        /// <summary>
        /// ServiceProvider 实例
        /// </summary>
        public virtual IServiceProvider ServiceProvider { get; set; }


        public FunctionBase(IServiceProvider serviceProvider)
        {
            ServiceProvider = serviceProvider;
        }

        /// <summary>
        /// 执行程序
        /// </summary>
        /// <param name="param"></param>
        /// <returns></returns>
        public abstract FunctionResult Run(IFunctionParameter param);

        /// <summary>
        /// 记录日志
        /// </summary>
        /// <param name="sb"></param>
        /// <param name="msg"></param>
        protected void RecordLog(StringBuilder sb, string msg)
        {
            FunctionHelper.RecordLog(sb, msg);
        }

        /// <summary>
        /// 获取所有参数的信息列表
        /// </summary>
        /// <param name="serviceProvider"></param>
        /// <param name="tryLoadData">是否尝试载入数据（参数必须实现 IFunctionParameterLoadDataBase 接口）</param>
        /// <returns></returns>
        public async Task<List<FunctionParameterInfo>> GetFunctionParameterInfoAsync(IServiceProvider serviceProvider, bool tryLoadData)
        {
            var obj = GenerateParameterInstance();

            //预载入参数
            if (tryLoadData && obj is IFunctionParameterLoadDataBase loadDataParam)
            {
                await loadDataParam.LoadData(serviceProvider);//载入参数
            }

            var props = FunctionParameterType.GetProperties();
            ParameterType parameterType = ParameterType.Text;
            List<FunctionParameterInfo> result = new List<FunctionParameterInfo>();
            foreach (var prop in props)
            {
                SelectionList selectionList = null;
                parameterType = ParameterType.Text;//默认为文本内容
                //判断是否存在选项
                if (prop.PropertyType == typeof(SelectionList))
                {
                    var selections = prop.GetValue(obj, null) as SelectionList;
                    switch (selections.SelectionType)
                    {
                        case SelectionType.DropDownList:
                            parameterType = ParameterType.DropDownList;
                            break;
                        case SelectionType.CheckBoxList:
                            parameterType = ParameterType.CheckBoxList;
                            break;
                        default:
                            //TODO: throw
                            break;
                    }
                    selectionList = selections;
                }

                var name = prop.Name;
                string title = null;
                string description = null;
                var isRequired = prop.GetCustomAttribute<RequiredAttribute>() != null;
                var descriptionAttr = prop.GetCustomAttribute<DescriptionAttribute>();
                if (descriptionAttr != null && descriptionAttr.Description != null)
                {
                    //分割：名称||说明
                    var descriptionAttrArr = descriptionAttr.Description.Split(new[] { "||" }, StringSplitOptions.RemoveEmptyEntries);
                    title = descriptionAttrArr[0];
                    if (descriptionAttrArr.Length > 1)
                    {
                        description = descriptionAttrArr[1];
                    }
                }
                var systemType = prop.PropertyType.Name;

                object value = null;
                try
                {
                    value = prop.GetValue(obj);
                }
                catch (Exception ex)
                {
                    SenparcTrace.BaseExceptionLog(ex);
                }

                var functionParamInfo = new FunctionParameterInfo(name, title, description, isRequired, systemType, parameterType,
                                            selectionList ?? new SelectionList(SelectionType.Unknown), value);
                result.Add(functionParamInfo);
            }
            return result;
        }
    }
}


<|start_filename|>src/Extensions/Senparc.Xncf.DatabaseToolkit/Senparc.Xncf.DatabaseToolkit/Register.Database.cs<|end_filename|>
﻿using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
using Senparc.CO2NET.Trace;
using Senparc.Ncf.Core.Models;
using Senparc.Ncf.Database;
using Senparc.Ncf.XncfBase;
using Senparc.Xncf.DatabaseToolkit.Functions;
using System;

namespace Senparc.Xncf.DatabaseToolkit
{
    public partial class Register : IXncfDatabase
    {
        public const string DATABASE_PREFIX = "DatabaseToolkit";
        public string DatabaseUniquePrefix => DATABASE_PREFIX;

        public Type TryGetXncfDatabaseDbContextType => MultipleDatabasePool.Instance.GetXncfDbContextType(this);

        public void AddXncfDatabaseModule(IServiceCollection services)
        {
            //SenparcTrace.SendCustomLog("执行调试", "DatabaseToolkit.AddXncfDatabaseModule");
            services.AddScoped<DbConfig>();
            services.AddScoped<SetConfig>();
            services.AddScoped<SetConfig.SetConfig_Parameters>();

            //AutoMap映射
            base.AddAutoMapMapping(profile =>
            {
                profile.CreateMap<SetConfig.SetConfig_Parameters, SetConfig>();
                profile.CreateMap<SetConfig.SetConfig_Parameters, DbConfig>();
            });
        }

        public void OnModelCreating(ModelBuilder modelBuilder)
        {
            //实现 [XncfAutoConfigurationMapping] 特性之后，可以自动执行，无需手动添加
            //modelBuilder.ApplyConfiguration(new DbConfig_WeixinUserConfigurationMapping());
        }
    }
}


<|start_filename|>src/Extensions/Senparc.Xncf.Swagger/Register.Area.cs<|end_filename|>
//using Enyim.Caching.Configuration;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Senparc.CO2NET.Trace;
using Senparc.Ncf.Core.Areas;
using Senparc.Ncf.Core.Config;
using Senparc.Xncf.Swagger.Utils;
using System;
using System.Collections.Generic;
using System.IO;

namespace Senparc.Xncf.Swagger
{
    public partial class Register : IAreaRegister //注册 XNCF 页面接口（按需选用）
    {
        #region IAreaRegister 接口

        public string HomeUrl => "/Admin/Swagger/Index";

        public List<AreaPageMenuItem> AareaPageMenuItems => new List<AreaPageMenuItem>() {
             new AreaPageMenuItem(GetAreaHomeUrl(),"首页","fa fa-laptop"),
                     };

        public IMvcBuilder AuthorizeConfig(IMvcBuilder builder, IHostEnvironment env)
        {
            builder.Services
                .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
                .AddCookie(Utils.ConfigurationHelper.SWAGGER_ATUH_COOKIE, options =>
                {
                    if (Utils.ConfigurationHelper.CustsomSwaggerOptions.UseAdminAuth)
                        options.LoginPath = "/Admin/Login/";
                    else
                        options.LoginPath = $"/{Utils.ConfigurationHelper.CustsomSwaggerOptions.RoutePrefix}/login.html";
                    options.Cookie.HttpOnly = false;
                });

            builder.AddRazorPagesOptions(options =>
            {
                //此处可配置页面权限
            });

            SenparcTrace.SendCustomLog("Swagger 启动", "完成 Area:Senparc.Xncf.Swagger 注册");

            return builder;
        }

        #endregion

        #region IXncfRazorRuntimeCompilation 接口
        public string LibraryPath => Path.GetFullPath(Path.Combine(SiteConfig.WebRootPath, "..", "..", "Senparc.Xncf.Swagger"));
        #endregion
    }
}


<|start_filename|>src/Extensions/Senparc.Xncf.DatabaseToolkit/Senparc.Xncf.DatabaseToolkit/Functions/ShowDatabaseConfiguration.cs<|end_filename|>
﻿using Senparc.Ncf.Core.Models;
using Senparc.Ncf.Database;
using Senparc.Ncf.XncfBase;
using Senparc.Ncf.XncfBase.Functions;
using System;
using System.Collections.Generic;
using System.Text;

namespace Senparc.Xncf.DatabaseToolkit.Functions
{
    /// <summary>
    /// 显示当前的数据库配置类型
    /// </summary>
    public class ShowDatabaseConfiguration : FunctionBase
    {
        public class Parameters : IFunctionParameter
        {

        }

        public ShowDatabaseConfiguration(IServiceProvider serviceProvider) : base(serviceProvider)
        {
        }

        public override string Name => "查看数据库配置类型";

        public override string Description => "查看实现 IDatabaseConfiguration 接口的数据库配置类型";

        public override Type FunctionParameterType => typeof(Parameters);

        public override FunctionResult Run(IFunctionParameter param)
        {
            return FunctionHelper.RunFunction<Parameters>(param, (typeParam, sb, result) =>
            {
                var databaseConfigurationFactory = DatabaseConfigurationFactory.Instance;
                var currentDatabaseConfiguration = databaseConfigurationFactory.Current;
                result.Message = $"当前 DatabaseConfiguration：{currentDatabaseConfiguration.GetType().Name}，数据库类型：{currentDatabaseConfiguration.MultipleDatabaseType}";
            });
        }
    }
}


<|start_filename|>src/Extensions/Senparc.Xncf.DatabaseToolkit/Senparc.Xncf.DatabaseToolkit/Functions/ExportSQL.cs<|end_filename|>
﻿using Microsoft.EntityFrameworkCore;
using Senparc.Ncf.Core.Models;
using Senparc.Ncf.XncfBase;
using Senparc.Ncf.XncfBase.Functions;
using System;

namespace Senparc.Xncf.DatabaseToolkit.Functions
{
    public class ExportSQL : FunctionBase
    {
        public class ExportSQL_Parameters : IFunctionParameter
        {

        }

        //注意：Name 必须在单个 Xncf 模块中唯一！
        public override string Name => "导出当前数据库 SQL 脚本";

        public override string Description => "导出当前站点正在使用的所有表的 SQL 脚本";

        public override Type FunctionParameterType => typeof(ExportSQL_Parameters);

        public ExportSQL(IServiceProvider serviceProvider) : base(serviceProvider)
        {
        }

        /// <summary>
        /// 运行
        /// </summary>
        /// <param name="param"></param>
        /// <returns></returns>
        public override FunctionResult Run(IFunctionParameter param)
        {
            return FunctionHelper.RunFunction<ExportSQL_Parameters>(param, (typeParam, sb, result) =>
             {
                 RecordLog(sb, "开始获取 ISenparcEntities 对象");
                 var senparcEntities = ServiceProvider.GetService(typeof(ISenparcEntitiesDbContext)) as SenparcEntitiesBase;
                 RecordLog(sb, "获取 ISenparcEntities 对象成功");
                 RecordLog(sb, "开始生成 SQL ");
                 var sql = senparcEntities.Database.GenerateCreateScript();
                 RecordLog(sb, "SQL 已生成：");
                 RecordLog(sb, $"============ NCF Database {SystemTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}  ============");
                 RecordLog(sb, "\r\n\r\n" + sql + "\r\n\r\n");
                 RecordLog(sb, $"============ NCF Database {SystemTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}  ============");
                 result.Message = "SQL 已生成，请下载日志文件！";
             });
        }
    }
}


<|start_filename|>src/Extensions/Senparc.Xncf.DatabaseToolkit/Senparc.Xncf.DatabaseToolkit/Register.Thread.cs<|end_filename|>
﻿using Microsoft.Extensions.DependencyInjection;
using Senparc.CO2NET.Extensions;
using Senparc.CO2NET.Trace;
using Senparc.Ncf.Core.Exceptions;
using Senparc.Ncf.Service;
using Senparc.Ncf.XncfBase;
using Senparc.Ncf.XncfBase.Threads;
using Senparc.Xncf.DatabaseToolkit.Functions;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;

namespace Senparc.Xncf.DatabaseToolkit
{
    public partial class Register : IXncfThread
    {
        public void ThreadConfig(XncfThreadBuilder xncfThreadBuilder)
        {
            xncfThreadBuilder.AddThreadInfo(new Ncf.XncfBase.Threads.ThreadInfo(
                name: "定时备份",
                intervalTime: TimeSpan.FromSeconds(30),
                task: async (app, threadInfo) =>
                {
                    try
                    {
                        //SenparcTrace.SendCustomLog("执行调试", "DatabaseToolkit.Register.ThreadConfig");
                        threadInfo.RecordStory("开始检测并备份");

                        using (var scope = app.ApplicationServices.CreateScope())
                        {
                            var serviceProvider = scope.ServiceProvider;

                            //检测当前模块是否可用
                            XncfRegisterManager xncfRegisterManager = new XncfRegisterManager(serviceProvider);
                            var xncfIsValiable = await xncfRegisterManager.CheckXncfValiable(this);
                            if (!xncfIsValiable)
                            {
                                throw new NcfModuleException($"{this.MenuName} 模块当前不可用，跳过数据库自动备份轮询");
                            }

                            //初始化数据库备份方法
                            BackupDatabase backupDatabase = new BackupDatabase(serviceProvider);
                            //初始化参数
                            var backupParam = new BackupDatabase.BackupDatabase_Parameters();
                            var dbConfigService = serviceProvider.GetService<ServiceBase<DbConfig>>();
                            var dbConfig = await dbConfigService.GetObjectAsync(z => true);
                            var stopBackup = false;
                            try
                            {
                                if (dbConfig != null && dbConfig.BackupCycleMinutes > 0 && !dbConfig.BackupPath.IsNullOrEmpty())
                                {
                                    if (SystemTime.NowDiff(dbConfig.LastBackupTime) > TimeSpan.FromMinutes(dbConfig.BackupCycleMinutes))
                                    {
                                        backupParam.Path = dbConfig.BackupPath;
                                        //await backupParam.LoadData(serviceProvider);
                                        //threadInfo.RecordStory("完成备份设置数据载入");
                                    }
                                    else
                                    {
                                        stopBackup = true;
                                    }
                                }
                                else
                                {
                                    threadInfo.RecordStory("不需要备份，或没有设置备份周期/路径，已忽略本次备份计划");
                                    stopBackup = true;//不需要备份，或没有设置，返回
                                }
                            }
                            catch (Exception ex)
                            {
                                threadInfo.RecordStory($@"遇到异常，可能未配置数据库，已忽略本次备份计划。如需启动，请更新此模块到最新版本。
异常信息：{ex.Message}
{ex.StackTrace}");
                                stopBackup = true;//可能没有配置数据库，返回
                            }

                            if (stopBackup)
                            {
                                return;
                            }


                            //执行备份方法
                            threadInfo.RecordStory("备份开始：" + backupParam.Path);
                            var result = backupDatabase.Run(backupParam);
                            if (!result.Success)
                            {
                                threadInfo.RecordStory("执行备份发生异常：" + result.Message);
                                throw new Exception("执行备份发生异常");
                            }

                            dbConfig.RecordBackupTime();
                            await dbConfigService.SaveObjectAsync(dbConfig);

                            threadInfo.RecordStory("完成数据库自动备份：" + result.Message);
                            SenparcTrace.SendCustomLog("完成数据库自动备份", backupParam.Path);
                        }
                    }
                    catch (NcfModuleException ex)
                    {
                        throw;
                    }
                    catch
                    {
                        throw;
                    }
                    finally
                    {
                        threadInfo.RecordStory("检测并备份结束");
                    }
                },
                exceptionHandler: ex =>
                {
                    SenparcTrace.SendCustomLog("DatabaseToolkit", $@"{ex.Message}
{ex.StackTrace}
{ex.InnerException?.StackTrace}");
                    return Task.CompletedTask;
                }));
        }
    }
}


<|start_filename|>src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/Functions/AddMigration.cs<|end_filename|>
﻿using Senparc.Ncf.Service;
using Senparc.Ncf.XncfBase;
using Senparc.Ncf.XncfBase.Functions;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using Microsoft.Extensions.DependencyInjection;
using System.Threading.Tasks;
using System.Diagnostics;
using Senparc.Ncf.Database.MultipleMigrationDbContext;
using Senparc.Ncf.Core.Models;

namespace Senparc.Xncf.XncfBuilder.Functions
{

    public class AddMigration : FunctionBase
    {
        public AddMigration(IServiceProvider serviceProvider) : base(serviceProvider)
        {
        }

        public class Parameters : FunctionParameterLoadDataBase, IFunctionParameter
        {
            [Required]
            [MaxLength(250)]
            [Description("Senparc.Web.DatabasePlant 项目物理路径||用于使用 netcoreapp3.1 等目标框架启动迁移操作，如：E:\\Senparc项目\\NeuCharFramework\\NCF\\src\\Senparc.Web.DatabasePlant\\")]
            public string DatabasePlantPath { get; set; }

            [Required]
            [MaxLength(250)]
            [Description("XNCF 项目路径||输入 XNCF 项目根目录的完整物理路径，如：E:\\Senparc项目\\NeuCharFramework\\NCF\\src\\MyDemo.Xncf.NewApp\\")]
            public string ProjectPath { get; set; }

            [Description("生成数据库类型||更多类型陆续添加中")]
            public SelectionList DatabaseTypes { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] {
                 new SelectionItem(MultipleDatabaseType.Sqlite.ToString(),MultipleDatabaseType.Sqlite.ToString(),"",true),
                 new SelectionItem(MultipleDatabaseType.SqlServer.ToString(),MultipleDatabaseType.SqlServer.ToString(),"",true),
                 new SelectionItem(MultipleDatabaseType.MySql.ToString(),MultipleDatabaseType.MySql.ToString(),"",true),
            });

            [Required]
            [MaxLength(100)]
            [Description("自定义 DbContext 名称||如：MyDemoSenparcEntities（注意：不需要加数据库类型后缀）")]
            public string DbContextName { get; set; }

            [Required]
            [MaxLength(100)]
            [Description("更新名称||可使用英文、数字、下划线，不可以有空格，如：Add_Config")]
            public string MigrationName { get; set; }

            [Description("输出详细日志||使用 add-migration 的 -v 参数")]
            public SelectionList OutputVerbose { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] {
                 new SelectionItem("1","使用","",false)
            });

            /// <summary>
            /// 预载入数据
            /// </summary>
            /// <param name="serviceProvider"></param>
            /// <returns></returns>
            public override async Task LoadData(IServiceProvider serviceProvider)
            {
                try
                {
                    ////低版本没有数据库，此处需要try
                    //var configService = serviceProvider.GetService<ServiceBase<Config>>();
                    //var config = await configService.GetObjectAsync(z => true);
                    //if (config != null)
                    //{
                    //    configService.Mapper.Map(config, this);
                    //}
                }
                catch
                {
                }

            }
        }

        public override string Name => "Add-Migration 命令";

        public override string Description => "可视化完成多数据库的 add-migration 命令，使用 Code First 更新数据库。注意：根据计算机配置和数据库情况，执行过程可能在30-60秒不等，请耐心等待。";

        public override Type FunctionParameterType => typeof(Parameters);

        public override FunctionResult Run(IFunctionParameter param)
        {
            return FunctionHelper.RunFunction<Parameters>(param, (typeParam, sb, result) =>
            {
                if (typeParam.DatabaseTypes.SelectedValues.Count() == 0)
                {
                    result.Message = "至少选择 1 个数据库！";
                    return;
                }

                var commandTexts = new List<string>();

                //添加停机坪引用（直接引用会有问题）
                //var slnFilePath = Path.Combine(typeParam.DatabasePlantPath, "..\\");
                //commandTexts.Add($"dotnet sln {slnFilePath} add {typeParam.ProjectPath}");
                //commandTexts.Add($"dotnet add {typeParam.DatabasePlantPath} reference {typeParam.ProjectPath}");

                //进入项目目录
                commandTexts.Add(@$"cd {typeParam.ProjectPath}");

                //执行迁移
                foreach (var dbType in typeParam.DatabaseTypes.SelectedValues)
                {
                    string migrationDir = GetMigrationDir(typeParam, dbType);
                    var outputVerbose = typeParam.OutputVerbose.SelectedValues.Contains("1") ? " -v" : "";
                    var dbTypeSuffix = $"_{dbType}";
                    commandTexts.Add($"dotnet ef migrations add {typeParam.MigrationName} -c {typeParam.DbContextName}{dbTypeSuffix} -s {typeParam.DatabasePlantPath} -o {migrationDir}{outputVerbose}");
                    // --framework netcoreapp3.1
                    // 如需指定框架，可以追加上述参数，也可以支持更多参数，如net5.0
                }

                ////移除停机坪引用（直接引用会有问题）
                //commandTexts.Add($"dotnet remove {typeParam.DatabasePlantPath} reference {typeParam.ProjectPath}");
                //commandTexts.Add($"dotnet sln {slnFilePath} remove {typeParam.ProjectPath}");

                Process p = new Process();
                p.StartInfo.FileName = "cmd.exe";
                p.StartInfo.UseShellExecute = false;
                p.StartInfo.RedirectStandardInput = true;
                p.StartInfo.RedirectStandardOutput = true;
                p.StartInfo.RedirectStandardError = true;
                p.StartInfo.CreateNoWindow = true;
                string strOutput = null;
                try
                {
                    p.Start();
                    foreach (string item in commandTexts)
                    {
                        p.StandardInput.WriteLine(item);
                    }
                    p.StandardInput.WriteLine("exit");
                    strOutput = p.StandardOutput.ReadToEnd();
                    base.RecordLog(sb, strOutput);

                    //strOutput = Encoding.UTF8.GetString(Encoding.Default.GetBytes(strOutput));
                    p.WaitForExit();
                    p.Close();
                }
                catch (Exception e)
                {
                    strOutput = e.Message;
                }

                ////Pomelo-MySQL 命名有不统一的情况，需要处理
                //if (typeParam.DatabaseTypes.SelectedValues.Contains(MultipleDatabaseType.MySql.ToString()))
                //{
                //    string migrationDir = GetMigrationDir(typeParam, MultipleDatabaseType.MySql.ToString());
                //    var defaultFileName = $"{typeParam.DbContextName}ModelSnapshot.cs";
                //    var pomeloFileName = $"{typeParam.DbContextName}_MySqlModelSnapshot.cs";
                //    if (File.Exists(defaultFileName) && File.Exists(pomeloFileName))
                //    {
                //        File.Delete(defaultFileName);
                //        base.RecordLog(sb, $"扫描到不兼容常规格式的 Pomelo.EntityFrameworkCore.MySql 的快照文件：{pomeloFileName}，已将默认文件删除（{defaultFileName}）！");
                //    }
                //}

                result.Message = "执行完毕，请查看日志！";

                if (strOutput.Contains("Build FAILED", StringComparison.InvariantCultureIgnoreCase))
                {
                    result.Message += "重要提示：可能出现错误，请检查日志！";
                }
            });
        }

        /// <summary>
        /// 获取迁移文件生成目录
        /// </summary>
        /// <param name="typeParam"></param>
        /// <param name="dbType"></param>
        /// <returns></returns>
        private string GetMigrationDir(Parameters typeParam, string dbType)
        {
            return Path.Combine(typeParam.ProjectPath, "Migrations", $"Migrations.{dbType}");
        }
    }
}


<|start_filename|>src/Basic/Senparc.Ncf.XncfBase/Register.cs<|end_filename|>
﻿using AutoMapper;
using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Senparc.CO2NET.Cache;
using Senparc.CO2NET.RegisterServices;
using Senparc.CO2NET.Trace;
using Senparc.Ncf.Core.Enums;
using Senparc.Ncf.Core.Models;
using Senparc.Ncf.Core.Models.DataBaseModel;
using Senparc.Ncf.Service;
using Senparc.Ncf.XncfBase.Threads;
using Senparc.Ncf.XncfBase.Attributes;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Senparc.Ncf.Database;
using Senparc.Ncf.XncfBase.Database;
using Senparc.Ncf.Database.MultipleMigrationDbContext;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace Senparc.Ncf.XncfBase
{
    /// <summary>
    /// Xncf 全局注册类
    /// </summary>
    public static class Register
    {
        /// <summary>
        /// 所有线程的集合
        /// </summary>
        public static ConcurrentDictionary<ThreadInfo, Thread> ThreadCollection { get; set; } = new ConcurrentDictionary<ThreadInfo, Thread>();

        /// <summary>
        /// 所有自动注册 Xncf 的数据库的 ConfigurationMapping 对象
        /// </summary>
        public static List<object> XncfAutoConfigurationMappingList { get; set; } = new List<object>();
        //public static List<IEntityTypeConfiguration<EntityBase>> XncfAutoConfigurationMappingList = new List<IEntityTypeConfiguration<EntityBase>>();

        /// <summary>
        /// 扫描程序集分类
        /// </summary>
        enum ScanTypeKind
        {
            IXncfRegister,
            IXncfFunction,
            XncfAutoConfigurationMappingAttribute
        }

        private static void SetLog(StringBuilder sb, string log)
        {
            var msg = $"[{SystemTime.Now}] {log}";
            sb.AppendLine(msg);
            //Debug.WriteLine(msg);
            //Console.WriteLine(msg);
        }

        /// <summary>
        /// 启动 XNCF 模块引擎，包括初始化扫描和注册等过程
        /// </summary>
        /// <returns></returns>
        public static string StartEngine(this IServiceCollection services, IConfiguration configuration)
        {
            StringBuilder sb = new StringBuilder();
            SetLog(sb, "Start scanning XncfModules");
            var scanTypesCount = 0;
            var hideTypeCount = 0;
            ConcurrentDictionary<Type, ScanTypeKind> types = new ConcurrentDictionary<Type, ScanTypeKind>();


            //所有 XNCF 模块，包括被忽略的。
            //var cache = CacheStrategyFactory.GetObjectCacheStrategyInstance();
            //using (cache.BeginCacheLock("Senparc.Ncf.XncfBase.Register", "Scan")) //在注册阶段还未完成缓存配置
            {

                try
                {
                    //遍历所有程序集
                    var assemblies = AppDomain.CurrentDomain.GetAssemblies();
                    foreach (var a in assemblies)
                    {
                        scanTypesCount++;
                        var aTypes = a.GetTypes();
                        //遍历程序集内的所有类型
                        foreach (var t in aTypes)
                        {
                            if (t.IsAbstract)
                            {
                                continue;//忽略抽象类
                            }
                            //Console.WriteLine(t.GetType().Name);
                            //获取 XncfRegister
                            if (t.GetInterfaces().Contains(typeof(IXncfRegister)))
                            {
                                types[t] = ScanTypeKind.IXncfRegister;
                            }
                            //获取 XncfFunction
                            //if (t.GetInterfaces().Contains(typeof(IXncfFunction)))
                            //{
                            //    types[t] = ScanTypeKind.IXncfFunction; /* 暂时不收录处理 */
                            //}
                            //获取 XncfAutoConfigurationMapping
                            if (t.GetCustomAttributes(true).FirstOrDefault(z => z is XncfAutoConfigurationMappingAttribute) != null
                                /*&& t.GetInterfaces().Contains(typeof(IEntityTypeConfiguration<>))*/)
                            {
                                types[t] = ScanTypeKind.XncfAutoConfigurationMappingAttribute;
                            }


                            //获取多数据库配置（XncfDatabaseDbContext 的子类）
                            if (t.IsSubclassOf(typeof(DbContext)) /*t.IsSubclassOf(typeof(XncfDatabaseDbContext))*/ &&
                               t.GetInterface(nameof(ISenparcEntitiesDbContext)) != null &&
                              t.GetCustomAttributes(true).FirstOrDefault(z => z is MultipleMigrationDbContextAttribute) != null)
                            {
                                //获取特性
                                var multiDbContextAttr = t.GetCustomAttributes(true).FirstOrDefault(z => z is MultipleMigrationDbContextAttribute) as MultipleMigrationDbContextAttribute;

                                //添加配置
                                var multipleDatabasePool = MultipleDatabasePool.Instance;
                                var result = multipleDatabasePool.TryAdd(multiDbContextAttr, t);
                                SetLog(sb, result);
                            }

                        }
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"扫描程集异常退出，可能无法获得完整程序集信息：{ex.Message}");
                }

                SetLog(sb, $"Satisfies the ScanTypeKind condition: {types.Count()}");

                //先注册 XncfRegister
                {
                    //筛选
                    var allTypes = types.Where(z => z.Value == ScanTypeKind.IXncfRegister/* && z.Key.GetInterfaces().Contains(typeof(IXncfRegister))*/)
                        .Select(z => z.Key);
                    //按照优先级进行排序
                    var orderedTypes = allTypes.OrderByDescending(z =>
                    {
                        var orderAttribute = z.GetCustomAttributes(true).FirstOrDefault(attr => attr is XncfOrderAttribute) as XncfOrderAttribute;
                        if (orderAttribute != null)
                        {
                            return orderAttribute.Order;
                        }
                        return 0;
                    });


                    foreach (var type in orderedTypes)
                    {
                        SetLog(sb, $"Scan IXncfRegister: {type.FullName}");

                        var register = type.Assembly.CreateInstance(type.FullName) as IXncfRegister;

                        if (!XncfRegisterManager.RegisterList.Contains(register))
                        {
                            if (XncfRegisterManager.RegisterList.Exists(z => z.Uid.Equals(register.Uid, StringComparison.OrdinalIgnoreCase)))
                            {
                                throw new XncfFunctionException("已经存在相同 Uid 的模块：" + register.Uid);
                            }

                            if (register.IgnoreInstall)
                            {
                                hideTypeCount++;
                            }
                            XncfRegisterManager.RegisterList.Add(register);//只有允许安装的才进行注册，否则执行完即结束
                            services.AddScoped(type);//DI 中注册
                            foreach (var functionType in register.Functions)
                            {
                                services.AddScoped(functionType);//DI 中注册
                            }
                        }

                        //初始化数据库
                        if (register is IXncfDatabase)
                        {

                        }

                    }

                    #region 暂时不收录 IXncfFunction

                    /* 暂时不收录 */
                    ////再扫描具体方法
                    //foreach (var type in types.Where(z => z != null && z.GetInterfaces().Contains(typeof(IXncfFunction))))
                    //{
                    //    SetLog(sb, "扫描到 IXncfFunction：{type.FullName}");

                    //    if (!ModuleFunctionCollection.ContainsKey(type))
                    //    {
                    //        throw new NCFExceptionBase($"{type.FullName} 未能提供正确的注册方法！");
                    //    }

                    //    var function = type as IXncfFunction;
                    //    ModuleFunctionCollection[type].Add(function);
                    //}

                    #endregion
                }

                //处理 XncfAutoConfigurationMappingAttribute
                {
                    var allTypes = types.Where(z => z.Value == ScanTypeKind.XncfAutoConfigurationMappingAttribute).Select(z => z.Key);
                    foreach (var type in allTypes)
                    {
                        var obj = type.Assembly.CreateInstance(type.FullName);
                        XncfAutoConfigurationMappingList.Add(obj /*as IEntityTypeConfiguration<EntityBase>*/);
                    }
                }
            }

            var scanResult = $"The initialization scan ended, scanning a total of {scanTypesCount} Assemblies.";
            if (hideTypeCount > 0)
            {
                scanResult += $"Among them, {hideTypeCount} assemblies are non-installed assemblies and will not be cached.";
            }
            SetLog(sb, $"{scanResult}");


            //Repository & Service
            services.AddScoped(typeof(Senparc.Ncf.Repository.IRepositoryBase<>), typeof(Senparc.Ncf.Repository.RepositoryBase<>));
            services.AddScoped(typeof(Senparc.Ncf.Repository.RepositoryBase<>));
            services.AddScoped(typeof(Senparc.Ncf.Repository.IClientRepositoryBase<>), typeof(Senparc.Ncf.Repository.ClientRepositoryBase<>));
            services.AddScoped(typeof(Senparc.Ncf.Repository.ClientRepositoryBase<>));
            services.AddScoped(typeof(IServiceBase<>), typeof(ServiceBase<>));
            services.AddScoped(typeof(ServiceBase<>));

            //ConfigurationMapping
            services.AddScoped(typeof(ConfigurationMappingBase<>));
            services.AddScoped(typeof(ConfigurationMappingWithIdBase<,>));


            services.AddScoped(typeof(DbContextOptionsBuilder<>));
            services.AddScoped(typeof(DbContextOptionsBuilder));

           
            //支持 AutoMapper
            //引入当前系统
            services.AddAutoMapper(z => z.AddProfile<Core.AutoMapper.SystemProfile>());
            //引入所有模块
            services.AddAutoMapper(z => z.AddProfile<AutoMapper.XncfModuleProfile>());

            //说明：AutoMapper 需要放到 XNCF 注册之前，因为 XNCF 内呢能存在动态生成的程序集引发异常

            //微模块进行 Service 注册
            foreach (var xncfRegister in XncfRegisterManager.RegisterList)
            {
                xncfRegister.AddXncfModule(services, configuration);
            }
            SetLog(sb, $"Finish services.AddXncfModule(): Total of {scanTypesCount} assemblies were scanned.");

            return sb.ToString();
        }

        /// <summary>
        /// 扫描并安装
        /// </summary>
        /// <param name="xncfModuleDtos">现有已安装的模块</param>
        /// <param name="serviceProvider">IServiceProvider</param>
        /// <param name="afterInstalledOrUpdated">安装或更新后执行</param>
        /// <param name="justScanThisUid">只扫描并更新特定的Uid</param>
        /// <returns></returns>
        public static async Task<string> ScanAndInstall(IList<CreateOrUpdate_XncfModuleDto> xncfModuleDtos,
            IServiceProvider serviceProvider,
            Func<IXncfRegister, InstallOrUpdate, Task> afterInstalledOrUpdated = null,
            string justScanThisUid = null)
        {
            StringBuilder sb = new StringBuilder();
            SetLog(sb, "开始扫描 XncfModules");

            //先注册
            var updatedCount = 0;
            var cache = CacheStrategyFactory.GetObjectCacheStrategyInstance();
            using (await cache.BeginCacheLockAsync("Senparc.Ncf.XncfBase.Register", "Scan").ConfigureAwait(false))
            {
                foreach (var register in XncfRegisterManager.RegisterList)
                {
                    SetLog(sb, $"扫描到 IXncfRegister：{register.GetType().FullName}");
                    if (register.IgnoreInstall)
                    {
                        SetLog(sb, $"当前模块要求忽略安装 uid：[{justScanThisUid}]，此模块跳过");
                        continue;
                    }

                    if (justScanThisUid != null && register.Uid != justScanThisUid)
                    {
                        SetLog(sb, $"由于只要求更新 uid：[{justScanThisUid}]，此模块跳过");
                        continue;
                    }
                    else
                    {
                        SetLog(sb, "符合尝试安装/更新要求，继续执行");
                    }

                    var xncfModuleStoredDto = xncfModuleDtos.FirstOrDefault(z => z.Uid == register.Uid);
                    var xncfModuleAssemblyDto = new UpdateVersion_XncfModuleDto(register.Name, register.Uid, register.MenuName, register.Version, register.Description, register.Icon);

                    //检查更新，并安装到数据库
                    var xncfModuleService = serviceProvider.GetService<XncfModuleService>();
                    var installOrUpdate = await xncfModuleService.CheckAndUpdateVersionAsync(xncfModuleStoredDto, xncfModuleAssemblyDto).ConfigureAwait(false);
                    SetLog(sb, $"是否更新版本：{installOrUpdate?.ToString() ?? "未安装"}");

                    if (installOrUpdate.HasValue)
                    {
                        updatedCount++;

                        //执行安装程序
                        await register.InstallOrUpdateAsync(serviceProvider, installOrUpdate.Value).ConfigureAwait(false);

                        await afterInstalledOrUpdated?.Invoke(register, installOrUpdate.Value);
                    }
                }
            }

            SetLog(sb, $"扫描结束，共新增或更新 {updatedCount} 个程序集");
            return sb.ToString();
        }

        /// <summary>
        /// 通常在 Startup.cs 中的 Configure() 方法中执行
        /// </summary>
        /// <param name="app"></param>
        /// <param name="registerService">CO2NET 注册对象</param>
        /// <returns></returns>
        public static IApplicationBuilder UseXncfModules(this IApplicationBuilder app, IRegisterService registerService)
        {
            foreach (var register in XncfRegisterManager.RegisterList)
            {
                try
                {
                    register.UseXncfModule(app, registerService);
                }
                catch
                {
                }

                //执行中间件
                if (register is IXncfMiddleware middlewareRegister)
                {
                    try
                    {
                        middlewareRegister.UseMiddleware(app);
                    }
                    catch
                    {
                    }
                }

                //执行线程
                if (register is IXncfThread threadRegister)
                {
                    try
                    {
                        XncfThreadBuilder xncfThreadBuilder = new XncfThreadBuilder();
                        threadRegister.ThreadConfig(xncfThreadBuilder);
                        xncfThreadBuilder.Build(app, register);
                    }
                    catch (Exception ex)
                    {
                        SenparcTrace.BaseExceptionLog(ex);
                    }
                }
            }
            return app;
        }

        /// <summary>
        /// 所有已经使用的 [AutoConfigurationMapping] 对应的实体类型
        /// </summary>
        public static List<Type> ApplyedAutoConfigurationMappingTypes = new List<Type>();
        private static List<Type> AddedApplyedAutoConfigurationMappingEntityTypes = new List<Type>();
        /// <summary>
        /// 自动添加所有 XNCF 模块中标记了 [XncfAutoConfigurationMapping] 特性的对象
        /// </summary>
        public static void ApplyAllAutoConfigurationMapping(ModelBuilder modelBuilder)
        {
            var entityTypeConfigurationMethod = typeof(ModelBuilder).GetMethods()
                .FirstOrDefault(z => z.Name == "ApplyConfiguration" && z.ContainsGenericParameters && z.GetParameters().SingleOrDefault()?.ParameterType.GetGenericTypeDefinition() == typeof(IEntityTypeConfiguration<>));

            //所有模块中数据库实体中自动获取所有的 DbSet 下的实体类型

            //TODO:筛选基础类中的
            var xncfDatabaseDbContextList = MultipleDatabasePool.Instance.Values.SelectMany(z => z.Values);
            //TODO:为了进一步提高效率，可以对同一个 DbContext 的子类去重

            foreach (var xncfDatabaseDbContextType in xncfDatabaseDbContextList)
            {
                var setKeyInfoList = EntitySetKeys.GetEntitySetInfo(xncfDatabaseDbContextType).Values;
                foreach (var setKeyInfo in setKeyInfoList)
                {
                    //数据库实体类型
                    var entityType = setKeyInfo.DbSetType;
                    if (AddedApplyedAutoConfigurationMappingEntityTypes.Contains(entityType))
                    {
                        //Console.WriteLine($"\t [{xncfDatabaseDbContextType.Name}]ApplyAllAutoConfigurationMapping 有重复 setKeyInfo：{entityType.Name}，已跳过");
                        continue;
                    }
                    //Console.WriteLine($"\t [{xncfDatabaseDbContextType.Name}]ApplyAllAutoConfigurationMapping 处理 setKeyInfo：{entityType.Name}");

                    //默认空 ConfigurationMapping 对象的泛型类型
                    var blankEntityTypeConfigurationType = typeof(BlankEntityTypeConfiguration<>).MakeGenericType(entityType);
                    //创建一个新的实例
                    var blankEntityTypeConfiguration = Activator.CreateInstance(blankEntityTypeConfigurationType);
                    //最佳到末尾，这样可以优先执行用户自定义的代码
                    XncfAutoConfigurationMappingList.Add(blankEntityTypeConfiguration);
                    AddedApplyedAutoConfigurationMappingEntityTypes.Add(entityType);
                }
            }

            foreach (var autoConfigurationMapping in XncfAutoConfigurationMappingList)
            {
                if (autoConfigurationMapping == null)
                {
                    continue;
                }

                SenparcTrace.SendCustomLog("监测到 ApplyAllAutoConfigurationMapping 执行", autoConfigurationMapping.GetType().FullName);

                //获取配置实体类型，如：DbConfig_WeixinUserConfigurationMapping
                Type mappintConfigType = autoConfigurationMapping.GetType();
                //获取 IEntityTypeConfiguration<Entity> 接口
                var interfaceType = mappintConfigType.GetInterfaces().FirstOrDefault(z => z.Name.StartsWith("IEntityTypeConfiguration"));
                if (interfaceType == null)
                {
                    continue;
                }
                //实体类型，如：DbConfig
                Type entityType = interfaceType.GenericTypeArguments[0];

                //PS：此处不能过滤，否则可能导致如 SystemServiceEntities_SqlServer / SystemServiceEntities_Mysql 中只有先注册的对象才成功，后面的被忽略
                //if (ApplyedAutoConfigurationMappingTypes.Contains(entityType))
                //{
                //    //如果已经添加过则跳过。作此判断因为：原始的 XncfAutoConfigurationMappingList 数据可能和上一步自动添加 DataSet 中的对象有重复
                //    //continue;
                //}

                entityTypeConfigurationMethod.MakeGenericMethod(entityType)
                                .Invoke(modelBuilder, new object[1]
                                {
                                    autoConfigurationMapping
                 });

                ApplyedAutoConfigurationMappingTypes.Add(entityType);

                //entityTypeConfigurationMethod.Invoke(modelBuilder, new[] { autoConfigurationMapping });
            }

            //TODO：添加 IQueryTypeConfiguration<>


        }
    }
}


<|start_filename|>src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/Functions/BuildXncf.cs<|end_filename|>
﻿using Senparc.Ncf.Service;
using Senparc.Ncf.XncfBase;
using Senparc.Ncf.XncfBase.Functions;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using Microsoft.Extensions.DependencyInjection;
using System.Threading.Tasks;
using System.Diagnostics;
using Senparc.CO2NET.Extensions;

namespace Senparc.Xncf.XncfBuilder.Functions
{
    public class BuildXncf : FunctionBase
    {
        public BuildXncf(IServiceProvider serviceProvider) : base(serviceProvider)
        {
        }

        public class Parameters : FunctionParameterLoadDataBase, IFunctionParameter
        {
            [Required]
            [MaxLength(250)]
            [Description("解决方案文件（.sln）路径||输入 NCF 项目的解决方案（.sln）文件完整物理路径，将在其并列位置生成模块目录，如：E:\\Senparc项目\\NeuCharFramework\\NCF\\src\\NCF.sln")]
            public string SlnFilePath { get; set; }

            [Description("配置解决方案文件（.sln）||")]
            public SelectionList NewSlnFile { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] {
                 new SelectionItem("backup","备份 .sln 文件（推荐）","如果使用覆盖现有 .sln 文件，对当前文件进行备份",true),
                 new SelectionItem("new","生成新的 .sln 文件","如果不选择，将覆盖现有 .sln 文件（不会影响已有功能，但如果 sln 解决方案正在运行，可能会触发自动重启服务）,并推荐使用备份功能",false),
            });

            [MaxLength(250)]
            [Description("安装新模板||安装 XNCF 的模板，如果重新安装可能需要 30-40s，如果已安装过模板，可选择【已安装】，以节省模板获取时间。")]
            public SelectionList TemplatePackage { get; set; } = new SelectionList(SelectionType.DropDownList, new[] {
                 new SelectionItem("online","在线获取（从 Nuget.org 等在线环境获取最新版本，时间会略长）","从 Nuget.org 等在线环境获取最新版本，时间会略长",false),
                 new SelectionItem("local","本地安装（从 .sln 同级目录下安装 Senparc.Xncf.XncfBuilder.Template.*.nupkg 包）","从 .sln 同级目录下安装 Senparc.Xncf.XncfBuilder.Template.*.nupkg 包",false),
                 new SelectionItem("no","已安装，不需要安装新版本","请确保已经在本地安装过版本（无论新旧），否则将自动从在线获取",true),
            });

            [Description("目标框架版本||指定项目的 TFM(Target Framework Moniker)")]
            public SelectionList FrameworkVersion { get; set; } = new SelectionList(SelectionType.DropDownList, new[] {
                 new SelectionItem("netstandard2.1","netstandard2.1","使用 .NET Standard 2.1（兼容 .NET Core 3.1 和 .NET 5）",true),
                 new SelectionItem("netcoreapp3.1","netcoreapp3.1","使用 .NET Core 3.1",false),
                 new SelectionItem("net5.0","net5.0","使用 .NET 5.0",false),
            });

            [Description("自定义目标框架版本||其他目标框架版本，如果填写，将覆盖【目标框架版本】的选择")]
            public string OtherFrameworkVersion { get; set; }

            [Required]
            [MaxLength(50)]
            [Description("组织名称||用于作为模块命名空间（及名称）的前缀")]
            public string OrgName { get; set; }

            [Required]
            [MaxLength(50)]
            [Description("模块名称||同时将作为类名（请注意类名规范），支持连续英文大小写和数字，不能以数字开头，不能带有空格和.,/*等特殊符号")]
            public string XncfName { get; set; }

            //[Required]
            //[MaxLength(36)]
            //[Description("Uid||必须确保全局唯一，生成后必须固定")]
            //public string Uid { get; set; }

            [Required]
            [MaxLength(50)]
            [Description("版本号||如：1.0、2.0-beta1")]
            public string Version { get; set; }

            [Required]
            [MaxLength(50)]
            [Description("菜单名称||如“NCF 生成器”")]
            public string MenuName { get; set; }

            [Required]
            [MaxLength(50)]
            [Description("图标||支持 Font Awesome 图标集，如：fa fa-star")]
            public string Icon { get; set; }

            [Required]
            [MaxLength(400)]
            [Description("说明||模块的说明")]
            public string Description { get; set; }

            [Description("功能配置||")]
            public SelectionList UseModule { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] {
                 new SelectionItem("function","配置“函数”功能","是否需要使用函数模块（Function）",false),
                 new SelectionItem("database","配置“数据库”功能","是否需要使用数据库模块（Database），将配置空数据库",false),
                 new SelectionItem("webapi","配置“WebApi”功能","是否需要使用WebApi模块（WebApi）",false),
                 new SelectionItem("web","配置“Web（Area） 页面”功能","是否需要使用 Web 页面模块（Web），如果选择，将忽略 .NET Standard 2.1配置，强制使用 .NET Core 3.1（默认），或 .NET 5（需要选中）",false),
            });

            [Description("安装 Sample||")]
            public SelectionList UseSammple { get; set; } = new SelectionList(SelectionType.CheckBoxList, new[] {
                 new SelectionItem("1","是","是否安装数据库示例，由于展示需要，将自动安装上述“数据库”、“Web（Area） 页面”功能",false),
            });

            /// <summary>
            /// 预载入数据
            /// </summary>
            /// <param name="serviceProvider"></param>
            /// <returns></returns>
            public override async Task LoadData(IServiceProvider serviceProvider)
            {
                try
                {
                    //低版本没有数据库，此处需要try
                    var configService = serviceProvider.GetService<ServiceBase<Config>>();
                    var config = await configService.GetObjectAsync(z => true);
                    if (config != null)
                    {
                        configService.Mapper.Map(config, this);
                    }
                }
                catch
                {
                }

            }
        }

        public override string Name => "生成 XNCF";

        public override string Description => "根据配置条件生成 XNCF";

        public override Type FunctionParameterType => typeof(Parameters);

        private string _outPutBaseDir;


        /// <summary>
        /// 执行模板生成
        /// </summary>
        /// <returns></returns>
        private string BuildSample(Parameters typeParam, ref StringBuilder sb)
        {
            Console.WriteLine("开始创建 XNCF 项目");

            string projectName = GetProjectName(typeParam);
            _outPutBaseDir = Path.GetDirectoryName(typeParam.SlnFilePath);  //Path.Combine(Senparc.CO2NET.Config.RootDictionaryPath, ".."/*, $"{projectName}"*/);//找到sln根目录即可
            //_outPutBaseDir = Path.GetFullPath(_outPutBaseDir);
            if (!Directory.Exists(_outPutBaseDir))
            {
                Directory.CreateDirectory(_outPutBaseDir);
            }

            //TODO:参数合法性校验

            //获取类库当前项目引用版本信息
            Func<string, string, string> getLibVersionParam = (dllName, paramName) =>
            {
                var dllPath = Path.GetDirectoryName(new Uri(System.Reflection.Assembly.GetExecutingAssembly().CodeBase).LocalPath);
                var xncfBaseVersionPath = Path.Combine(dllPath, dllName);
                var libVBersion = FileVersionInfo.GetVersionInfo(System.Reflection.Assembly.LoadFrom(xncfBaseVersionPath).Location).ProductVersion;
                return $" --{paramName} {libVBersion}";
            };

            //配置 true 或 false 的参数
            Func<bool, string, string> getBoolParam = (condition, paramName) => $" --{paramName} {(condition ? "true" : "false")}";

            //基础信息  注意： -- 前面统一加 1 个空格
            var orgName = $" --OrgName {typeParam.OrgName}";
            var xncfName = $" --XncfName {typeParam.XncfName}";
            var guid = $" --Guid {Guid.NewGuid().ToString().ToUpper()}";
            var icon = $" --Icon \"{typeParam.Icon}\"";
            var description = $" --Description \"{typeParam.Description}\"";
            var version = $" --Version {typeParam.Version}";
            var menuName = $" --MenuName \"{typeParam.MenuName}\"";
            string xncfBaseVersion = getLibVersionParam("Senparc.Ncf.XncfBase.dll", "XncfBaseVersion");
            string ncfAreaBaseVersion = getLibVersionParam("Senparc.Ncf.AreaBase.dll", "NcfAreaBaseVersion");

            //配置功能
            var isUseSample = typeParam.UseSammple.SelectedValues.Contains("1");
            var useSample = getBoolParam(isUseSample, "UseSample");
            var useFunction = getBoolParam(typeParam.UseModule.SelectedValues.Contains("function"), "UseFunction");
            var isUseWeb = isUseSample || typeParam.UseModule.SelectedValues.Contains("web");
            var useWeb = getBoolParam(isUseWeb, "UseWeb");
            var useDatabase = getBoolParam(isUseSample, "UseDatabase");
            var useWebApi = getBoolParam(typeParam.UseModule.SelectedValues.Contains("webapi"), "UseWebApi");

            //获取当前配置的 FrameworkVersion
            var frameworkVersion = typeParam.OtherFrameworkVersion.IsNullOrEmpty()
                                        ? typeParam.FrameworkVersion.SelectedValues.First()
                                        : typeParam.OtherFrameworkVersion;
            if (isUseWeb && frameworkVersion == "netstandard2.1")
            {
                //需要使用网页，强制修正为支持 Host 的目标框架
                frameworkVersion = "netcoreapp3.1";//TODO:后续将支持 .NET 5.0
            }

            var targetFramework = $" --TargetFramework {frameworkVersion}";

            var commandTexts = new List<string> {
                $"cd {_outPutBaseDir}",
                //"echo %DATE:~0,4%-%DATE:~5,2%-%DATE:~8,2% %TIME:~0,2%:%TIME:~3,2%:%TIME:~6,2%",
                //下一句如果上方执行了dotnet new的命令，执行大约需要1分钟
                $"dotnet new xncf -n {projectName} --force --IntegrationToNcf {targetFramework}{useSample}{useFunction}{useWeb}{useDatabase}{useWebApi} {orgName}{xncfName}{guid}{icon}{description}{version}{menuName}{xncfBaseVersion}{ncfAreaBaseVersion}",
                $"dotnet add ./Senparc.Web/Senparc.Web.csproj reference ./{projectName}/{projectName}.csproj",
                $"dotnet sln {typeParam.SlnFilePath} add ./{projectName}/{projectName}.csproj --solution-folder XncfModules"
            };

            Func<Process> GetNewProcess = () =>
            {
                Process p = new Process();
                p.StartInfo.FileName = "cmd.exe";
                p.StartInfo.UseShellExecute = false;
                p.StartInfo.RedirectStandardInput = true;
                p.StartInfo.RedirectStandardOutput = true;
                p.StartInfo.RedirectStandardError = true;
                p.StartInfo.CreateNoWindow = true;
                p.Start();
                return p;
            };

            Action<Process> CloseProcess = p =>
            {
                p.WaitForExit();
                p.Close();
            };

            string strOutput;
            try
            {
                #region 检查并安装模板

                Process pListTemplate = GetNewProcess();
                Console.WriteLine("dotnet new - l ：");
                pListTemplate.StandardInput.WriteLine($"dotnet new -l");
                pListTemplate.StandardInput.WriteLine("exit");//需要执行exit后才能读取 StandardOutput
                var output = pListTemplate.StandardOutput.ReadToEnd();
                CloseProcess(pListTemplate);


                Console.WriteLine("\t" + output);
                var unInstallTemplatePackage = !output.Contains("Custom XNCF Module Template");
                var installPackageCmd = string.Empty;
                switch (typeParam.TemplatePackage.SelectedValues.First())
                {
                    case "online":
                        Console.WriteLine("online");
                        base.RecordLog(sb, "配置在线安装 XNCF 模板");
                        installPackageCmd = $"dotnet new -i Senparc.Xncf.XncfBuilder.Template";
                        break;
                    case "local":
                        Console.WriteLine("local");

                        var slnDir = Directory.GetParent(typeParam.SlnFilePath).FullName;
                        var packageFile = Directory.GetFiles(slnDir, "Senparc.Xncf.XncfBuilder.Template.*.nupkg").LastOrDefault();

                        if (packageFile.IsNullOrEmpty())
                        {
                            base.RecordLog(sb, "本地未找到文件：Senparc.Xncf.XncfBuilder.Template.*.nupkg，转为在线安装");
                            goto case "online";
                        }
                        base.RecordLog(sb, $"配置本地安装 XNCF 模板：{packageFile}");
                        installPackageCmd = $"dotnet new -i {packageFile}";
                        break;
                    case "no":
                        Console.WriteLine("no");

                        base.RecordLog(sb, $"未要求安装 XNCF 模板");
                        if (unInstallTemplatePackage)
                        {
                            base.RecordLog(sb, $"未发现已安装模板，转到在线安装");
                            goto case "online";
                        }
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }

                if (!installPackageCmd.IsNullOrEmpty())
                {
                    var pInstallTemplate = GetNewProcess();
                    base.RecordLog(sb, $"执行 XNCF 模板安装命令：" + installPackageCmd);
                    pInstallTemplate.StandardInput.WriteLine(installPackageCmd);
                    pInstallTemplate.StandardInput.WriteLine("exit");//需要执行exit后才能读取 StandardOutput
                    CloseProcess(pInstallTemplate);
                }
                Console.WriteLine($"[{SystemTime.Now}] finish install template");

                #endregion

                var pCreate = GetNewProcess();

                Console.WriteLine($"[{SystemTime.Now}] start to create xncf");

                foreach (string item in commandTexts)
                {
                    Console.WriteLine("run:" + item);
                    pCreate.StandardInput.WriteLine(item);
                    Console.WriteLine($"[{SystemTime.Now}] run：{item}");
                }
                pCreate.StandardInput.WriteLine("exit");
                Console.WriteLine($"[{SystemTime.Now}] run：exit");

                strOutput = pCreate.StandardOutput.ReadToEnd();
                Console.WriteLine($"[{SystemTime.Now}] ReadToEnd()");

                base.RecordLog(sb, strOutput);

                //strOutput = Encoding.UTF8.GetString(Encoding.Default.GetBytes(strOutput));
                CloseProcess(pCreate);
            }
            catch (Exception e)
            {
                strOutput = e.Message;
            }


            return strOutput;
        }

        /// <summary>
        /// 项目名称
        /// </summary>
        /// <param name="typeParam"></param>
        /// <returns></returns>
        private static string GetProjectName(Parameters typeParam)
        {
            return $"{typeParam.OrgName}.Xncf.{typeParam.XncfName}";
        }

        public override FunctionResult Run(IFunctionParameter param)
        {
            return FunctionHelper.RunFunction<Parameters>(param, (typeParam, sb, result) =>
            {
                var outputStr = BuildSample(typeParam, ref sb); //执行模板生成
                var projectFilePath = $"{typeParam.OrgName}.Xncf.{typeParam.XncfName}\\{typeParam.OrgName}.Xncf.{typeParam.XncfName}.csproj";

                #region 生成 .sln

                var relativeFilePath = $"{typeParam.OrgName}.Xncf.{typeParam.XncfName}.csproj";

                //生成 .sln
                if (!typeParam.SlnFilePath.ToUpper().EndsWith(".SLN"))
                {
                    result.Success = false;
                    result.Message = $"解决方案文件未找到，请手动引用项目 {projectFilePath}";
                    sb.AppendLine($"操作未全部完成：{result.Message}");
                }
                else if (File.Exists(typeParam.SlnFilePath))
                {
                    //是否创建新的 .sln 文件
                    var useNewSlnFile = typeParam.NewSlnFile.SelectedValues.Contains("new");

                    var slnFileName = Path.GetFileName(typeParam.SlnFilePath);
                    string newSlnFileName = slnFileName;
                    string newSlnFilePath = typeParam.SlnFilePath;
                    if (useNewSlnFile)
                    {
                        newSlnFileName = $"{slnFileName}-new-{SystemTime.Now.DateTime.ToString("yyyyMMdd_HHmmss")}.sln";
                        newSlnFilePath = Path.Combine(Path.GetDirectoryName(typeParam.SlnFilePath), newSlnFileName);
                        File.Copy(typeParam.SlnFilePath, newSlnFilePath);
                        sb.AppendLine($"完成 {newSlnFilePath} 文件创建");
                    }
                    else
                    {
                        var backupSln = typeParam.NewSlnFile.SelectedValues.Contains("backup");
                        var backupFileName = $"{slnFileName}-backup-{SystemTime.Now.DateTime.ToString("yyyyMMdd_HHmmss")}.sln";
                        var backupFilePath = Path.Combine(Path.GetDirectoryName(typeParam.SlnFilePath), backupFileName);
                        File.Copy(typeParam.SlnFilePath, backupFilePath);
                        sb.AppendLine($"完成 {newSlnFilePath} 文件备份");
                    }


                    result.Message = $"项目生成成功！请打开  {newSlnFilePath} 解决方案文件查看已附加的项目！<br />注意：如果您操作的项目此刻正在运行中，可能会引发重新编译，导致您看到的这个页面可能已失效。";
                }
                else
                {
                    result.Success = false;
                    result.Message = $"解决方案文件未找到，请手动引用项目 {relativeFilePath}";
                    sb.AppendLine($"操作未全部完成：{result.Message}");
                }


                Console.WriteLine(outputStr);
                Console.WriteLine(sb.ToString());

                #endregion

                #region 将当前设置保存到数据库

                var configService = base.ServiceProvider.GetService<ServiceBase<Config>>();
                var config = configService.GetObject(z => true);
                if (config == null)
                {
                    config = new Config(typeParam.SlnFilePath, typeParam.OrgName, typeParam.XncfName, typeParam.Version, typeParam.MenuName, typeParam.Icon);
                }
                else
                {
                    configService.Mapper.Map(typeParam, config);
                }
                configService.SaveObject(config);

                //result.Message += "\r\n\r\n" + outputStr;

                #endregion
            });
        }
    }
}


<|start_filename|>src/Extensions/Senparc.Xncf.XncfBuilder/Senparc.Xncf.XncfBuilder/Register.Database.cs<|end_filename|>
﻿using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
using Senparc.Ncf.Core.Models;
using Senparc.Ncf.Database;
using Senparc.Ncf.Database.MultipleMigrationDbContext;
using Senparc.Ncf.XncfBase;
using Senparc.Ncf.XncfBase.Database;
using Senparc.Xncf.XncfBuilder.Functions;
using Senparc.Xncf.XncfBuilder.Models.MultipleDatabase;
using System;

namespace Senparc.Xncf.XncfBuilder
{
    public partial class Register : IXncfDatabase
    {
        public const string DATABASE_PREFIX = "XncfBuilder";
        public string DatabaseUniquePrefix => DATABASE_PREFIX;

        public Type TryGetXncfDatabaseDbContextType => MultipleDatabasePool.Instance.GetXncfDbContextType(this.GetType());

        public void AddXncfDatabaseModule(IServiceCollection services)
        {
            services.AddScoped<Config>();
            services.AddScoped<BuildXncf.Parameters>();

            //services.AddScoped<XncfBuilderEntities_SqlServer>();//注意：此处不能直接这样自动配置数据库实体，基类中已经统一配置 implementationFactory

            //AutoMap映射
            base.AddAutoMapMapping(profile =>
            {
                profile.CreateMap<Config, ConfigDto>();
                profile.CreateMap<ConfigDto, Config>();
                profile.CreateMap<BuildXncf.Parameters, Config>();
                profile.CreateMap<Config, BuildXncf.Parameters>();
            });
        }

        public void OnModelCreating(ModelBuilder modelBuilder)
        {
            //实现 [XncfAutoConfigurationMapping] 特性之后，可以自动执行，无需手动添加
            //modelBuilder.ApplyConfiguration(new DbConfig_WeixinUserConfigurationMapping());
        }
    }
}


<|start_filename|>src/Extensions/Senparc.Xncf.DatabaseToolkit/Senparc.Xncf.DatabaseToolkit/Functions/CheckUpdate.cs<|end_filename|>
﻿using Microsoft.EntityFrameworkCore;
using Senparc.Ncf.Core.Models;
using Senparc.Ncf.XncfBase;
using Senparc.Ncf.XncfBase.Functions;
using System;
using System.Linq;

namespace Senparc.Xncf.DatabaseToolkit.Functions
{
    public class CheckUpdate : FunctionBase
    {
        public class CheckUpdate_Parameters : IFunctionParameter
        {

        }

        //注意：Name 必须在单个 Xncf 模块中唯一！
        public override string Name => "检测数据库是否需要更新";

        public override string Description => "使用 Entity Framework Core 的 Code First 模式中的 Migration 功能，检测系统当前数据库是否有未被更新的版本，如果有，请使用“Merge EF Core”方法进行更新。";

        public override Type FunctionParameterType => typeof(CheckUpdate_Parameters);

        public CheckUpdate(IServiceProvider serviceProvider) : base(serviceProvider)
        {
        }

        /// <summary>
        /// 运行
        /// </summary>
        /// <param name="param"></param>
        /// <returns></returns>
        public override FunctionResult Run(IFunctionParameter param)
        {
            return FunctionHelper.RunFunction<CheckUpdate_Parameters>(param, (typeParam, sb, result) =>
            {
                RecordLog(sb, "开始获取 ISenparcEntities 对象");
                var senparcEntities = ServiceProvider.GetService(typeof(ISenparcEntitiesDbContext)) as SenparcEntitiesBase;
                RecordLog(sb, "获取 ISenparcEntities 对象成功");
                RecordLog(sb, "开始检测未安装版本");
                var pendingMigrations = senparcEntities.Database.GetPendingMigrations();

                var oldMigrations = senparcEntities.Database.GetAppliedMigrations();
                foreach (var item in oldMigrations)
                {
                    result.Message = "检测到当前已安装更新：" + item;
                }

                if (pendingMigrations.Count() == 0)
                {
                    result.Message = "未检测到官方 NCF 框架更新。";
                }
                else
                {
                    foreach (var item in pendingMigrations)
                    {
                        RecordLog(sb, "检测到未安装的新版本：" + item);
                    }
                    result.Message = $"检测到 {pendingMigrations.Count()} 个新版本，请使用“Merge EF Core”方法进行更新！";
                }
                RecordLog(sb, result.Message);
            });
        }
    }
}


<|start_filename|>src/Extensions/Senparc.Xncf.DatabaseToolkit/Senparc.Xncf.DatabaseToolkit/Register.cs<|end_filename|>
﻿using Senparc.Ncf.Core.Enums;
using Senparc.Ncf.Core.Models;
using Senparc.Ncf.XncfBase;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;

namespace Senparc.Xncf.DatabaseToolkit
{
    [XncfRegister]
    public partial class Register : XncfRegisterBase, IXncfRegister
    {
        public Register()
        { }

        #region IXncfRegister 接口

        public override string Name => "Senparc.Xncf.DatabaseToolkit";

        public override string Uid => "3019CCBE-0739-43D5-9DED-027A0B26745E";//必须确保全局唯一，生成后必须固定
        public override string Version => "0.6.0";//必须填写版本号

        public override string MenuName => "数据库工具包";
        public override string Icon => "fa fa-database";
        public override string Description => "为方便数据库操作提供的工具包。请完全了解本工具各项功能特点后再使用，所有数据库操作都有损坏数据的可能，修改数据库前务必注意数据备份！";

        /// <summary>
        /// 注册当前模块需要支持的功能模块
        /// </summary>
        public override IList<Type> Functions => new[] {
            typeof(Functions.SetConfig),
            typeof(Functions.BackupDatabase),
            typeof(Functions.ShowDatabaseConfiguration),
            typeof(Functions.ExportSQL),
            typeof(Functions.CheckUpdate),
            typeof(Functions.UpdateDatabase),
        };

        public override async Task InstallOrUpdateAsync(IServiceProvider serviceProvider, InstallOrUpdate installOrUpdate)
        {
            //安装或升级版本时更新数据库
            await base.MigrateDatabaseAsync(serviceProvider);
        }

        public override async Task UninstallAsync(IServiceProvider serviceProvider, Func<Task> unsinstallFunc)
        {
            #region 删除数据库（演示）

            var mySenparcEntitiesType = this.TryGetXncfDatabaseDbContextType;
            DatabaseToolkitEntities mySenparcEntities = serviceProvider.GetService(mySenparcEntitiesType) as DatabaseToolkitEntities;

            //指定需要删除的数据实体

            //注意：这里作为演示，在卸载模块的时候删除了所有本模块创建的表，实际操作过程中，请谨慎操作，并且按照删除顺序对实体进行排序！
            var dropTableKeys = EntitySetKeys.GetEntitySetInfo(this.TryGetXncfDatabaseDbContextType).Keys.ToArray();
            await base.DropTablesAsync(serviceProvider, mySenparcEntities, dropTableKeys);

            #endregion

            await unsinstallFunc().ConfigureAwait(false);
        }

        #endregion
    }
}

<|start_filename|>src/Extensions/Senparc.Xncf.DatabaseToolkit/Senparc.Xncf.DatabaseToolkit/Functions/BackupDatabase.cs<|end_filename|>
﻿using Microsoft.EntityFrameworkCore;
using Senparc.Ncf.Core.Models;
using Senparc.Ncf.Service;
using Senparc.Ncf.XncfBase;
using Senparc.Ncf.XncfBase.Functions;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Senparc.CO2NET.Extensions;

namespace Senparc.Xncf.DatabaseToolkit.Functions
{
    public class BackupDatabase : FunctionBase
    {
        public enum BackupDatabaseOptions
        {
            如果文件存在则不覆盖 = 0,
            校验备份成功 = 1
        }

        public class BackupDatabase_Parameters : FunctionParameterLoadDataBase
        {
            [Required]
            [MaxLength(300)]
            [Description("路径||本地物理路径，如：E:\\Senparc\\Database-Backup\\NCF.bak，必须包含文件名。请确保此路径有网站程序访问权限！")]
            public string Path { get; set; }

            [Description("选项||备份数据库选项")]
            public SelectionList Options { get; set; } = new SelectionList(SelectionType.CheckBoxList,
                new[] {
                    new SelectionItem($"{(int)BackupDatabaseOptions.如果文件存在则不覆盖}","如果文件存在，则不覆盖","文件已存在的情况下，不会执行备份操作"),
                    new SelectionItem($"{(int)BackupDatabaseOptions.校验备份成功}","备份完成后校验.bak文件是否更新成功","此操作只能检查文件是否被更新，无法检测文件内部内容",true),
                });

            public override async Task LoadData(IServiceProvider serviceProvider)
            {
                var configService = serviceProvider.GetService<ServiceBase<DbConfig>>();
                var config = await configService.GetObjectAsync(z => true);
                if (config != null)
                {
                    Path = config.BackupPath;
                }
            }
        }

        //注意：Name 必须在单个 Xncf 模块中唯一！
        public override string Name => "备份数据库";

        public override string Description => "将当前使用的数据库备份到指定路径。友情提示：建议确保该路径不具备公开访问权限！";

        public override Type FunctionParameterType => typeof(BackupDatabase_Parameters);

        public BackupDatabase(IServiceProvider serviceProvider) : base(serviceProvider)
        {
        }

        /// <summary>
        /// 运行
        /// </summary>
        /// <param name="param"></param>
        /// <returns></returns>
        public override FunctionResult Run(IFunctionParameter param)
        {
            return FunctionHelper.RunFunction<BackupDatabase_Parameters>(param, (typeParam, sb, result) =>
            {
                try
                {
                    var path = typeParam.Path;

                    if (File.Exists(path))
                    {
                        var copyPath = path + ".last.bak";
                        if (typeParam.Options.SelectedValues.Contains($"{(int)BackupDatabaseOptions.如果文件存在则不覆盖}"))
                        {
                            RecordLog(sb, "检测到同名文件，停止覆盖。地址：" + copyPath);
                            result.Message = "检测到同名文件，停止覆盖！";
                            return;
                        }

                        RecordLog(sb, "检测到同名文件，已经移动到（并覆盖）：" + copyPath);
                        File.Move(path, copyPath);
                    }

                    RecordLog(sb, "开始获取 ISenparcEntities 对象");
                    var senparcEntities = ServiceProvider.GetService(typeof(ISenparcEntitiesDbContext)) as SenparcEntitiesBase;
                    RecordLog(sb, "获取 ISenparcEntities 对象成功");

                    var currentDatabaseConfiguration = DatabaseConfigurationFactory.Instance.Current;
                    var backupSql = currentDatabaseConfiguration.GetBackupDatabaseSql(senparcEntities.Database.GetDbConnection(), path);
                    if (backupSql.IsNullOrEmpty())
                    {
                        RecordLog(sb, $"{currentDatabaseConfiguration.GetType().Name} 内部已处理，无需单独执行 SQL");
                    }
                    else
                    {
                        RecordLog(sb, "准备执行 SQL：" + backupSql);
                        int affectRows = senparcEntities.Database.ExecuteSqlRaw(backupSql);
                        RecordLog(sb, "执行完毕，备份结束。affectRows：" + affectRows);
                    }

                    if (typeParam.Options.SelectedValues.Contains($"{(int)BackupDatabaseOptions.校验备份成功}"))
                    {
                        RecordLog(sb, "检查备份文件：" + path);
                        if (File.Exists(path))
                        {
                            var modifyTime = File.GetLastWriteTimeUtc(path);
                            if ((SystemTime.UtcDateTime - modifyTime).TotalSeconds < 5/*5秒钟内创建的*/)
                            {
                                RecordLog(sb, "检查通过，备份成功！最后修改时间：" + modifyTime.ToString());
                                result.Message = "检查通过，备份成功！";
                            }
                            else
                            {
                                result.Message = $"文件存在，但修改时间不符，可能未备份成功，请检查文件！文件最后修改时间：{modifyTime.ToString()}";
                                RecordLog(sb, result.Message);
                            }
                        }
                        else
                        {
                            result.Message = "备份文件未生成，备份失败！";
                            RecordLog(sb, result.Message);
                        }
                    }
                    else
                    {
                        result.Message = "备份完成！";
                    }
                }
                catch (Exception ex)
                {
                    result.Message += ex.Message;
                    throw;
                }
            });
        }
    }
}


<|start_filename|>src/Basic/Senparc.Ncf.XncfBase/Interfaces/Functions/IFunctionParameter.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.Text;

namespace Senparc.Ncf.XncfBase
{
    /// <summary>
    /// Function 的参数配置
    /// </summary>
    public interface IFunctionParameter
    {
    }
}


<|start_filename|>src/Basic/Senparc.Ncf.XncfBase/Interfaces/Functions/IXncfFunction.cs<|end_filename|>
﻿
using Senparc.Ncf.XncfBase.Functions;
using System;
using System.Collections.Generic;
using System.Text;

namespace Senparc.Ncf.XncfBase
{
    /// <summary>
    /// 扩展方法接口
    /// </summary>
    public interface IXncfFunction
    {
        string Name { get; }
        string Description { get; }

        Type FunctionParameterType { get; }

        /// <summary>
        /// 生成参数定义对象的实例
        /// </summary>
        /// <returns></returns>
        IFunctionParameter GenerateParameterInstance();

        /// <summary>
        /// ServiceProvider 实例
        /// </summary>
        IServiceProvider ServiceProvider { get; set; }

        /// <summary>
        /// 执行程序
        /// </summary>
        /// <param name="param">参数</param>
        /// <returns></returns>
        FunctionResult Run(IFunctionParameter param);
    }
}


<|start_filename|>src/Basic/Senparc.Ncf.XncfBase/Functions/FunctionHelper.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;

namespace Senparc.Ncf.XncfBase.Functions
{
    /// <summary>
    /// Function 帮助类
    /// </summary>
    public class FunctionHelper
    {
        #region 同步方法

        /// <summary>
        /// 执行 Run 方法的公共方法
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="param"></param>
        /// <param name="func"></param>
        /// <returns></returns>
        public static FunctionResult RunFunction<T>(IFunctionParameter param, Func<T, StringBuilder, FunctionResult, FunctionResult> func)
            where T : IFunctionParameter
        {
            var typeParam = (T)param;
            StringBuilder sb = new StringBuilder();
            FunctionResult result = new FunctionResult()
            {
                Success = true
            };

            try
            {
                var newResult = func.Invoke(typeParam, sb, result);
                if (newResult != null)
                {
                    result = newResult;
                }
            }
            catch (Exception ex)
            {
                result.Success = false;
                result.Exception = new XncfFunctionException(ex.Message, ex);
                result.Message = "发生错误！" + result.Message;

                RecordLog(sb, "发生错误：" + ex.Message);
                RecordLog(sb, ex.StackTrace.ToString());
            }
            result.Log = sb.ToString();
            return result;
        }


        /// <summary>
        /// 执行 Run 方法的公共方法
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="param"></param>
        /// <param name="action"></param>
        /// <returns></returns>
        public static FunctionResult RunFunction<T>(IFunctionParameter param, Action<T, StringBuilder, FunctionResult> action)
            where T : IFunctionParameter
        {
            return RunFunction<T>(param, (typeParam, sb, result) =>
            {
                action(typeParam, sb, result);
                return null;
            });
        }

        /// <summary>
        /// 记录日志
        /// </summary>
        /// <param name="sb"></param>
        /// <param name="msg"></param>
        public static void RecordLog(StringBuilder sb, string msg)
        {
            sb.AppendLine($"[{SystemTime.Now.ToString()}]\t{msg}");
        }

        #endregion

        #region 异步方法


        /// <summary>
        /// 【异步方法】执行 Run 方法的公共方法
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="param"></param>
        /// <param name="func"></param>
        /// <returns></returns>
        public static async Task<FunctionResult> RunFunctionAsync<T>(IFunctionParameter param, Func<T, StringBuilder, FunctionResult, Task<FunctionResult>> func)
            where T : IFunctionParameter
        {
            var typeParam = (T)param;
            StringBuilder sb = new StringBuilder();
            FunctionResult result = new FunctionResult()
            {
                Success = true
            };

            try
            {
                var newResult = await func.Invoke(typeParam, sb, result).ConfigureAwait(false);
                if (newResult != null)
                {
                    result = newResult;
                }
            }
            catch (Exception ex)
            {
                result.Success = false;
                result.Exception = new XncfFunctionException(ex.Message, ex);
                result.Message = "发生错误！" + result.Message;

                RecordLog(sb, "发生错误：" + ex.Message);
                RecordLog(sb, ex.StackTrace.ToString());
            }
            result.Log = sb.ToString();
            return result;
        }

        /// <summary>
        /// 【异步方法】执行 Run 方法的公共方法
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="param"></param>
        /// <param name="action"></param>
        /// <returns></returns>
        public static async Task<FunctionResult> RunFunctionAsync<T>(IFunctionParameter param, Func<T, StringBuilder, FunctionResult, Task> action)
            where T : IFunctionParameter
        {
            return await RunFunctionAsync<T>(param, async (typeParam, sb, result) =>
             {
                 await action(typeParam, sb, result).ConfigureAwait(false);
                 return null;
             });
        }


        #endregion
    }
}


<|start_filename|>src/Basic/Senparc.Ncf.Core/Areas/AreaRegister.cs<|end_filename|>
﻿using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Senparc.CO2NET.Cache;
using Senparc.CO2NET.Exceptions;
using Senparc.CO2NET.Trace;
using Senparc.Ncf.Core.AssembleScan;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Senparc.Ncf.Core.Areas
{
    /// <summary>
    /// 对所有扩展 Area 进行注册
    /// </summary>
    public static class AreaRegister
    {
        /// <summary>
        /// 自动注册所有 Area
        /// </summary>
        /// <param name="builder"></param>
        /// <param name="env"></param>
        /// <param name="eachRegsiterAction">遍历到每一个 Register 额外的操作</param>
        /// <returns></returns>
        public static IMvcBuilder AddNcfAreas(this IMvcBuilder builder, Microsoft.Extensions.Hosting.IHostEnvironment/*IWebHostEnvironment*/ env, Action<IAreaRegister> eachRegsiterAction = null)
        {
            AssembleScanHelper.AddAssembleScanItem(assembly =>
            {
                try
                {
                    var areaRegisterTypes = assembly.GetTypes()
                                .Where(z => z.GetInterface(nameof(IAreaRegister)) != null)
                                .ToArray();

                    foreach (var registerType in areaRegisterTypes)
                    {
                        var register = Activator.CreateInstance(registerType, true) as IAreaRegister;
                        if (register!=null)
                        {
                            register.AuthorizeConfig(builder, env);//进行注册
                            eachRegsiterAction?.Invoke(register);//执行额外的操作
                        }
                        else
                        {
                            SenparcTrace.BaseExceptionLog(new BaseException($"{registerType.Name} 类型没有实现接口 IAreaRegister！"));
                        }
                    }
                }
                catch (Exception ex)
                {
                    SenparcTrace.SendCustomLog("AddNcfAreas() 自动扫描程序集报告（非程序异常）：" + assembly.FullName, ex.ToString());
                }
            }, false);
            return builder;
        }
    }
}