Files
OptixServe/OptixServe.Infrastructure/Utilites/DesignTimeDbContextFactory.cs
Huxley Deng 8b18de1735 BRAKING refactor project structure.
Refactor: the project is now divided into a more clear structure, with **Infrastructure** and **Application** layers added.

Refactor: configurations are split into sections for different layers.

Fix: now EF Core related operations, such as migration, should be invoked in `OptixServe.Infrastructure`, with config file and data dir passed into `dotnet ef` command. See `OptixServe.Infrastructure/Utilites/DesignTimeDbContextFactory.cs` for details.

Fix: EF migrations are ignored in gitignore on purpose in early development.
2025-07-11 14:48:50 +08:00

130 lines
4.4 KiB
C#

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.Extensions.Configuration;
using OptixServe.Infrastructure.Configuration;
using OptixServe.Infrastructure.Data;
namespace OptixServe.Infrastructure.Utilites;
public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<AppDbContext>
{
private record CommandParseResult
{
public string? ConfigPath { get; set; } = null;
public string? DataDir { get; set; } = null;
}
private static CommandParseResult ParseArgs(string[] args)
{
var result = new CommandParseResult();
bool configSet = false;
bool dataDirSet = false;
for (int i = 0; i < args.Length; i++)
{
string arg = args[i];
if (arg == "--config" || arg == "-c")
{
if (configSet)
{
throw new ArgumentException("The --config/-c option can only be specified once.");
}
if (i + 1 >= args.Length)
{
throw new ArgumentException("Missing value for --config/-c option.");
}
result.ConfigPath = args[i + 1];
configSet = true;
i++;
}
else if (arg == "--data-dir" || arg == "-d")
{
if (dataDirSet)
{
throw new ArgumentException("The --data-dir/-d option can only be specified once.");
}
if (i + 1 >= args.Length)
{
throw new ArgumentException("Missing value for --data-dir/-d option.");
}
result.DataDir = args[i + 1];
dataDirSet = true;
i++;
}
else
{
throw new ArgumentException($"Unknown argument: {arg}");
}
}
return result;
}
/// <summary>
/// Creates DbContext instance in Design-Time
///
/// <para>Custom arguments should be passed in <i>dotnet</i> command </para>
///
/// <b>Example:</b><br/>
/// <example><c>dotnet ef database update -- -c ../data/appsettings.Development.json -d ../data/`</c></example>
///
/// </summary>
/// <param name="args">
/// <list type="bullet">
/// <item>
/// --config/-c: App configuration file to load with database connection settings.
/// </item>
/// <item>
/// --data-dir/-d: App data dir to work with.
/// Currently this only affects finding SQLite database when relative path
/// is specified in database settings.
/// </item>
/// </list>
/// </param>
/// <returns></returns>
public AppDbContext CreateDbContext(string[] args)
{
var parsedArgs = ParseArgs(args);
IConfigurationRoot configuration;
if (!string.IsNullOrEmpty(parsedArgs.ConfigPath))
{
var absConfigPath = Path.GetFullPath(parsedArgs.ConfigPath);
var configDirectory = Path.GetDirectoryName(absConfigPath);
var configFileName = Path.GetFileName(absConfigPath);
// Use the provided config file path
configuration = new ConfigurationBuilder()
.SetBasePath(configDirectory ?? "")
.AddJsonFile(configFileName, optional: false)
.Build();
}
else
{
// Default search configuration path
configuration = ConfigurationHelper.CreateDefaultBuilder().Build();
}
var dbSettings = configuration.GetSection("OptixServe:Infrastructure:Database").Get<DatabaseSettings>()!;
// Resolve SQLite database if `--data-dir` is specified and `dbSettings.Host` is relative
if (parsedArgs.DataDir != null)
{
if (dbSettings.Type == DatabaseType.Sqlite && !Path.IsPathFullyQualified(dbSettings.Host!))
{
var dbRelPath = Path.Combine(parsedArgs.DataDir, dbSettings.Host!);
var dbAbsPath = Path.GetFullPath(dbRelPath);
dbSettings.Host = dbAbsPath;
}
}
var optionsBuilder = new DbContextOptionsBuilder<AppDbContext>();
DatabaseHelper.ConfigureDbContext(optionsBuilder, dbSettings);
return new AppDbContext(optionsBuilder.Options);
}
}