luoqian c5f741e6a4 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 读取数据库配置
2026-05-15 15:26:46 +08:00

100 lines
3.6 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 Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Avalonia_EFCore.Database
{
/// <summary>
/// 应用数据库上下文基类 —— 自动根据 DatabaseConfiguration 选择数据库提供程序。
/// 所有业务 DbContext 继承此类即可获得多数据库支持。
/// </summary>
public abstract class AppDbContext(DatabaseConfiguration dbConfig) : DbContext
{
private readonly DatabaseConfiguration _dbConfig = dbConfig;
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (optionsBuilder.IsConfigured) return;
ConfigureProvider(optionsBuilder, _dbConfig);
if (_dbConfig.EnableDetailedLog)
{
optionsBuilder.LogTo(Console.WriteLine, Microsoft.Extensions.Logging.LogLevel.Information);
}
// 启用详细的 EF Core 错误信息
optionsBuilder.EnableDetailedErrors();
optionsBuilder.EnableSensitiveDataLogging(_dbConfig.EnableDetailedLog);
}
/// <summary>
/// 根据配置选择数据库提供程序。
/// 使用注册模式,由宿主项目注册具体的提供程序实现。
/// </summary>
public static void ConfigureProvider(DbContextOptionsBuilder optionsBuilder, DatabaseConfiguration config)
{
if (DatabaseProviderRegistry.TryGet(config.Provider, out var configurator))
{
configurator(optionsBuilder, config.ConnectionString, config.Timeout);
}
else
{
throw new NotSupportedException(
$"数据库提供程序 {config.Provider} 未注册。" +
$"请在宿主项目中安装对应的 EF Core NuGet 包并调用 DatabaseProviderRegistry.Register()。");
}
optionsBuilder.EnableDetailedErrors();
optionsBuilder.EnableSensitiveDataLogging(config.EnableDetailedLog);
if (config.EnableDetailedLog)
{
optionsBuilder.LogTo(Console.WriteLine, Microsoft.Extensions.Logging.LogLevel.Information);
}
}
/// <summary>
/// 保存时自动设置时间戳。
/// </summary>
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
SetTimestamps();
return base.SaveChanges(acceptAllChangesOnSuccess);
}
public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
{
SetTimestamps();
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
private void SetTimestamps()
{
var entries = ChangeTracker.Entries()
.Where(e => e.State == EntityState.Added || e.State == EntityState.Modified);
foreach (var entry in entries)
{
var entity = entry.Entity;
// 使用反射设置 CreatedAt / UpdatedAt如果存在
var createdAtProp = entity.GetType().GetProperty("CreatedAt");
var updatedAtProp = entity.GetType().GetProperty("UpdatedAt");
if (entry.State == EntityState.Added && createdAtProp != null)
{
createdAtProp.SetValue(entity, DateTime.UtcNow);
}
if (updatedAtProp != null)
{
updatedAtProp.SetValue(entity, DateTime.UtcNow);
}
}
}
}
}