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
@@ -1,13 +1,10 @@
using System.Reflection;
using Microsoft.Data.Sqlite;
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;
internal string DatabasePath { get; private set; } = string.Empty;
public DbSet<Sdn> Sdns { get; set; } = default!;
public DbSet<Address> Addresses { get; set; } = default!;
public DbSet<Alias> Aliases { get; set; } = default!;
@@ -24,7 +21,8 @@ class AppDbContext(IOptionsSnapshot<DbOptions> options) : DbContext, IDisposable
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.Open();
@@ -32,9 +30,40 @@ class AppDbContext(IOptionsSnapshot<DbOptions> options) : DbContext, IDisposable
optionsBuilder.UseSqlite(_connection);
}
public override void Dispose()
public async override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
_connection?.Dispose();
base.Dispose();
// TODO: Use TimeProvider instead of DateTime.UtcNow
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
{
}