2026-05-11 14:35:34 +08:00
|
|
|
|
using Avalonia_Common.Infrastructure;
|
|
|
|
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
|
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
|
|
|
|
using Microsoft.EntityFrameworkCore.Migrations;
|
|
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.Linq;
|
2026-05-15 15:26:46 +08:00
|
|
|
|
using System.Reflection;
|
2026-05-11 14:35:34 +08:00
|
|
|
|
using System.Threading;
|
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
|
|
|
|
|
|
namespace Avalonia_EFCore.Database
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 数据库管理器 —— 负责连接测试、自动迁移、种子数据、版本检查。
|
|
|
|
|
|
/// 在应用启动时调用,确保数据库结构与应用代码同步。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class DatabaseManager<TContext> where TContext : AppDbContext
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly TContext _context;
|
|
|
|
|
|
private readonly DatabaseConfiguration _config;
|
|
|
|
|
|
private readonly IServiceProvider? _serviceProvider;
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
{
|
2026-05-15 15:26:46 +08:00
|
|
|
|
AppLog.Information(
|
|
|
|
|
|
"正在初始化数据库 Provider={Provider}, AppVersion={AppVersion}",
|
|
|
|
|
|
_config.Provider,
|
|
|
|
|
|
GetApplicationVersion());
|
2026-05-11 14:35:34 +08:00
|
|
|
|
|
2026-05-15 15:26:46 +08:00
|
|
|
|
// 1. 自动迁移(如果启用)。MigrateAsync 会按迁移历史顺序执行全部待处理迁移,
|
|
|
|
|
|
// 支持用户从较旧软件版本直接升级到当前版本。
|
2026-05-11 14:35:34 +08:00
|
|
|
|
if (_config.AutoMigrate)
|
|
|
|
|
|
{
|
2026-05-15 15:26:46 +08:00
|
|
|
|
if (_config.RecreateDatabase)
|
|
|
|
|
|
{
|
|
|
|
|
|
AppLog.Warning(
|
|
|
|
|
|
"RecreateDatabase=true,将删除并重建当前连接指向的数据库。Provider={Provider}",
|
|
|
|
|
|
_config.Provider);
|
|
|
|
|
|
|
|
|
|
|
|
await _context.Database.EnsureDeletedAsync();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-11 14:35:34 +08:00
|
|
|
|
await MigrateAsync();
|
|
|
|
|
|
}
|
2026-05-15 15:26:46 +08:00
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
var canConnect = await CanConnectAsync();
|
|
|
|
|
|
if (!canConnect)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new InvalidOperationException(
|
|
|
|
|
|
$"无法连接到数据库 [{_config.Provider}],请检查连接字符串和数据库服务状态。");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-05-11 14:35:34 +08:00
|
|
|
|
|
2026-05-15 15:26:46 +08:00
|
|
|
|
// 2. 种子数据
|
2026-05-11 14:35:34 +08:00
|
|
|
|
if (seeder != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
seeder(_context, _serviceProvider);
|
|
|
|
|
|
await _context.SaveChangesAsync();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 测试数据库连接是否正常。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public async Task<bool> CanConnectAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
return await _context.Database.CanConnectAsync();
|
|
|
|
|
|
}
|
|
|
|
|
|
catch
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 执行待处理的迁移。
|
|
|
|
|
|
/// 使用 EF Core 原生迁移机制,自动检测并应用 Schema 变更。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public async Task MigrateAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2026-05-15 15:26:46 +08:00
|
|
|
|
var appliedMigrations = (await _context.Database.GetAppliedMigrationsAsync()).ToList();
|
2026-05-11 14:35:34 +08:00
|
|
|
|
var pendingMigrations = await _context.Database.GetPendingMigrationsAsync();
|
|
|
|
|
|
|
|
|
|
|
|
if (pendingMigrations.Any())
|
|
|
|
|
|
{
|
2026-05-15 15:26:46 +08:00
|
|
|
|
if (appliedMigrations.Count == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
AppLog.Information(
|
|
|
|
|
|
"未检测到已应用迁移,将按当前 Provider={Provider} 从 0 构建完整表结构",
|
|
|
|
|
|
_config.Provider);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-11 14:35:34 +08:00
|
|
|
|
AppLog.Information(
|
2026-05-15 15:26:46 +08:00
|
|
|
|
"当前已应用 {AppliedCount} 个迁移,检测到 {PendingCount} 个待执行迁移: {Migrations}",
|
|
|
|
|
|
appliedMigrations.Count,
|
2026-05-11 14:35:34 +08:00
|
|
|
|
pendingMigrations.Count(),
|
|
|
|
|
|
string.Join(", ", pendingMigrations));
|
|
|
|
|
|
|
|
|
|
|
|
await _context.Database.MigrateAsync();
|
|
|
|
|
|
|
|
|
|
|
|
AppLog.Information("数据库迁移完成({Count} 个迁移已应用)", pendingMigrations.Count());
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
AppLog.Information("数据库已是最新版本,无需迁移");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
AppLog.Error(ex, "数据库迁移失败");
|
|
|
|
|
|
throw;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-15 15:26:46 +08:00
|
|
|
|
private static string GetApplicationVersion()
|
|
|
|
|
|
{
|
|
|
|
|
|
var assembly = Assembly.GetEntryAssembly() ?? typeof(TContext).Assembly;
|
|
|
|
|
|
return assembly
|
|
|
|
|
|
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
|
|
|
|
|
|
?.InformationalVersion
|
|
|
|
|
|
?? assembly.GetName().Version?.ToString()
|
|
|
|
|
|
?? "unknown";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-11 14:35:34 +08:00
|
|
|
|
/// <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
|
|
|
|
|
|
{
|
|
|
|
|
|
public string Provider { get; set; } = string.Empty;
|
|
|
|
|
|
public List<string> AppliedMigrations { get; set; } = new();
|
|
|
|
|
|
public List<string> PendingMigrations { get; set; } = new();
|
|
|
|
|
|
public bool IsLatest { get; set; }
|
|
|
|
|
|
public bool CanConnect { get; set; }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|