Skip to content

Commit d45b745

Browse files
authored
Merge pull request #5226 from Particular/john/review
2 parents ddbb137 + 70c2871 commit d45b745

20 files changed

+1212
-189
lines changed

src/ServiceControl.Persistence.Sql.Core/Implementation/LicensingDataStore.cs

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ public async Task<IDictionary<string, IEnumerable<ThroughputData>>> GetEndpointT
2424

2525
var cutOff = DefaultCutOff();
2626

27-
var data = await dbContext.Throughput.Where(x => queueNames.Contains(x.EndpointName) && x.Date >= cutOff)
27+
var data = await dbContext.Throughput
28+
.AsNoTracking()
29+
.Where(x => queueNames.Contains(x.EndpointName) && x.Date >= cutOff)
2830
.ToListAsync(cancellationToken);
2931

3032
var lookup = data.ToLookup(x => x.EndpointName);
@@ -170,28 +172,43 @@ public async Task UpdateUserIndicatorOnEndpoints(List<UpdateUserIndicator> userI
170172
using var scope = serviceProvider.CreateScope();
171173
using var dbContext = scope.ServiceProvider.GetRequiredService<ServiceControlDbContextBase>();
172174

175+
// Get all relevant sanitized names from endpoints matched by name
176+
var sanitizedNames = await dbContext.Endpoints
177+
.Where(e => updates.Keys.Contains(e.EndpointName) && e.SanitizedEndpointName != null)
178+
.Select(e => e.SanitizedEndpointName)
179+
.Distinct()
180+
.ToListAsync(cancellationToken);
181+
182+
// Get all endpoints that match either by name or sanitized name in a single query
173183
var endpoints = await dbContext.Endpoints
174-
.Where(e => updates.Keys.Contains(e.EndpointName) || (e.SanitizedEndpointName != null && updates.Keys.Contains(e.SanitizedEndpointName)))
184+
.Where(e => updates.Keys.Contains(e.EndpointName)
185+
|| (e.SanitizedEndpointName != null && updates.Keys.Contains(e.SanitizedEndpointName))
186+
|| (e.SanitizedEndpointName != null && sanitizedNames.Contains(e.SanitizedEndpointName)))
175187
.ToListAsync(cancellationToken) ?? [];
176188

177189
foreach (var endpoint in endpoints)
178190
{
179191
if (endpoint.SanitizedEndpointName is not null && updates.TryGetValue(endpoint.SanitizedEndpointName, out var newValueFromSanitizedName))
180192
{
193+
// Direct match by sanitized name
181194
endpoint.UserIndicator = newValueFromSanitizedName;
182195
}
183196
else if (updates.TryGetValue(endpoint.EndpointName, out var newValueFromEndpoint))
184197
{
198+
// Direct match by endpoint name - this should also update all endpoints with the same sanitized name
185199
endpoint.UserIndicator = newValueFromEndpoint;
186-
//update all that match this sanitized name
187-
var sanitizedMatchingEndpoints = await dbContext.Endpoints
188-
.Where(e => e.SanitizedEndpointName == endpoint.SanitizedEndpointName && e.EndpointName != endpoint.EndpointName)
189-
.ToListAsync(cancellationToken) ?? [];
200+
}
201+
else if (endpoint.SanitizedEndpointName != null && sanitizedNames.Contains(endpoint.SanitizedEndpointName))
202+
{
203+
// This endpoint shares a sanitized name with an endpoint that was matched by name
204+
// Find the update value from the endpoint that has this sanitized name
205+
var matchingEndpoint = endpoints.FirstOrDefault(e =>
206+
e.SanitizedEndpointName == endpoint.SanitizedEndpointName &&
207+
updates.ContainsKey(e.EndpointName));
190208

191-
foreach (var matchingEndpointOnSanitizedName in sanitizedMatchingEndpoints)
209+
if (matchingEndpoint != null && updates.TryGetValue(matchingEndpoint.EndpointName, out var cascadedValue))
192210
{
193-
matchingEndpointOnSanitizedName.UserIndicator = newValueFromEndpoint;
194-
_ = dbContext.Endpoints.Update(matchingEndpointOnSanitizedName);
211+
endpoint.UserIndicator = cascadedValue;
195212
}
196213
}
197214
_ = dbContext.Endpoints.Update(endpoint);
@@ -263,7 +280,9 @@ public async Task<BrokerMetadata> GetBrokerMetadata(CancellationToken cancellati
263280
{
264281
using var scope = serviceProvider.CreateScope();
265282
await using var dbContext = scope.ServiceProvider.GetRequiredService<ServiceControlDbContextBase>();
266-
var existing = await dbContext.LicensingMetadata.SingleOrDefaultAsync(m => m.Key == key, cancellationToken);
283+
var existing = await dbContext.LicensingMetadata
284+
.AsNoTracking()
285+
.SingleOrDefaultAsync(m => m.Key == key, cancellationToken);
267286
if (existing is null)
268287
{
269288
return default;

src/ServiceControl.Persistence.Sql.MySQL/Migrations/20241208000000_InitialCreate.cs

Lines changed: 0 additions & 32 deletions
This file was deleted.

src/ServiceControl.Persistence.Sql.MySQL/Migrations/20251210040740_InitialCreate.Designer.cs

Lines changed: 143 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
#nullable disable
2+
3+
namespace ServiceControl.Persistence.Sql.MySQL.Migrations
4+
{
5+
using System;
6+
using Microsoft.EntityFrameworkCore.Metadata;
7+
using Microsoft.EntityFrameworkCore.Migrations;
8+
9+
/// <inheritdoc />
10+
public partial class InitialCreate : Migration
11+
{
12+
/// <inheritdoc />
13+
protected override void Up(MigrationBuilder migrationBuilder)
14+
{
15+
migrationBuilder.AlterDatabase()
16+
.Annotation("MySql:CharSet", "utf8mb4");
17+
18+
migrationBuilder.CreateTable(
19+
name: "DailyThroughput",
20+
columns: table => new
21+
{
22+
Id = table.Column<int>(type: "int", nullable: false)
23+
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
24+
EndpointName = table.Column<string>(type: "varchar(200)", maxLength: 200, nullable: false)
25+
.Annotation("MySql:CharSet", "utf8mb4"),
26+
ThroughputSource = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: false)
27+
.Annotation("MySql:CharSet", "utf8mb4"),
28+
Date = table.Column<DateOnly>(type: "date", nullable: false),
29+
MessageCount = table.Column<long>(type: "bigint", nullable: false)
30+
},
31+
constraints: table =>
32+
{
33+
table.PrimaryKey("PK_DailyThroughput", x => x.Id);
34+
})
35+
.Annotation("MySql:CharSet", "utf8mb4");
36+
37+
migrationBuilder.CreateTable(
38+
name: "LicensingMetadata",
39+
columns: table => new
40+
{
41+
Id = table.Column<int>(type: "int", nullable: false)
42+
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
43+
Key = table.Column<string>(type: "varchar(200)", maxLength: 200, nullable: false)
44+
.Annotation("MySql:CharSet", "utf8mb4"),
45+
Data = table.Column<string>(type: "varchar(2000)", maxLength: 2000, nullable: false)
46+
.Annotation("MySql:CharSet", "utf8mb4")
47+
},
48+
constraints: table =>
49+
{
50+
table.PrimaryKey("PK_LicensingMetadata", x => x.Id);
51+
})
52+
.Annotation("MySql:CharSet", "utf8mb4");
53+
54+
migrationBuilder.CreateTable(
55+
name: "ThroughputEndpoint",
56+
columns: table => new
57+
{
58+
Id = table.Column<int>(type: "int", nullable: false)
59+
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
60+
EndpointName = table.Column<string>(type: "varchar(200)", maxLength: 200, nullable: false)
61+
.Annotation("MySql:CharSet", "utf8mb4"),
62+
ThroughputSource = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: false)
63+
.Annotation("MySql:CharSet", "utf8mb4"),
64+
SanitizedEndpointName = table.Column<string>(type: "longtext", nullable: true)
65+
.Annotation("MySql:CharSet", "utf8mb4"),
66+
EndpointIndicators = table.Column<string>(type: "longtext", nullable: true)
67+
.Annotation("MySql:CharSet", "utf8mb4"),
68+
UserIndicator = table.Column<string>(type: "longtext", nullable: true)
69+
.Annotation("MySql:CharSet", "utf8mb4"),
70+
Scope = table.Column<string>(type: "longtext", nullable: true)
71+
.Annotation("MySql:CharSet", "utf8mb4"),
72+
LastCollectedData = table.Column<DateOnly>(type: "date", nullable: false)
73+
},
74+
constraints: table =>
75+
{
76+
table.PrimaryKey("PK_ThroughputEndpoint", x => x.Id);
77+
})
78+
.Annotation("MySql:CharSet", "utf8mb4");
79+
80+
migrationBuilder.CreateTable(
81+
name: "TrialLicense",
82+
columns: table => new
83+
{
84+
Id = table.Column<int>(type: "int", nullable: false, defaultValue: 1),
85+
TrialEndDate = table.Column<DateOnly>(type: "date", nullable: false)
86+
},
87+
constraints: table =>
88+
{
89+
table.PrimaryKey("PK_TrialLicense", x => x.Id);
90+
})
91+
.Annotation("MySql:CharSet", "utf8mb4");
92+
93+
migrationBuilder.CreateIndex(
94+
name: "UC_DailyThroughput_EndpointName_ThroughputSource_Date",
95+
table: "DailyThroughput",
96+
columns: new[] { "EndpointName", "ThroughputSource", "Date" },
97+
unique: true);
98+
99+
migrationBuilder.CreateIndex(
100+
name: "IX_LicensingMetadata_Key",
101+
table: "LicensingMetadata",
102+
column: "Key",
103+
unique: true);
104+
105+
migrationBuilder.CreateIndex(
106+
name: "UC_ThroughputEndpoint_EndpointName_ThroughputSource",
107+
table: "ThroughputEndpoint",
108+
columns: new[] { "EndpointName", "ThroughputSource" },
109+
unique: true);
110+
}
111+
112+
/// <inheritdoc />
113+
protected override void Down(MigrationBuilder migrationBuilder)
114+
{
115+
migrationBuilder.DropTable(
116+
name: "DailyThroughput");
117+
118+
migrationBuilder.DropTable(
119+
name: "LicensingMetadata");
120+
121+
migrationBuilder.DropTable(
122+
name: "ThroughputEndpoint");
123+
124+
migrationBuilder.DropTable(
125+
name: "TrialLicense");
126+
}
127+
}
128+
}

0 commit comments

Comments
 (0)