Skip to content

Commit f9ff573

Browse files
committed
Demonstrate how to choose DbContext at runtime.
Note this only works properly if the EF Core models (not the database tables) are identical.
1 parent 9c51fe3 commit f9ff573

File tree

6 files changed

+119
-10
lines changed

6 files changed

+119
-10
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using GettingStarted.Models;
2+
using JetBrains.Annotations;
3+
using Microsoft.EntityFrameworkCore;
4+
5+
namespace GettingStarted.Data;
6+
7+
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
8+
public class PostgreSqlSampleDbContext(DbContextOptions<PostgreSqlSampleDbContext> options) : DbContext(options)
9+
{
10+
public DbSet<Book> Books => Set<Book>();
11+
}

src/Examples/GettingStarted/Data/SampleDbContext.cs renamed to src/Examples/GettingStarted/Data/SqliteSampleDbContext.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
namespace GettingStarted.Data;
66

77
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
8-
public class SampleDbContext(DbContextOptions<SampleDbContext> options) : DbContext(options)
8+
public class SqliteSampleDbContext(DbContextOptions<SqliteSampleDbContext> options) : DbContext(options)
99
{
1010
public DbSet<Book> Books => Set<Book>();
1111
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace GettingStarted;
2+
3+
internal enum DatabaseType
4+
{
5+
Sqlite,
6+
PostgreSql
7+
}

src/Examples/GettingStarted/GettingStarted.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@
1313

1414
<ItemGroup>
1515
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="$(EntityFrameworkCoreVersion)" />
16+
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="$(EntityFrameworkCoreVersion)" />
1617
</ItemGroup>
1718
</Project>

src/Examples/GettingStarted/Program.cs

+56-9
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,39 @@
11
using System.Diagnostics;
2+
using GettingStarted;
23
using GettingStarted.Data;
34
using GettingStarted.Models;
45
using JsonApiDotNetCore.Configuration;
6+
using JsonApiDotNetCore.Repositories;
57
using Microsoft.EntityFrameworkCore;
68
using Microsoft.EntityFrameworkCore.Diagnostics;
79

810
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
911

1012
// Add services to the container.
1113

12-
builder.Services.AddDbContext<SampleDbContext>(options =>
14+
builder.Services.AddDbContext<SqliteSampleDbContext>(options =>
1315
{
1416
options.UseSqlite("Data Source=SampleDb.db;Pooling=False");
1517
SetDbContextDebugOptions(options);
1618
});
1719

18-
builder.Services.AddJsonApi<SampleDbContext>(options =>
20+
builder.Services.AddDbContext<PostgreSqlSampleDbContext>(options =>
21+
{
22+
options.UseNpgsql("Host=localhost;Database=ExampleDb;User ID=postgres;Password=postgres;Include Error Detail=true");
23+
SetDbContextDebugOptions(options);
24+
});
25+
26+
// EntityFrameworkCoreRepository injects IDbContextResolver to obtain the DbContext during a request.
27+
builder.Services.AddHttpContextAccessor();
28+
builder.Services.AddScoped<IDbContextResolver, QueryStringDbContextResolver>();
29+
30+
// DbContext is used to scan the model at app startup. Pick any, since their entities are identical.
31+
builder.Services.AddJsonApi<SqliteSampleDbContext>(options =>
1932
{
2033
options.Namespace = "api";
2134
options.UseRelativeLinks = true;
2235
options.IncludeTotalResourceCount = true;
36+
options.AllowUnknownQueryStringParameters = true;
2337

2438
#if DEBUG
2539
options.IncludeExceptionStackTraceInErrors = true;
@@ -36,7 +50,8 @@
3650
app.UseJsonApi();
3751
app.MapControllers();
3852

39-
await CreateDatabaseAsync(app.Services);
53+
await CreateSqliteDatabaseAsync(app.Services);
54+
await CreatePostgreSqlDatabaseAsync(app.Services);
4055

4156
app.Run();
4257

@@ -48,21 +63,19 @@ static void SetDbContextDebugOptions(DbContextOptionsBuilder options)
4863
options.ConfigureWarnings(builder => builder.Ignore(CoreEventId.SensitiveDataLoggingEnabledWarning));
4964
}
5065

51-
static async Task CreateDatabaseAsync(IServiceProvider serviceProvider)
66+
static async Task CreateSqliteDatabaseAsync(IServiceProvider serviceProvider)
5267
{
5368
await using AsyncServiceScope scope = serviceProvider.CreateAsyncScope();
5469

55-
var dbContext = scope.ServiceProvider.GetRequiredService<SampleDbContext>();
70+
var dbContext = scope.ServiceProvider.GetRequiredService<SqliteSampleDbContext>();
5671
await dbContext.Database.EnsureDeletedAsync();
5772
await dbContext.Database.EnsureCreatedAsync();
5873

59-
await CreateSampleDataAsync(dbContext);
74+
await CreateSqliteSampleDataAsync(dbContext);
6075
}
6176

62-
static async Task CreateSampleDataAsync(SampleDbContext dbContext)
77+
static async Task CreateSqliteSampleDataAsync(SqliteSampleDbContext dbContext)
6378
{
64-
// Note: The generate-examples.ps1 script (to create example requests in documentation) depends on these.
65-
6679
dbContext.Books.AddRange(new Book
6780
{
6881
Title = "Frankenstein",
@@ -91,3 +104,37 @@ static async Task CreateSampleDataAsync(SampleDbContext dbContext)
91104

92105
await dbContext.SaveChangesAsync();
93106
}
107+
108+
static async Task CreatePostgreSqlDatabaseAsync(IServiceProvider serviceProvider)
109+
{
110+
await using AsyncServiceScope scope = serviceProvider.CreateAsyncScope();
111+
112+
var dbContext = scope.ServiceProvider.GetRequiredService<PostgreSqlSampleDbContext>();
113+
await dbContext.Database.EnsureDeletedAsync();
114+
await dbContext.Database.EnsureCreatedAsync();
115+
116+
await CreatePostgreSqlSampleDataAsync(dbContext);
117+
}
118+
119+
static async Task CreatePostgreSqlSampleDataAsync(PostgreSqlSampleDbContext dbContext)
120+
{
121+
dbContext.Books.AddRange(new Book
122+
{
123+
Title = "Wolf Hall",
124+
PublishYear = 2009,
125+
Author = new Person
126+
{
127+
Name = "Hilary Mantel"
128+
}
129+
}, new Book
130+
{
131+
Title = "Gilead",
132+
PublishYear = 2004,
133+
Author = new Person
134+
{
135+
Name = "Marilynne Robinson"
136+
}
137+
});
138+
139+
await dbContext.SaveChangesAsync();
140+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using System.Net;
2+
using GettingStarted.Data;
3+
using JsonApiDotNetCore.Errors;
4+
using JsonApiDotNetCore.Repositories;
5+
using JsonApiDotNetCore.Serialization.Objects;
6+
using Microsoft.EntityFrameworkCore;
7+
using Microsoft.Extensions.Primitives;
8+
9+
namespace GettingStarted;
10+
11+
public sealed class QueryStringDbContextResolver(IHttpContextAccessor httpContextAccessor, SqliteSampleDbContext startupDbContext) : IDbContextResolver
12+
{
13+
private readonly IHttpContextAccessor _httpContextAccessor = httpContextAccessor;
14+
private readonly SqliteSampleDbContext _startupDbContext = startupDbContext;
15+
16+
public DbContext GetContext()
17+
{
18+
HttpContext? httpContext = _httpContextAccessor.HttpContext;
19+
20+
if (httpContext == null)
21+
{
22+
// DbContext is used to scan the model at app startup. Pick any, since their entities are identical.
23+
return _startupDbContext;
24+
}
25+
26+
StringValues dbType = httpContext.Request.Query["dbType"];
27+
28+
if (!Enum.TryParse(dbType, true, out DatabaseType databaseType))
29+
{
30+
throw new JsonApiException(new ErrorObject(HttpStatusCode.BadRequest)
31+
{
32+
Title = "The 'dbType' query string parameter is missing or invalid."
33+
});
34+
}
35+
36+
return databaseType switch
37+
{
38+
DatabaseType.Sqlite => httpContext.RequestServices.GetRequiredService<SqliteSampleDbContext>(),
39+
DatabaseType.PostgreSql => httpContext.RequestServices.GetRequiredService<PostgreSqlSampleDbContext>(),
40+
_ => throw new NotSupportedException("Unknown database type.")
41+
};
42+
}
43+
}

0 commit comments

Comments
 (0)