Skip to content

Commit 1902753

Browse files
committed
Implement settings & extend test coverage
1 parent 1395ee5 commit 1902753

18 files changed

Lines changed: 293 additions & 40 deletions

File tree

src/ServiceControl.Persistence.EFCore/DbContexts/ServiceControlDbContext.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,22 @@ namespace ServiceControl.Persistence.EFCore.DbContexts;
77
public abstract class ServiceControlDbContext(DbContextOptions options) : DbContext(options)
88
{
99
public DbSet<EndpointSettingsEntity> EndpointSettings { get; set; }
10+
public DbSet<FailedMessageEntity> FailedMessages { get; set; }
1011
public DbSet<KnownEndpointEntity> KnownEndpoints { get; set; }
1112
public DbSet<KnownEndpointInsertOnlyEntity> KnownEndpointsInsertOnly { get; set; }
13+
public DbSet<TrialMetadataEntity> TrialMetadata { get; set; }
1214

1315
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
14-
{
15-
optionsBuilder.EnableDetailedErrors();
16-
}
16+
=> optionsBuilder.EnableDetailedErrors();
1717

1818
protected override void OnModelCreating(ModelBuilder modelBuilder)
1919
{
2020
base.OnModelCreating(modelBuilder);
2121

22+
modelBuilder.ApplyConfiguration(new EndpointSettingsConfiguration());
23+
modelBuilder.ApplyConfiguration(new FailedMessageEntityConfiguration());
2224
modelBuilder.ApplyConfiguration(new KnownEndpointConfiguration());
2325
modelBuilder.ApplyConfiguration(new KnownEndpointInsertOnlyConfiguration());
26+
modelBuilder.ApplyConfiguration(new TrialMetadataEntityConfiguration());
2427
}
2528
}

src/ServiceControl.Persistence.EFCore/Entities/FailedMessageEntity.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ namespace ServiceControl.Persistence.EFCore.Entities;
22

33
public class FailedMessageEntity
44
{
5+
//todo: incomplete, needs the ingestion done...
56
public Guid Id { get; set; }
6-
public string EndpointAddress
7-
8-
//todo: incomplete, needs the other feature finished...
7+
8+
public required string OriginalMessageIdentifier { get; set; }
9+
10+
public required string EndpointAddress { get; set; }
911
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace ServiceControl.Persistence.EFCore.Entities;
2+
3+
public class TrialMetadataEntity
4+
{
5+
public const int TrialMetadataId = 1;
6+
7+
public int Id { get; set; }
8+
public DateOnly? TrialEndDate { get; set; }
9+
}

src/ServiceControl.Persistence.EFCore/EntityConfigurations/EndpointSettingsEntity.cs renamed to src/ServiceControl.Persistence.EFCore/EntityConfigurations/EndpointSettingsConfiguration.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ namespace ServiceControl.Persistence.EFCore.EntityConfigurations;
44
using Microsoft.EntityFrameworkCore;
55
using Microsoft.EntityFrameworkCore.Metadata.Builders;
66

7-
public class EndpointSettingsConfiguration : IEntityTypeConfiguration<EndpointSettingsEntity>
7+
public class EndpointSettingsConfiguration : IEntityTypeConfiguration<EndpointSettingsEntity>
88
{
99
public void Configure(EntityTypeBuilder<EndpointSettingsEntity> builder)
1010
{
1111
builder.ToTable("EndpointSettings");
1212
builder.HasKey(x => x.Name);
13+
builder.Property(x => x.Name).HasMaxLength(200).IsRequired();
1314
}
1415
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
namespace ServiceControl.Persistence.EFCore.EntityConfigurations;
2+
3+
using Entities;
4+
using Microsoft.EntityFrameworkCore;
5+
using Microsoft.EntityFrameworkCore.Metadata.Builders;
6+
7+
public class FailedMessageEntityConfiguration : IEntityTypeConfiguration<FailedMessageEntity>
8+
{
9+
public void Configure(EntityTypeBuilder<FailedMessageEntity> builder)
10+
{
11+
builder.ToTable("FailedMessages");
12+
builder.HasKey(e => e.Id);
13+
builder.Property(e => e.Id).ValueGeneratedNever();
14+
//todo: validate column constraints
15+
builder.Property(e => e.OriginalMessageIdentifier).HasMaxLength(200).IsRequired();
16+
builder.Property(e => e.EndpointAddress).HasMaxLength(200).IsRequired();
17+
18+
builder.HasIndex(e => new { e.OriginalMessageIdentifier, e.EndpointAddress });
19+
}
20+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
namespace ServiceControl.Persistence.EFCore.EntityConfigurations;
2+
3+
using Entities;
4+
using Microsoft.EntityFrameworkCore;
5+
using Microsoft.EntityFrameworkCore.Metadata.Builders;
6+
7+
public class TrialMetadataEntityConfiguration : IEntityTypeConfiguration<TrialMetadataEntity>
8+
{
9+
public void Configure(EntityTypeBuilder<TrialMetadataEntity> builder)
10+
{
11+
builder.HasKey(e => e.Id);
12+
builder.HasData(new TrialMetadataEntity
13+
{
14+
Id = 1,
15+
TrialEndDate = null
16+
});
17+
}
18+
}

src/ServiceControl.Persistence.EFCore/Implementation/DataStoreBase.cs

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -36,20 +36,7 @@ protected async Task ExecuteWithDbContext(Func<ServiceControlDbContext, Task> op
3636
/// <summary>
3737
/// Executes an operation with a scoped DbContext, without returning a result
3838
/// </summary>
39-
protected async IAsyncEnumerable<T> ExecuteWithDbContext<T>(Func<ServiceControlDbContext, IAsyncEnumerable<T>> operation)
40-
{
41-
await using var scope = scopeFactory.CreateAsyncScope();
42-
var dbContext = scope.ServiceProvider.GetRequiredService<ServiceControlDbContext>();
43-
await foreach (var row in operation(dbContext))
44-
{
45-
yield return row;
46-
}
47-
}
48-
49-
/// <summary>
50-
/// Executes an operation with a scoped DbContext, without returning a result
51-
/// </summary>
52-
protected async IAsyncEnumerable<T> ExecuteWithDbContext<T>(Func<ServiceControlDbContext, IAsyncEnumerable<T>> operation, [EnumeratorCancellation] CancellationToken cancellationToken)
39+
protected async IAsyncEnumerable<T> ExecuteWithDbContext<T>(Func<ServiceControlDbContext, IAsyncEnumerable<T>> operation, [EnumeratorCancellation] CancellationToken cancellationToken = default)
5340
{
5441
await using var scope = scopeFactory.CreateAsyncScope();
5542
var dbContext = scope.ServiceProvider.GetRequiredService<ServiceControlDbContext>();

src/ServiceControl.Persistence.EFCore/Implementation/EndpointSettingsStore.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,21 @@ public Task UpdateEndpointSettings(EndpointSettings settings, CancellationToken
2525
await context.SaveChangesAsync(token);
2626
return;
2727
}
28-
catch (someexception e) when (condition)
28+
catch (DbUpdateException)
2929
{
30-
//this failed because of key conflict so it's a race!
30+
//this probably failed because of key conflict so try again
3131
}
3232

3333
await context.Entry(entity).ReloadAsync(token);
34-
entity.TrackInstances = settings.TrackInstances;
35-
await context.SaveChangesAsync(token);
3634
}
35+
36+
entity.TrackInstances = settings.TrackInstances;
37+
await context.SaveChangesAsync(token);
3738
});
3839

3940
public Task Delete(string name, CancellationToken cancellationToken) => ExecuteWithDbContext(async context =>
4041
{
41-
context.KnownEndpoints.RemoveRange(context.KnownEndpoints.Where(x => x.Name == name));
42+
context.EndpointSettings.RemoveRange(context.EndpointSettings.Where(x => x.Name == name));
4243
await context.SaveChangesAsync(cancellationToken);
4344
});
4445
}
Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,32 @@
11
namespace ServiceControl.Persistence.EFCore.Implementation;
22

3+
using Microsoft.EntityFrameworkCore;
4+
using Microsoft.Extensions.DependencyInjection;
35
using ServiceControl.MessageFailures;
46
using ServiceControl.Persistence.Infrastructure;
57

6-
public class QueueAddressStore : IQueueAddressStore
8+
public class QueueAddressStore(IServiceScopeFactory scopeFactory) : DataStoreBase(scopeFactory), IQueueAddressStore
79
{
8-
public Task<QueryResult<IList<QueueAddress>>> GetAddresses(PagingInfo pagingInfo)
9-
{
10-
await exe
11-
var queryable
12-
}
10+
public Task<QueryResult<IList<QueueAddress>>> GetAddresses(PagingInfo pagingInfo) =>
11+
GetAddressesBySearchTerm(string.Empty, pagingInfo);
1312

1413
public Task<QueryResult<IList<QueueAddress>>> GetAddressesBySearchTerm(string search, PagingInfo pagingInfo) =>
15-
throw new NotImplementedException();
14+
ExecuteWithDbContext(async context =>
15+
{
16+
var query = context.FailedMessages
17+
.Where(fm => string.IsNullOrWhiteSpace(search) || fm.EndpointAddress.StartsWith(search))
18+
.Select(fm => new { fm.OriginalMessageIdentifier, fm.EndpointAddress })
19+
.Distinct()
20+
.GroupBy(failure => failure.EndpointAddress)
21+
.OrderBy(failuresByEndpoint => failuresByEndpoint.Key)
22+
.Select(failuresByEndpoint => new QueueAddress
23+
{
24+
PhysicalAddress = failuresByEndpoint.Key,
25+
FailedMessageCount = failuresByEndpoint.Count()
26+
});
27+
28+
var items = await query.Skip(pagingInfo.Offset).Take(pagingInfo.PageSize).ToListAsync();
29+
30+
return new QueryResult<IList<QueueAddress>>(items, new QueryStatsInfo("", query.Count(), false));
31+
});
1632
}
Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,23 @@
11
namespace ServiceControl.Persistence.EFCore.Implementation;
22

3-
public class TrialLicenseDataProvider : ITrialLicenseDataProvider
3+
using Entities;
4+
using Microsoft.EntityFrameworkCore;
5+
using Microsoft.Extensions.DependencyInjection;
6+
7+
public class TrialLicenseDataProvider(IServiceScopeFactory scopeFactory) : DataStoreBase(scopeFactory), ITrialLicenseDataProvider
48
{
59
public Task<DateOnly?> GetTrialEndDate(CancellationToken cancellationToken)
6-
{
7-
}
10+
=> ExecuteWithDbContext(async context =>
11+
{
12+
var trialMetadata = await context.TrialMetadata.SingleAsync(t => t.Id == TrialMetadataEntity.TrialMetadataId, cancellationToken);
13+
return trialMetadata.TrialEndDate;
14+
});
815

9-
public Task StoreTrialEndDate(DateOnly trialEndDate, CancellationToken cancellationToken) =>
10-
throw new NotImplementedException();
11-
}
16+
public Task StoreTrialEndDate(DateOnly trialEndDate, CancellationToken cancellationToken)
17+
=> ExecuteWithDbContext(async context =>
18+
{
19+
var trialMetadata = await context.TrialMetadata.SingleAsync(t => t.Id == TrialMetadataEntity.TrialMetadataId, cancellationToken);
20+
trialMetadata.TrialEndDate = trialEndDate;
21+
await (Task)context.SaveChangesAsync(cancellationToken);
22+
});
23+
}

0 commit comments

Comments
 (0)