Skip to content

Commit ec55939

Browse files
authored
Merge pull request #1 from bkoelman/tweaks
Workaround for slow queries with resource inheritance
2 parents ebfefa6 + fdd7f2d commit ec55939

17 files changed

+1517
-60
lines changed

.config/dotnet-tools.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"version": 1,
3+
"isRoot": true,
4+
"tools": {
5+
"dotnet-ef": {
6+
"version": "8.0.16",
7+
"commands": [
8+
"dotnet-ef"
9+
],
10+
"rollForward": false
11+
}
12+
}
13+
}

JsonApiBugReport/Data/DummySeed/SeedData.cs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11

22
using Bogus;
3-
using JsonApiBugReport.Data;
4-
using JsonApiBugReport.Data.Enums;
53
using Microsoft.EntityFrameworkCore;
64
using Microsoft.Extensions.DependencyInjection;
75
using System;
8-
using System.Collections.Generic;
96
using System.Linq;
107

118
namespace JsonApiBugReport.Data.DummySeed;
@@ -27,6 +24,7 @@ public static void Initialize(IServiceProvider serviceProvider)
2724

2825
// Seed Users
2926
var userFaker = new Faker<User>()
27+
.UseUtc()
3028
.RuleFor(u => u.FirstName, f => f.Name.FirstName())
3129
.RuleFor(u => u.LastName, f => f.Name.LastName())
3230
.RuleFor(u => u.Email, f => f.Internet.Email())
@@ -40,6 +38,7 @@ public static void Initialize(IServiceProvider serviceProvider)
4038

4139
// Seed UnitGroups
4240
var unitGroupFaker = new Faker<UnitGroup>()
41+
.UseUtc()
4342
.RuleFor(ug => ug.Name, f => f.Commerce.Department())
4443
.RuleFor(ug => ug.Description, f => f.Lorem.Sentence())
4544
.RuleFor(ug => ug.IsActive, f => f.Random.Bool())
@@ -53,6 +52,7 @@ public static void Initialize(IServiceProvider serviceProvider)
5352

5453
// Seed Units
5554
var unitFaker = new Faker<Unit>()
55+
.UseUtc()
5656
.RuleFor(u => u.Name, f => f.Commerce.ProductName())
5757
.RuleFor(u => u.Mnemonic, f => f.Random.AlphaNumeric(5))
5858
.RuleFor(u => u.Quantity, f => f.Random.Decimal(1, 100))
@@ -65,6 +65,7 @@ public static void Initialize(IServiceProvider serviceProvider)
6565

6666
// Seed PriceGroups
6767
var priceGroupFaker = new Faker<PriceGroup>()
68+
.UseUtc()
6869
.RuleFor(pg => pg.Name, f => f.Commerce.Department())
6970
.RuleFor(pg => pg.Description, f => f.Lorem.Sentence())
7071
.RuleFor(pg => pg.CreatedAt, f => f.Date.Past())
@@ -77,6 +78,7 @@ public static void Initialize(IServiceProvider serviceProvider)
7778

7879
// Seed Products
7980
var productFaker = new Faker<Product>()
81+
.UseUtc()
8082
.RuleFor(p => p.Name, f => f.Commerce.ProductName())
8183
.RuleFor(p => p.IsEnabled, f => f.Random.Bool())
8284
.RuleFor(p => p.ShortDescription, f => f.Commerce.ProductDescription())
@@ -99,6 +101,7 @@ public static void Initialize(IServiceProvider serviceProvider)
99101

100102
// Seed ProductAddons
101103
var productAddonFaker = new Faker<ProductAddon>()
104+
.UseUtc()
102105
.RuleFor(pa => pa.Name, f => f.Commerce.ProductName())
103106
.RuleFor(pa => pa.IsEnabled, f => f.Random.Bool())
104107
.RuleFor(pa => pa.ShortDescription, f => f.Commerce.ProductDescription())
@@ -122,6 +125,7 @@ public static void Initialize(IServiceProvider serviceProvider)
122125

123126
// Seed ProductBundles
124127
var productBundleFaker = new Faker<ProductBundle>()
128+
.UseUtc()
125129
.RuleFor(pb => pb.Name, f => f.Commerce.ProductName())
126130
.RuleFor(pb => pb.IsEnabled, f => f.Random.Bool())
127131
.RuleFor(pb => pb.ShortDescription, f => f.Commerce.ProductDescription())
@@ -142,6 +146,7 @@ public static void Initialize(IServiceProvider serviceProvider)
142146

143147
// Seed ProductGroups
144148
var productGroupFaker = new Faker<ProductGroup>()
149+
.UseUtc()
145150
.RuleFor(pg => pg.Name, f => f.Commerce.ProductName())
146151
.RuleFor(pg => pg.IsEnabled, f => f.Random.Bool())
147152
.RuleFor(pg => pg.ShortDescription, f => f.Commerce.ProductDescription())
@@ -165,4 +170,14 @@ public static void Initialize(IServiceProvider serviceProvider)
165170
context.SaveChanges();
166171
}
167172
}
173+
174+
private static Faker<T> UseUtc<T>(this Faker<T> faker)
175+
where T : class
176+
{
177+
// Setting the system DateTime to kind Utc, so that faker calls like PastOffset() don't depend on the system time zone.
178+
// See https://docs.microsoft.com/en-us/dotnet/api/system.datetimeoffset.op_implicit?view=net-6.0#remarks
179+
faker.UseDateTimeReference(DateTime.UtcNow);
180+
181+
return faker;
182+
}
168183
}

JsonApiBugReport/Extensions/DbContextExtensions.cs

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,44 +6,50 @@
66
using Microsoft.Extensions.DependencyInjection;
77
using System;
88
using System.Threading.Tasks;
9+
using Microsoft.EntityFrameworkCore.Diagnostics;
910

1011
namespace JsonApiBugReport.Extensions;
1112

1213
public static class DbContextExtensions
1314
{
1415
public static IServiceCollection AddApplicationDbContext(this IServiceCollection services, IConfiguration configuration)
1516
{
16-
var connectionString = configuration.GetConnectionString("ApplicationDb");
1717
return services
1818
.AddDbContext<ApplicationDbContext>(options =>
1919
{
20+
#if USE_SQL_SERVER
21+
var connectionString = configuration.GetConnectionString("SqlServerDb");
2022
options.UseSqlServer(connectionString);
23+
#else
24+
var connectionString = configuration.GetConnectionString("PostgresDb");
25+
options.UseNpgsql(connectionString);
26+
#endif
27+
28+
#if DEBUG
29+
options.LogTo(Console.WriteLine, [RelationalEventId.CommandExecuting]);
30+
options.EnableSensitiveDataLogging();
31+
#endif
2132
});
2233
}
2334

2435
public static async Task RunDbMigrations(this WebApplication app)
2536
{
26-
2737
using var scope = app.Services.CreateScope();
28-
{
29-
var services = scope.ServiceProvider;
38+
var services = scope.ServiceProvider;
3039

31-
try
32-
{
33-
var context = services.GetRequiredService<ApplicationDbContext>();
40+
try
41+
{
42+
var context = services.GetRequiredService<ApplicationDbContext>();
3443

35-
// Apply migrations
36-
await context.Database.MigrateAsync();
44+
// Apply migrations
45+
await context.Database.MigrateAsync();
3746

38-
// Seed only if needed
39-
SeedData.Initialize(services);
40-
}
41-
catch (Exception ex)
42-
{
43-
Console.WriteLine($"Migration or seeding failed: {ex.Message}");
44-
}
47+
// Seed only if needed
48+
SeedData.Initialize(services);
49+
}
50+
catch (Exception ex)
51+
{
52+
Console.WriteLine($"Migration or seeding failed: {ex.Message}");
4553
}
46-
4754
}
48-
4955
}
Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,43 @@
11
<Project Sdk="Microsoft.NET.Sdk.Web">
22

3-
<PropertyGroup>
4-
<TargetFramework>net8.0</TargetFramework>
5-
</PropertyGroup>
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<UseLatestJsonApiDotNetCoreVersion>True</UseLatestJsonApiDotNetCoreVersion>
6+
<UseSqlServer>True</UseSqlServer>
7+
</PropertyGroup>
68

9+
<PropertyGroup Condition="$(UseLatestJsonApiDotNetCoreVersion) == 'True'">
10+
<JsonApiDotNetCoreVersion>5.7.2-master-01199</JsonApiDotNetCoreVersion>
11+
</PropertyGroup>
12+
13+
<PropertyGroup Condition="$(UseLatestJsonApiDotNetCoreVersion) != 'True'">
14+
<JsonApiDotNetCoreVersion>5.6.0</JsonApiDotNetCoreVersion>
15+
</PropertyGroup>
16+
17+
<PropertyGroup Condition="'$(UseSqlServer)' == 'True'">
18+
<DefineConstants>$(DefineConstants);USE_SQL_SERVER</DefineConstants>
19+
</PropertyGroup>
20+
21+
<ItemGroup Condition="'$(UseSqlServer)' == 'True'">
22+
<Compile Remove="Migrations\Postgres\**" />
23+
</ItemGroup>
24+
25+
<ItemGroup Condition="'$(UseSqlServer)' != 'True'">
26+
<Compile Remove="Migrations\SqlServer\**" />
27+
</ItemGroup>
728

829
<ItemGroup>
30+
<PackageReference Include="AgileObjects.ReadableExpressions" Version="4.1.3" />
931
<PackageReference Include="Bogus" Version="35.6.3" />
10-
<PackageReference Include="JsonApiDotNetCore" Version="5.7.1" />
32+
<PackageReference Include="JsonApiDotNetCore" Version="$(JsonApiDotNetCoreVersion)" />
1133
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.16" />
12-
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.16" />
34+
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Condition="'$(UseSqlServer)' == 'True'" Version="8.0.16" />
1335
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.16">
14-
<PrivateAssets>all</PrivateAssets>
15-
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
36+
<PrivateAssets>all</PrivateAssets>
37+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1638
</PackageReference>
1739
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
18-
</ItemGroup>
19-
20-
21-
<ItemGroup>
22-
<Folder Include="Data\DummySeed\" />
40+
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Condition="'$(UseSqlServer)' != 'True'" Version="8.0.11" />
2341
</ItemGroup>
2442

2543
</Project>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
@HostAddress = https://localhost:7084
2+
3+
### Basic query with includes (pruned)
4+
5+
GET {{HostAddress}}/PriceGroup?include=products.unitGroup.units
6+
7+
### Single include for easier debugging of recursion
8+
9+
GET {{HostAddress}}/PriceGroup?include=products
10+
11+
### Sparse fieldset on subset of derived types
12+
13+
GET {{HostAddress}}/PriceGroup?include=products.unitGroup.units&fields[Addon]=trialDuration,allowsCustomEndDate&fields[Bundle]=isTaxable
14+
15+
### Non-default page size on top-level include
16+
17+
GET {{HostAddress}}/PriceGroup?include=products.unitGroup.units&page[size]=products:5
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System.Linq.Expressions;
2+
using AgileObjects.ReadableExpressions;
3+
using JsonApiDotNetCore.Queries;
4+
using JsonApiDotNetCore.Queries.QueryableBuilding;
5+
using Microsoft.Extensions.Logging;
6+
7+
#nullable enable
8+
9+
namespace JsonApiBugReport;
10+
11+
public class LoggingQueryableBuilder(
12+
IIncludeClauseBuilder includeClauseBuilder,
13+
IWhereClauseBuilder whereClauseBuilder,
14+
IOrderClauseBuilder orderClauseBuilder,
15+
ISkipTakeClauseBuilder skipTakeClauseBuilder,
16+
ISelectClauseBuilder selectClauseBuilder,
17+
ILogger<LoggingQueryableBuilder> logger)
18+
: QueryableBuilder(includeClauseBuilder, whereClauseBuilder, orderClauseBuilder, skipTakeClauseBuilder,
19+
selectClauseBuilder)
20+
{
21+
public override Expression ApplyQuery(QueryLayer layer, QueryableBuilderContext context)
22+
{
23+
var expression = base.ApplyQuery(layer, context);
24+
var text = expression.ToReadableString();
25+
26+
if (text.StartsWith("[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression]"))
27+
{
28+
logger.LogInformation("Expression: {Expression}", text);
29+
}
30+
31+
return expression;
32+
}
33+
}

0 commit comments

Comments
 (0)