Skip to content

Commit

Permalink
Refactor Repositories
Browse files Browse the repository at this point in the history
* Make all methods for datebase access async (append "Async" to name where missing)
* Fix bug when transaction fails
* Add MatchRepository.DeleteMatchResultAsync()
* Add more logging
  • Loading branch information
axunonb committed Jan 29, 2024
1 parent 4fdb7d6 commit d9f3fbb
Show file tree
Hide file tree
Showing 22 changed files with 241 additions and 233 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public async Task Generate_Schedule_Should_Succeed()
Assert.Multiple(() =>
{
Assert.That(_excludeMatchDays, Has.Count.EqualTo(1));
Assert.That(_excludeMatchDays[0].TournamentId, Is.EqualTo(1));
Assert.That(_excludeMatchDays[0].DateFrom, Is.EqualTo(new DateTime(2024, 1, 1)));
Assert.That(_excludeMatchDays[0].DateTo, Is.EqualTo(new DateTime(2024, 1, 1).AddDays(1).AddMinutes(-1)));
Assert.That(_excludeMatchDays[0].Reason, Is.EqualTo("Any Reason"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ private MatchScheduler GetMatchSchedulerInstance()
rep.AnyCompleteMatchesExistAsync(It.IsAny<long>(), It.IsAny<CancellationToken>()))
.Returns((long tournamentId, CancellationToken cancellationToken) => Task.FromResult(false));
matchRepoMock.Setup(rep =>
rep.GetMatches(It.IsAny<long>(), It.IsAny<CancellationToken>()))
rep.GetMatchesAsync(It.IsAny<long>(), It.IsAny<CancellationToken>()))
.Returns((long tournamentId, CancellationToken cancellationToken) =>
{
return Task.FromResult(tournamentMatches);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace TournamentManager.Data;
/// </summary>
public class AvailableMatchDateRepository
{
private static readonly ILogger<AvailableMatchDateRepository> _logger = AppLogging.CreateLogger<AvailableMatchDateRepository>();
private readonly ILogger<AvailableMatchDateRepository> _logger = AppLogging.CreateLogger<AvailableMatchDateRepository>();
private readonly MultiTenancy.IDbContext _dbContext;
public AvailableMatchDateRepository(MultiTenancy.IDbContext dbContext)
{
Expand All @@ -34,7 +34,6 @@ public virtual async Task<EntityCollection<AvailableMatchDateEntity>> GetAvailab
FilterToUse = AvailableMatchDateFields.TournamentId == tournamentId
};
await da.FetchEntityCollectionAsync(qp, cancellationToken);
da.CloseConnection();

_logger.LogDebug("Fetched {count} available match dates for tournament {tournamentId}.", available.Count, tournamentId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace TournamentManager.Data;
/// </summary>
public class ExcludedMatchDateRepository
{
private static readonly ILogger _logger = AppLogging.CreateLogger<ExcludedMatchDateRepository>();
private readonly ILogger _logger = AppLogging.CreateLogger<ExcludedMatchDateRepository>();
private readonly MultiTenancy.IDbContext _dbContext;
public ExcludedMatchDateRepository(MultiTenancy.IDbContext dbContext)
{
Expand All @@ -37,7 +37,6 @@ public virtual async Task<EntityCollection<ExcludeMatchDateEntity>> GetExcludedM
FilterToUse = ExcludeMatchDateFields.TournamentId == tournamentId
};
await da.FetchEntityCollectionAsync(qp, cancellationToken);
da.CloseConnection();

return excluded;
}
Expand Down
33 changes: 6 additions & 27 deletions TournamentManager/TournamentManager/Data/GenericRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,14 @@ namespace TournamentManager.Data;

public class GenericRepository
{
private static readonly ILogger _logger = AppLogging.CreateLogger<GenericRepository>();
private readonly ILogger _logger = AppLogging.CreateLogger<GenericRepository>();
private readonly MultiTenancy.IDbContext _dbContext;

public GenericRepository(MultiTenancy.IDbContext dbContext)
{
_dbContext = dbContext;
}

public virtual bool SaveEntity<T>(T entityToSave, bool refetchAfterSave, bool recurse) where T:IEntity2
{
var transactionName = Guid.NewGuid().ToString("N");
using var da = _dbContext.GetNewAdapter();
try
{
da.StartTransaction(IsolationLevel.ReadCommitted, transactionName);
var success = da.SaveEntity(entityToSave, refetchAfterSave, recurse);
da.Commit();
_logger.LogDebug("Entity of type {type} saved.", typeof(T));
return success;
}
catch (Exception)
{
if (da.IsTransactionInProgress)
da.Rollback();

da.CloseConnection();
throw;
}
}


public virtual async Task<bool> SaveEntityAsync<T>(T entityToSave, bool refetchAfterSave, bool recurse, CancellationToken cancellationToken) where T : IEntity2
{
var transactionName = Guid.NewGuid().ToString("N");
Expand All @@ -44,15 +22,16 @@ public virtual async Task<bool> SaveEntityAsync<T>(T entityToSave, bool refetchA
{
await da.StartTransactionAsync(IsolationLevel.ReadCommitted, transactionName, cancellationToken);
var success = await da.SaveEntityAsync(entityToSave, refetchAfterSave, recurse, cancellationToken);
da.Commit();
await da.CommitAsync(cancellationToken);
return success;
}
catch (Exception)
catch (Exception e)
{
_logger.LogCritical(e, "Error saving entity in transaction: {entity}", entityToSave);

if (da.IsTransactionInProgress)
da.Rollback();

da.CloseConnection();
throw;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace TournamentManager.Data;
/// </summary>
public class ManagerOfTeamRepository
{
private static readonly ILogger _logger = AppLogging.CreateLogger<ManagerOfTeamRepository>();
private readonly ILogger _logger = AppLogging.CreateLogger<ManagerOfTeamRepository>();
private readonly MultiTenancy.IDbContext _dbContext;

public ManagerOfTeamRepository(MultiTenancy.IDbContext dbContext)
Expand Down
137 changes: 84 additions & 53 deletions TournamentManager/TournamentManager/Data/MatchRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,20 @@
using TournamentManager.DAL.HelperClasses;
using TournamentManager.DAL.Linq;
using TournamentManager.DAL.TypedViewClasses;
using TournamentManager.MultiTenancy;

namespace TournamentManager.Data;

public class MatchRepository
{
private static readonly ILogger _logger = AppLogging.CreateLogger<MatchRepository>();
private readonly ILogger _logger = AppLogging.CreateLogger<MatchRepository>();
private readonly MultiTenancy.IDbContext _dbContext;
private readonly IAppDb _appDb;

public MatchRepository(MultiTenancy.IDbContext dbContext)
{
_dbContext = dbContext;
_appDb = _dbContext.AppDb;
}

public virtual async Task<List<CompletedMatchRow>> GetCompletedMatchesAsync(IPredicateExpression filter,
Expand Down Expand Up @@ -105,9 +108,9 @@ public virtual async Task<List<CalendarRow>> GetMatchCalendarAsync(long tourname
new QueryFactory().Calendar.Where(filter), cancellationToken));
}

public virtual async Task<EntityCollection<MatchEntity>> GetMatches(long tournamentId, CancellationToken cancellationToken)
public virtual async Task<EntityCollection<MatchEntity>> GetMatchesAsync(long tournamentId, CancellationToken cancellationToken)
{
var rounds = new TournamentRepository(_dbContext).GetTournamentRounds(tournamentId);
var rounds = await _appDb.TournamentRepository.GetTournamentRoundsAsync(tournamentId, cancellationToken);

var roundId = new List<long>(rounds.Count);
roundId.AddRange(rounds.Select(round => round.Id));
Expand All @@ -126,12 +129,10 @@ public virtual async Task<EntityCollection<MatchEntity>> GetMatches(long tournam
};

await da.FetchEntityCollectionAsync(qp, cancellationToken);
da.CloseConnection();

return matches;
}

public virtual async Task<EntityCollection<MatchEntity>> GetMatches(RoundEntity round, CancellationToken cancellationToken)
public virtual async Task<EntityCollection<MatchEntity>> GetMatchesAsync(RoundEntity round, CancellationToken cancellationToken)
{
IPredicateExpression roundFilter =
new PredicateExpression(new FieldCompareRangePredicate(MatchFields.RoundId, null, false,
Expand All @@ -147,37 +148,10 @@ public virtual async Task<EntityCollection<MatchEntity>> GetMatches(RoundEntity
};

await da.FetchEntityCollectionAsync(qp, cancellationToken);
da.CloseConnection();

return matches;
}

public virtual RoundLegEntity? GetLeg(MatchEntity match)
{
// if leg does not exist or no match.SequenceNo: result will be NULL!

if (!match.LegSequenceNo.HasValue)
return null;

if (match.Round is { RoundLegs: not null })
{
return match.Round.RoundLegs.First(l => l.SequenceNo == match.LegSequenceNo);
}

using var da = _dbContext.GetNewAdapter();
{
var metaData = new LinqMetaData(da);
var q = from l in metaData.RoundLeg
where l.RoundId == match.RoundId && l.SequenceNo == match.LegSequenceNo
select l;

var result = q.First<RoundLegEntity>();
da.CloseConnection();
return result;
}
}


public virtual async Task<MatchEntity?> GetMatchWithSetsAsync(long? matchId, CancellationToken cancellationToken)
{
if (matchId == null) return null;
Expand Down Expand Up @@ -286,12 +260,9 @@ public virtual async Task<bool> AnyCompleteMatchesExistAsync(long tournamentId,
where matchEntity.Round.TournamentId == tournamentId && matchEntity.IsComplete
select matchEntity.Id).Take(1).ToListAsync(cancellationToken)).Count != 0)
{
da.CloseConnection();
return true;
}

da.CloseConnection();

return false;
}

Expand All @@ -310,12 +281,9 @@ public virtual async Task<bool> AnyCompleteMatchesExistAsync(RoundEntity round,
where matchEntity.Round.Id == round.Id && matchEntity.IsComplete
select matchEntity.Id).Take(1).ToListAsync(cancellationToken)).Count != 0)
{
da.CloseConnection();
return true;
}

da.CloseConnection();

return false;
}

Expand All @@ -332,12 +300,9 @@ public virtual async Task<bool> AllMatchesCompletedAsync(TournamentEntity tourna
where matchEntity.Round.TournamentId == tournament.Id && !matchEntity.IsComplete
select matchEntity.Id).Take(1).ToListAsync(cancellationToken)).Count == 0)
{
da.CloseConnection();
return true;
}

da.CloseConnection();

return false;
}

Expand All @@ -354,12 +319,9 @@ public virtual async Task<bool> AllMatchesCompletedAsync(RoundEntity round, Canc
where matchEntity.Round.Id == round.Id && !matchEntity.IsComplete
select matchEntity.Id).Take(1).ToListAsync(cancellationToken)).Count == 0)
{
da.CloseConnection();
return true;
}

da.CloseConnection();

return false;
}

Expand All @@ -373,17 +335,86 @@ public virtual async Task<bool> AllMatchesCompletedAsync(RoundEntity round, Canc
public virtual async Task<bool> SaveMatchResultAsync(MatchEntity matchEntity, CancellationToken cancellationToken)
{
using var da = _dbContext.GetNewAdapter();
await da.StartTransactionAsync(IsolationLevel.ReadCommitted,
string.Concat(nameof(MatchRepository), nameof(SaveMatchResultAsync), Guid.NewGuid().ToString()), cancellationToken);

if (matchEntity.Sets.RemovedEntitiesTracker != null)
try
{
await da.StartTransactionAsync(IsolationLevel.ReadCommitted,
string.Concat(nameof(MatchRepository), nameof(SaveMatchResultAsync), Guid.NewGuid().ToString()), cancellationToken);

if (matchEntity.Sets.RemovedEntitiesTracker != null)
{
await da.DeleteEntityCollectionAsync(matchEntity.Sets.RemovedEntitiesTracker, cancellationToken);
matchEntity.Sets.RemovedEntitiesTracker.Clear();
matchEntity.Sets.RemovedEntitiesTracker = null;
}

var success = await da.SaveEntityAsync(matchEntity, false, true, cancellationToken);
await da.CommitAsync(cancellationToken);
return success;
}
catch (Exception e)
{
await da.DeleteEntityCollectionAsync(matchEntity.Sets.RemovedEntitiesTracker, cancellationToken);
matchEntity.Sets.RemovedEntitiesTracker.Clear();
_logger.LogCritical(e, "Error saving in transaction: MatchId={matchId}", matchEntity.Id);

if (da.IsTransactionInProgress)
da.Rollback();

return false;
}
}

/// <summary>
/// Resets properties from <see cref="MatchEntity"/> that are connected to a match result,
/// and deletes all containing <see cref="SetEntity"/>s from the database, using a transaction.
/// </summary>
/// <param name="matchId">The <see cref="MatchEntity.Id"/>.</param>
/// <param name="cancellationToken"></param>
/// <returns><see langword="true"/>, if the operation was successful, else <see langword="false"/>. </returns>
/// <exception cref="ArgumentException"></exception>
public virtual async Task<bool> DeleteMatchResultAsync(long matchId, CancellationToken cancellationToken)
{
// Get the match with the sets
var matchEntity = await GetMatchWithSetsAsync(matchId, cancellationToken)
?? throw new ArgumentException(@"No match found", nameof(matchId));

matchEntity.HomePoints = matchEntity.GuestPoints = null;
matchEntity.Remarks = string.Empty;
matchEntity.IsOverruled = false;
matchEntity.IsComplete = false;

// Track the removed sets
matchEntity.Sets.RemovedEntitiesTracker = new EntityCollection<SetEntity>();
matchEntity.Sets.Clear();

using var da = _dbContext.GetNewAdapter();

try
{
await da.StartTransactionAsync(IsolationLevel.ReadCommitted,
string.Concat(nameof(MatchRepository), nameof(DeleteMatchResultAsync), Guid.NewGuid().ToString()), cancellationToken);

// Delete the sets
if (matchEntity.Sets.RemovedEntitiesTracker != null)
{
await da.DeleteEntityCollectionAsync(matchEntity.Sets.RemovedEntitiesTracker, cancellationToken);
matchEntity.Sets.RemovedEntitiesTracker.Clear();
matchEntity.Sets.RemovedEntitiesTracker = null;
}

// Save the changes to the match
var success = await da.SaveEntityAsync(matchEntity, false, true, cancellationToken);
await da.CommitAsync(cancellationToken);
return success;
}
catch (Exception e)
{
_logger.LogCritical(e, "Error saving entity in transaction: {MatchId}", matchId);

if (da.IsTransactionInProgress)
da.Rollback();

return false;
}

var success = await da.SaveEntityAsync(matchEntity, false, true, cancellationToken);
da.Commit();
return success;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace TournamentManager.Data;
/// </summary>
public class PlayerInTeamRepository
{
private static readonly ILogger _logger = AppLogging.CreateLogger<PlayerInTeamRepository>();
private readonly ILogger _logger = AppLogging.CreateLogger<PlayerInTeamRepository>();
private readonly MultiTenancy.IDbContext _dbContext;

public PlayerInTeamRepository(MultiTenancy.IDbContext dbContext)
Expand Down
Loading

0 comments on commit d9f3fbb

Please sign in to comment.