Repository navigation
Expand file tree
/
Copy pathDesignTimeDbContextFactory.cs
More file actions
87 lines (76 loc) · 3.51 KB
/
Copy pathDesignTimeDbContextFactory.cs
File metadata and controls
87 lines (76 loc) · 3.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace PatchNotes.Data;
/// <summary>
/// SQLite context for local development.
/// Migrations: dotnet ef migrations add MigrationName --context SqliteContext --output-dir Migrations/Sqlite --startup-project ../PatchNotes.Api
/// </summary>
public class SqliteContext : PatchNotesDbContext
{
public SqliteContext(DbContextOptions<SqliteContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// SQLite doesn't have a native DateTimeOffset type, so convert to/from DateTime (UTC).
// NOTE: This normalizes all offsets to UTC on write — the original offset is not preserved.
// All DateTimeOffset values in this application use UTC (DateTimeOffset.UtcNow), so this
// is safe. Non-UTC offsets would be silently converted to UTC and lost on read-back.
// DateTime.SpecifyKind ensures the stored value is unambiguously treated as UTC on read,
// preventing an InvalidOperationException if EF Core returns Kind=Unspecified or Kind=Local.
var dtoConverter = new ValueConverter<DateTimeOffset, DateTime>(
v => v.UtcDateTime,
v => new DateTimeOffset(DateTime.SpecifyKind(v, DateTimeKind.Utc), TimeSpan.Zero));
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
foreach (var property in entityType.GetProperties())
{
if (property.ClrType == typeof(DateTimeOffset) || property.ClrType == typeof(DateTimeOffset?))
{
property.SetValueConverter(dtoConverter);
}
}
}
}
}
/// <summary>
/// SQL Server context for production.
/// Migrations: dotnet ef migrations add MigrationName --context SqlServerContext --output-dir Migrations/SqlServer --startup-project ../PatchNotes.Api
/// </summary>
public class SqlServerContext : PatchNotesDbContext
{
public SqlServerContext(DbContextOptions<SqlServerContext> options) : base(options) { }
}
/// <summary>
/// Design-time factory for SQLite migrations.
/// </summary>
public class SqliteContextFactory : IDesignTimeDbContextFactory<SqliteContext>
{
public SqliteContext CreateDbContext(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder<SqliteContext>();
optionsBuilder.UseSqlite("Data Source=patchnotes.db");
return new SqliteContext(optionsBuilder.Options);
}
}
/// <summary>
/// Design-time factory for SQL Server migrations.
/// Set ConnectionStrings__PatchNotes environment variable.
/// </summary>
public class SqlServerContextFactory : IDesignTimeDbContextFactory<SqlServerContext>
{
public SqlServerContext CreateDbContext(string[] args)
{
var connectionString = Environment.GetEnvironmentVariable("ConnectionStrings__PatchNotes") ??
throw new InvalidOperationException(
"Connection string not found. " +
"Set ConnectionStrings__PatchNotes environment variable.");
var optionsBuilder = new DbContextOptionsBuilder<SqlServerContext>();
optionsBuilder.UseSqlServer(connectionString, options =>
options.EnableRetryOnFailure(
maxRetryCount: 5,
maxRetryDelay: TimeSpan.FromSeconds(30),
errorNumbersToAdd: null));
return new SqlServerContext(optionsBuilder.Options);
}
}