feat: 将数据库模型和迁移集中到 EFCore 项目

- 将 AppDataContext、实体模型、Migrations 从 Avalonia-Services 移动到 Avalonia-EFCore
- 更新 API、PC、Services 中的数据库上下文和实体引用命名空间
- 在实体上显式绑定表名、字段名和数据库注释
- 更新 InitialCreate、Designer、Snapshot,使用新的表名、字段名和注释
- 新增 AppDataContextFactory,支持 dotnet ef 设计时创建 DbContext
- 新增本地 dotnet-ef 工具清单
- 新增一键生成迁移脚本 add-migration.ps1 / .cmd / .bat
- 启动时自动检测并执行未应用迁移
- 从 appsettings.json 读取数据库配置
This commit is contained in:
2026-05-15 15:26:46 +08:00
parent 271e9714ff
commit c5f741e6a4
26 changed files with 631 additions and 30 deletions
@@ -0,0 +1,36 @@
using Avalonia_EFCore.Models;
using Microsoft.EntityFrameworkCore;
namespace Avalonia_EFCore.Database
{
/// <summary>
/// 应用数据库上下文 —— 继承自 Avalonia-EFCore 的 AppDbContext。
/// 所有业务实体在此注册 DbSet。
/// 这是 Avalonia-API 和 Avalonia-PC 共用的具体数据上下文。
/// </summary>
public class AppDataContext(DatabaseConfiguration dbConfig) : AppDbContext(dbConfig)
{
/// <summary>天气预报数据</summary>
public DbSet<WeatherForecastEntity> WeatherForecasts => Set<WeatherForecastEntity>();
/// <summary>用户数据</summary>
public DbSet<UserEntity> Users => Set<UserEntity>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<WeatherForecastEntity>(entity =>
{
entity.HasKey(e => e.Id).HasName("pk-weather-forecast");
entity.Property(e => e.Summary).HasMaxLength(200);
});
modelBuilder.Entity<UserEntity>(entity =>
{
entity.HasKey(e => e.Id).HasName("pk-user");
entity.Property(e => e.Email).HasMaxLength(200);
});
}
}
}
@@ -0,0 +1,13 @@
using Microsoft.EntityFrameworkCore.Design;
namespace Avalonia_EFCore.Database
{
public class AppDataContextFactory : IDesignTimeDbContextFactory<AppDataContext>
{
public AppDataContext CreateDbContext(string[] args)
{
DatabaseProviderRegistry.RegisterDefaults();
return new AppDataContext(DatabaseConfiguration.ForSQLite("avalonia-api.db"));
}
}
}
+6
View File
@@ -48,6 +48,12 @@ namespace Avalonia_EFCore.Database
}
optionsBuilder.EnableDetailedErrors();
optionsBuilder.EnableSensitiveDataLogging(config.EnableDetailedLog);
if (config.EnableDetailedLog)
{
optionsBuilder.LogTo(Console.WriteLine, Microsoft.Extensions.Logging.LogLevel.Information);
}
}
/// <summary>
@@ -32,6 +32,12 @@ namespace Avalonia_EFCore.Database
/// <summary>是否在启动时自动执行迁移</summary>
public bool AutoMigrate { get; set; } = true;
/// <summary>
/// 是否在迁移前删除并重建当前连接指向的数据库。
/// 仅用于切换数据库类型或本地开发重建库;生产环境默认必须保持 false。
/// </summary>
public bool RecreateDatabase { get; set; } = false;
/// <summary>是否启用详细日志(会打印 SQL 语句)</summary>
public bool EnableDetailedLog { get; set; } = false;
+46 -10
View File
@@ -5,6 +5,7 @@ using Microsoft.EntityFrameworkCore.Migrations;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
@@ -32,21 +33,37 @@ namespace Avalonia_EFCore.Database
/// </summary>
public async Task InitializeAsync(Action<TContext, IServiceProvider?>? seeder = null)
{
// 1. 测试数据库连接
var canConnect = await CanConnectAsync();
if (!canConnect)
{
throw new InvalidOperationException(
$"无法连接到数据库 [{_config.Provider}],请检查连接字符串和数据库服务状态。");
}
AppLog.Information(
"正在初始化数据库 Provider={Provider}, AppVersion={AppVersion}",
_config.Provider,
GetApplicationVersion());
// 2. 自动迁移(如果启用)
// 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}],请检查连接字符串和数据库服务状态。");
}
}
// 3. 种子数据
// 2. 种子数据
if (seeder != null)
{
seeder(_context, _serviceProvider);
@@ -77,12 +94,21 @@ namespace Avalonia_EFCore.Database
{
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(
"检测到 {Count} 个待执行的数据库迁移: {Migrations}",
"当前已应用 {AppliedCount} 个迁移,检测到 {PendingCount} 个待执行迁移: {Migrations}",
appliedMigrations.Count,
pendingMigrations.Count(),
string.Join(", ", pendingMigrations));
@@ -102,6 +128,16 @@ namespace Avalonia_EFCore.Database
}
}
private static string GetApplicationVersion()
{
var assembly = Assembly.GetEntryAssembly() ?? typeof(TContext).Assembly;
return assembly
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
?.InformationalVersion
?? assembly.GetName().Version?.ToString()
?? "unknown";
}
/// <summary>
/// 获取数据库当前版本信息。
/// </summary>