feat: work on implementing UoW pattern

This commit is contained in:
Stevan Freeborn
2024-08-20 23:42:20 -05:00
parent c65dc6dab6
commit 00994cd4d9
21 changed files with 606 additions and 20 deletions
@@ -0,0 +1,22 @@
using Bogus;
namespace SanctionsSearch.Worker.Tests.Fakes;
class SdnFaker : Faker<Sdn>
{
public SdnFaker()
{
RuleFor(x => x.Id, f => f.Random.Int(1, int.MaxValue));
RuleFor(x => x.Name, f => f.Person.FullName);
RuleFor(x => x.Type, f => f.Lorem.Word());
RuleFor(x => x.Program, f => f.Lorem.Word());
RuleFor(x => x.Title, f => f.Lorem.Word());
RuleFor(x => x.CallSign, f => f.Lorem.Word());
RuleFor(x => x.VesselType, f => f.Lorem.Word());
RuleFor(x => x.Tonnage, f => f.Lorem.Word());
RuleFor(x => x.GrossRegisteredTonnage, f => f.Lorem.Word());
RuleFor(x => x.VesselFlag, f => f.Lorem.Word());
RuleFor(x => x.VesselOwner, f => f.Lorem.Word());
RuleFor(x => x.Remarks, f => f.Lorem.Word());
}
}
@@ -0,0 +1,77 @@
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using SanctionsSearch.Worker.Tests.Fakes;
namespace SanctionsSearch.Worker.Tests.Integration;
public class EfRepositoryTests : IAsyncLifetime
{
private readonly AppDbContext _context;
private readonly EfRepository<Sdn> _sdnRepository;
private readonly SdnFaker _sdnFaker = new();
public EfRepositoryTests()
{
var loggerFactory = LoggerFactory.Create(builder => builder.ClearProviders());
_context = new AppDbContext(new DbOptions { DatabaseName = $"{Guid.NewGuid()}.db" });
_sdnRepository = new EfRepository<Sdn>(_context, loggerFactory.CreateLogger<EfRepository<Sdn>>());
}
[Fact]
public async Task Upsert_WithNewEntity_ShouldAddEntityToDatabase()
{
var sdn = _sdnFaker.Generate();
await _sdnRepository.Upsert(sdn);
await _context.SaveChangesAsync();
var result = await _context.Sdns.FindAsync(sdn.Id);
result.Should().BeEquivalentTo(sdn);
result!.CreatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
result.UpdatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
}
[Fact]
public async Task Upsert_WithExistingEntity_ShouldUpdateEntityInDatabase()
{
var sdn = _sdnFaker.Generate();
await _sdnRepository.Upsert(sdn);
await _context.SaveChangesAsync();
var createdSdn = await _context.Sdns.FindAsync(sdn.Id);
createdSdn.Should().BeEquivalentTo(sdn);
createdSdn!.CreatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
createdSdn.UpdatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
var updatedSdn = _sdnFaker.Generate();
updatedSdn.Id = sdn.Id;
await _sdnRepository.Upsert(updatedSdn);
await _context.SaveChangesAsync();
var result = await _context.Sdns.FindAsync(sdn.Id);
result.Should().BeEquivalentTo(updatedSdn);
result!.CreatedAt.Should().BeSameDateAs(createdSdn.CreatedAt);
result.UpdatedAt.Should().BeAfter(createdSdn.UpdatedAt);
}
public async Task InitializeAsync()
{
await _context.Database.MigrateAsync();
}
public async Task DisposeAsync()
{
await _context.DisposeAsync();
SqliteConnection.ClearAllPools();
File.Delete(_context.DatabasePath);
}
}
@@ -9,6 +9,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Bogus" Version="35.6.0" />
<PackageReference Include="FluentAssertions" Version="6.12.0" /> <PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="Moq" Version="4.20.70" /> <PackageReference Include="Moq" Version="4.20.70" />
+3 -1
View File
@@ -8,4 +8,6 @@ global using Moq;
global using RichardSzalay.MockHttp; global using RichardSzalay.MockHttp;
global using SanctionsSearch.Worker.Services; global using SanctionsSearch.Worker.Services;
global using SanctionsSearch.Worker.Options; global using SanctionsSearch.Worker.Options;
global using SanctionsSearch.Worker.Models;
global using SanctionsSearch.Worker.Persistence;
@@ -0,0 +1,7 @@
namespace SanctionsSearch.Worker.Interfaces;
interface IRepository<T> where T : Entity
{
Task Upsert(T entity);
Task<IEnumerable<T>> Find(Expression<Func<T, bool>> predicate);
}
@@ -0,0 +1,5 @@
namespace SanctionsSearch.Worker.Interfaces;
interface ISdnRepository : IRepository<Sdn>
{
}
@@ -0,0 +1,7 @@
namespace SanctionsSearch.Worker.Interfaces;
interface IUnitOfWork
{
ISdnRepository Sdns { get; }
Task SaveChangesAsync();
}
@@ -0,0 +1,222 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using SanctionsSearch.Worker.Persistence;
#nullable disable
namespace SanctionsSearch.Worker.Migrations
{
[DbContext(typeof(AppDbContext))]
[Migration("20240821035135_update_models_with_timestamps")]
partial class update_models_with_timestamps
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.8");
modelBuilder.Entity("SanctionsSearch.Worker.Models.Address", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("CityProvincePostal")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Country")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<string>("Remarks")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("SdnId")
.HasColumnType("INTEGER");
b.Property<string>("StreetAddress")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("SdnId");
b.ToTable("Addresses");
});
modelBuilder.Entity("SanctionsSearch.Worker.Models.Alias", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Remarks")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("SdnId")
.HasColumnType("INTEGER");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("SdnId");
b.ToTable("Aliases");
});
modelBuilder.Entity("SanctionsSearch.Worker.Models.Comment", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<string>("Remarks")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("SdnId")
.HasColumnType("INTEGER");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("SdnId");
b.ToTable("Comments");
});
modelBuilder.Entity("SanctionsSearch.Worker.Models.Sdn", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("CallSign")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<string>("GrossRegisteredTonnage")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Program")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Remarks")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Tonnage")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("TEXT");
b.Property<string>("VesselFlag")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("VesselOwner")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("VesselType")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Sdns");
});
modelBuilder.Entity("SanctionsSearch.Worker.Models.Address", b =>
{
b.HasOne("SanctionsSearch.Worker.Models.Sdn", "Sdn")
.WithMany()
.HasForeignKey("SdnId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Sdn");
});
modelBuilder.Entity("SanctionsSearch.Worker.Models.Alias", b =>
{
b.HasOne("SanctionsSearch.Worker.Models.Sdn", "Sdn")
.WithMany("Aliases")
.HasForeignKey("SdnId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Sdn");
});
modelBuilder.Entity("SanctionsSearch.Worker.Models.Comment", b =>
{
b.HasOne("SanctionsSearch.Worker.Models.Sdn", "Sdn")
.WithMany()
.HasForeignKey("SdnId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Sdn");
});
modelBuilder.Entity("SanctionsSearch.Worker.Models.Sdn", b =>
{
b.Navigation("Aliases");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,107 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace SanctionsSearch.Worker.Migrations
{
/// <inheritdoc />
public partial class update_models_with_timestamps : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<DateTime>(
name: "CreatedAt",
table: "Sdns",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "UpdatedAt",
table: "Sdns",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "CreatedAt",
table: "Comments",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "UpdatedAt",
table: "Comments",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "CreatedAt",
table: "Aliases",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "UpdatedAt",
table: "Aliases",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "CreatedAt",
table: "Addresses",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "UpdatedAt",
table: "Addresses",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "CreatedAt",
table: "Sdns");
migrationBuilder.DropColumn(
name: "UpdatedAt",
table: "Sdns");
migrationBuilder.DropColumn(
name: "CreatedAt",
table: "Comments");
migrationBuilder.DropColumn(
name: "UpdatedAt",
table: "Comments");
migrationBuilder.DropColumn(
name: "CreatedAt",
table: "Aliases");
migrationBuilder.DropColumn(
name: "UpdatedAt",
table: "Aliases");
migrationBuilder.DropColumn(
name: "CreatedAt",
table: "Addresses");
migrationBuilder.DropColumn(
name: "UpdatedAt",
table: "Addresses");
}
}
}
@@ -1,4 +1,5 @@
// <auto-generated /> // <auto-generated />
using System;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
@@ -30,6 +31,9 @@ namespace SanctionsSearch.Worker.Migrations
.IsRequired() .IsRequired()
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<string>("Remarks") b.Property<string>("Remarks")
.IsRequired() .IsRequired()
.HasColumnType("TEXT"); .HasColumnType("TEXT");
@@ -41,6 +45,9 @@ namespace SanctionsSearch.Worker.Migrations
.IsRequired() .IsRequired()
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("TEXT");
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("SdnId"); b.HasIndex("SdnId");
@@ -54,6 +61,9 @@ namespace SanctionsSearch.Worker.Migrations
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<string>("Name") b.Property<string>("Name")
.IsRequired() .IsRequired()
.HasColumnType("TEXT"); .HasColumnType("TEXT");
@@ -69,6 +79,9 @@ namespace SanctionsSearch.Worker.Migrations
.IsRequired() .IsRequired()
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("TEXT");
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("SdnId"); b.HasIndex("SdnId");
@@ -82,6 +95,9 @@ namespace SanctionsSearch.Worker.Migrations
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<string>("Remarks") b.Property<string>("Remarks")
.IsRequired() .IsRequired()
.HasColumnType("TEXT"); .HasColumnType("TEXT");
@@ -89,6 +105,9 @@ namespace SanctionsSearch.Worker.Migrations
b.Property<int>("SdnId") b.Property<int>("SdnId")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("TEXT");
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("SdnId"); b.HasIndex("SdnId");
@@ -106,6 +125,9 @@ namespace SanctionsSearch.Worker.Migrations
.IsRequired() .IsRequired()
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<string>("GrossRegisteredTonnage") b.Property<string>("GrossRegisteredTonnage")
.IsRequired() .IsRequired()
.HasColumnType("TEXT"); .HasColumnType("TEXT");
@@ -134,6 +156,9 @@ namespace SanctionsSearch.Worker.Migrations
.IsRequired() .IsRequired()
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("TEXT");
b.Property<string>("VesselFlag") b.Property<string>("VesselFlag")
.IsRequired() .IsRequired()
.HasColumnType("TEXT"); .HasColumnType("TEXT");
+1 -2
View File
@@ -1,9 +1,8 @@
namespace SanctionsSearch.Worker.Models; namespace SanctionsSearch.Worker.Models;
class Address class Address : Entity
{ {
public int SdnId { get; set; } public int SdnId { get; set; }
public int Id { get; set; }
public string StreetAddress { get; set; } = string.Empty; public string StreetAddress { get; set; } = string.Empty;
public string CityProvincePostal { get; set; } = string.Empty; public string CityProvincePostal { get; set; } = string.Empty;
public string Country { get; set; } = string.Empty; public string Country { get; set; } = string.Empty;
+1 -2
View File
@@ -1,9 +1,8 @@
namespace SanctionsSearch.Worker.Models; namespace SanctionsSearch.Worker.Models;
class Alias class Alias : Entity
{ {
public int SdnId { get; set; } public int SdnId { get; set; }
public int Id { get; set; }
public string Type { get; set; } = string.Empty; public string Type { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty; public string Name { get; set; } = string.Empty;
public string Remarks { get; set; } = string.Empty; public string Remarks { get; set; } = string.Empty;
+1 -2
View File
@@ -1,9 +1,8 @@
namespace SanctionsSearch.Worker.Models; namespace SanctionsSearch.Worker.Models;
class Comment class Comment : Entity
{ {
public int SdnId { get; set; } public int SdnId { get; set; }
public int Id { get; set; }
public string Remarks { get; set; } = string.Empty; public string Remarks { get; set; } = string.Empty;
public virtual Sdn Sdn { get; set; } = default!; public virtual Sdn Sdn { get; set; } = default!;
@@ -0,0 +1,8 @@
namespace SanctionsSearch.Worker.Models;
abstract class Entity
{
public int Id { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
}
+1 -2
View File
@@ -1,8 +1,7 @@
namespace SanctionsSearch.Worker.Models; namespace SanctionsSearch.Worker.Models;
class Sdn class Sdn : Entity
{ {
public int Id { get; set; }
public string Name { get; set; } = string.Empty; public string Name { get; set; } = string.Empty;
public string Type { get; set; } = string.Empty; public string Type { get; set; } = string.Empty;
public string Program { get; set; } = string.Empty; public string Program { get; set; } = string.Empty;
@@ -1,13 +1,10 @@
using System.Reflection;
using Microsoft.Data.Sqlite;
namespace SanctionsSearch.Worker.Persistence; namespace SanctionsSearch.Worker.Persistence;
class AppDbContext(IOptionsSnapshot<DbOptions> options) : DbContext, IDisposable class AppDbContext(DbOptions options) : DbContext
{ {
private readonly IOptionsSnapshot<DbOptions> _options = options; private readonly DbOptions _options = options;
private SqliteConnection? _connection; private SqliteConnection? _connection;
internal string DatabasePath { get; private set; } = string.Empty;
public DbSet<Sdn> Sdns { get; set; } = default!; public DbSet<Sdn> Sdns { get; set; } = default!;
public DbSet<Address> Addresses { get; set; } = default!; public DbSet<Address> Addresses { get; set; } = default!;
public DbSet<Alias> Aliases { get; set; } = default!; public DbSet<Alias> Aliases { get; set; } = default!;
@@ -24,7 +21,8 @@ class AppDbContext(IOptionsSnapshot<DbOptions> options) : DbContext, IDisposable
Directory.CreateDirectory(dataDir); Directory.CreateDirectory(dataDir);
} }
var dbPath = Path.Combine(dataDir, _options.Value.DatabaseName); var dbPath = Path.Combine(dataDir, _options.DatabaseName);
DatabasePath = dbPath;
_connection = new SqliteConnection($"Data Source={dbPath}"); _connection = new SqliteConnection($"Data Source={dbPath}");
_connection.Open(); _connection.Open();
@@ -32,9 +30,40 @@ class AppDbContext(IOptionsSnapshot<DbOptions> options) : DbContext, IDisposable
optionsBuilder.UseSqlite(_connection); optionsBuilder.UseSqlite(_connection);
} }
public override void Dispose() public async override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{ {
_connection?.Dispose(); // TODO: Use TimeProvider instead of DateTime.UtcNow
base.Dispose(); var now = DateTime.UtcNow;
foreach (var changedEntity in ChangeTracker.Entries())
{
if (changedEntity.Entity is Entity entity)
{
switch (changedEntity.State)
{
case EntityState.Added:
entity.CreatedAt = now;
entity.UpdatedAt = now;
break;
case EntityState.Modified:
Entry(entity).Property(x => x.CreatedAt).IsModified = false;
entity.UpdatedAt = now;
break;
}
}
}
return await base.SaveChangesAsync(cancellationToken);
}
public async override ValueTask DisposeAsync()
{
if (_connection is not null)
{
await _connection.DisposeAsync();
}
await base.DisposeAsync();
} }
} }
@@ -0,0 +1,46 @@
namespace SanctionsSearch.Worker.Persistence;
class EfRepository<T> : IRepository<T> where T : Entity
{
protected readonly AppDbContext _context;
protected readonly ILogger<EfRepository<T>> _logger;
public EfRepository(AppDbContext context, ILogger<EfRepository<T>> logger)
{
_context = context;
_logger = logger;
}
public async Task<IEnumerable<T>> Find(Expression<Func<T, bool>> predicate)
{
try
{
return await _context.Set<T>().Where(predicate).ToListAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error finding entities of type {Type}", typeof(T).Name);
return [];
}
}
public async Task Upsert(T entity)
{
try
{
var existing = await _context.Set<T>().FindAsync(entity.Id);
if (existing is null)
{
await _context.Set<T>().AddAsync(entity);
return;
}
_context.Entry(existing).CurrentValues.SetValues(entity);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error upserting entity of type {Type} with {Id}", typeof(T).Name, entity.Id);
}
}
}
@@ -0,0 +1,17 @@
namespace SanctionsSearch.Worker.Persistence;
class EfUnitOfWork(AppDbContext context, ILoggerFactory loggerFactory) : IUnitOfWork, IAsyncDisposable
{
private readonly AppDbContext _context = context;
public ISdnRepository Sdns { get; } = new SdnRepository(context, loggerFactory.CreateLogger<SdnRepository>());
public async Task SaveChangesAsync()
{
await _context.SaveChangesAsync();
}
public async ValueTask DisposeAsync()
{
await _context.DisposeAsync();
}
}
@@ -0,0 +1,8 @@
namespace SanctionsSearch.Worker.Persistence;
class SdnRepository(
AppDbContext dbContext,
ILogger<SdnRepository> logger
) : EfRepository<Sdn>(dbContext, logger), ISdnRepository
{
}
+1
View File
@@ -31,6 +31,7 @@ try
builder.Services.ConfigureOptions<OfacFileServiceOptionsSetup>(); builder.Services.ConfigureOptions<OfacFileServiceOptionsSetup>();
builder.Services.ConfigureOptions<DbOptionsSetup>(); builder.Services.ConfigureOptions<DbOptionsSetup>();
builder.Services.AddScoped(rs => rs.GetRequiredService<IOptionsSnapshot<DbOptions>>().Value);
builder.Services.AddDbContext<AppDbContext>(); builder.Services.AddDbContext<AppDbContext>();
builder.Services.AddHostedService<Worker>(); builder.Services.AddHostedService<Worker>();
+7 -1
View File
@@ -9,4 +9,10 @@ global using Serilog;
global using Serilog.Exceptions; global using Serilog.Exceptions;
global using Serilog.Formatting.Compact; global using Serilog.Formatting.Compact;
global using Microsoft.EntityFrameworkCore; global using Microsoft.EntityFrameworkCore;
global using System.Linq.Expressions;
global using System.Reflection;
global using Microsoft.Data.Sqlite;