FileShare/FileShare-EFCore/Database/DatabaseManager.cs
luoqian 6acc92ca27 fix: 修复空 catch 吞异常问题,端口号抽离到配置,统一日志为 Serilog
- 所有空 catch 块补全日志记录,统一使用 Serilog/AppLog
- 按场景分级:Error(意外失败)、Warning(次要问题)、Information(预期内)
- 端口 HttpPort/HttpsPort 抽离到 appsettings.json Server 配置节
- QrCodeService 通过 IConfiguration 读取端口,消除硬编码
- 前端通过 Vite proxy 转发 /api,http.ts 统一使用 origin 地址
- 移除所有 Debug.WriteLine 和 Serilog.Log.Debug 日志

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 15:17:59 +08:00

226 lines
8.2 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using FileShare_Common.Infrastructure;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
namespace FileShare_EFCore.Database
{
/// <summary>
/// 数据库管理器 —— 负责连接测试、自动迁移、种子数据、版本检查。
/// 在应用启动时调用,确保数据库结构与应用代码同步。
/// </summary>
public class DatabaseManager<TContext> where TContext : AppDbContext
{
/// <summary>
/// 数据库上下文实例。
/// </summary>
private readonly TContext _context;
/// <summary>
/// 数据库配置。
/// </summary>
private readonly DatabaseConfiguration _config;
/// <summary>
/// DI 服务提供程序(可选,用于种子数据中解析服务)。
/// </summary>
private readonly IServiceProvider? _serviceProvider;
/// <summary>
/// 初始化数据库管理器。
/// </summary>
/// <param name="context">数据库上下文。</param>
/// <param name="config">数据库配置。</param>
/// <param name="serviceProvider">可选的 DI 容器。</param>
public DatabaseManager(TContext context, DatabaseConfiguration config, IServiceProvider? serviceProvider = null)
{
_context = context;
_config = config;
_serviceProvider = serviceProvider;
}
/// <summary>
/// 初始化数据库:测试连接 → 自动迁移 → 种子数据。
/// </summary>
public async Task InitializeAsync(Action<TContext, IServiceProvider?>? seeder = null)
{
AppLog.Information(
"正在初始化数据库 Provider={Provider}, AppVersion={AppVersion}",
_config.Provider,
GetApplicationVersion());
// 1. 自动迁移如果启用。MigrateAsync 会按迁移历史顺序执行全部待处理迁移,
// 支持用户从较旧软件版本直接升级到当前版本。
if (_config.AutoMigrate)
{
if (_config.RecreateDatabase)
{
AppLog.Warning(
"RecreateDatabase=true将删除并重建当前连接指向的数据库。Provider={Provider}",
_config.Provider);
await _context.Database.EnsureDeletedAsync();
}
await MigrateAsync();
}
else
{
var canConnect = await CanConnectAsync();
if (!canConnect)
{
throw new InvalidOperationException(
$"无法连接到数据库 [{_config.Provider}],请检查连接字符串和数据库服务状态。");
}
}
// 2. 种子数据
if (seeder != null)
{
seeder(_context, _serviceProvider);
await _context.SaveChangesAsync();
}
}
/// <summary>
/// 测试数据库连接是否正常。
/// </summary>
public async Task<bool> CanConnectAsync()
{
try
{
return await _context.Database.CanConnectAsync();
}
catch (Exception ex)
{
AppLog.Warning("数据库连接测试失败 Provider={Provider} Error={Error}", _config.Provider, ex.Message);
return false;
}
}
/// <summary>
/// 执行待处理的迁移。
/// 使用 EF Core 原生迁移机制,自动检测并应用 Schema 变更。
/// </summary>
public async Task MigrateAsync()
{
try
{
var appliedMigrations = (await _context.Database.GetAppliedMigrationsAsync()).ToList();
var pendingMigrations = await _context.Database.GetPendingMigrationsAsync();
if (pendingMigrations.Any())
{
if (appliedMigrations.Count == 0)
{
AppLog.Information(
"未检测到已应用迁移,将按当前 Provider={Provider} 从 0 构建完整表结构",
_config.Provider);
}
AppLog.Information(
"当前已应用 {AppliedCount} 个迁移,检测到 {PendingCount} 个待执行迁移: {Migrations}",
appliedMigrations.Count,
pendingMigrations.Count(),
string.Join(", ", pendingMigrations));
await _context.Database.MigrateAsync();
AppLog.Information("数据库迁移完成({Count} 个迁移已应用)", pendingMigrations.Count());
}
else
{
AppLog.Information("数据库已是最新版本,无需迁移");
}
}
catch (Exception ex)
{
AppLog.Error(ex, "数据库迁移失败");
throw;
}
}
/// <summary>
/// 获取当前应用程序的版本号,优先读取 AssemblyInformationalVersion回退到 AssemblyVersion。
/// </summary>
/// <returns>应用程序版本字符串。</returns>
private static string GetApplicationVersion()
{
var assembly = Assembly.GetEntryAssembly() ?? typeof(TContext).Assembly;
return assembly
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
?.InformationalVersion
?? assembly.GetName().Version?.ToString()
?? "unknown";
}
/// <summary>
/// 获取数据库当前版本信息。
/// </summary>
public async Task<DatabaseVersionInfo> GetVersionInfoAsync()
{
var appliedMigrations = await _context.Database.GetAppliedMigrationsAsync();
var pendingMigrations = await _context.Database.GetPendingMigrationsAsync();
return new DatabaseVersionInfo
{
Provider = _config.Provider.ToString(),
AppliedMigrations = appliedMigrations.ToList(),
PendingMigrations = pendingMigrations.ToList(),
IsLatest = !pendingMigrations.Any(),
CanConnect = await CanConnectAsync(),
};
}
/// <summary>
/// 生成从指定迁移到最新版本的 SQL 脚本(用于生产环境审计)。
/// </summary>
public string GenerateMigrationScript(string? fromMigration = null)
{
var migrator = _context.GetService<IMigrator>();
return fromMigration is null
? migrator.GenerateScript()
: migrator.GenerateScript(fromMigration);
}
/// <summary>
/// 确保数据库已创建(不执行迁移,适用于简单场景)。
/// </summary>
public bool EnsureCreated()
{
return _context.Database.EnsureCreated();
}
}
/// <summary>
/// 数据库版本信息 DTO。
/// </summary>
public class DatabaseVersionInfo
{
/// <summary>
/// 获取或设置数据库提供程序名称。
/// </summary>
public string Provider { get; set; } = string.Empty;
/// <summary>
/// 获取或设置已应用的迁移列表。
/// </summary>
public List<string> AppliedMigrations { get; set; } = new();
/// <summary>
/// 获取或设置待应用的迁移列表。
/// </summary>
public List<string> PendingMigrations { get; set; } = new();
/// <summary>
/// 获取或设置是否为最新版本。
/// </summary>
public bool IsLatest { get; set; }
/// <summary>
/// 获取或设置数据库是否可连接。
/// </summary>
public bool CanConnect { get; set; }
}
}