diff --git a/Directory.Build.props b/Directory.Build.props
index e8af0ce..ba4b6de 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -18,6 +18,8 @@
latest
strict
$(NoWarn);1591
+
+ false
diff --git a/League/League.csproj b/League/League.csproj
index 8ffd7d6..cd3e59d 100644
--- a/League/League.csproj
+++ b/League/League.csproj
@@ -20,8 +20,6 @@ Localizations for English and German are included. The library is in operation o
true
false
true
-
- false
en;de
enable
diff --git a/TournamentManager/DAL/Code/Markdown/EntityModel/_DefaultGroup/Entities/Tournament.md b/TournamentManager/DAL/Code/Markdown/EntityModel/_DefaultGroup/Entities/Tournament.md
index 5bc47c2..7643124 100644
--- a/TournamentManager/DAL/Code/Markdown/EntityModel/_DefaultGroup/Entities/Tournament.md
+++ b/TournamentManager/DAL/Code/Markdown/EntityModel/_DefaultGroup/Entities/Tournament.md
@@ -17,7 +17,7 @@ Related Entity | Full description
[Ranking](../../_DefaultGroup/Entities/Ranking.htm) | Ranking.Tournament - Tournament.Rankings (m:1)
[Registration](../../_DefaultGroup/Entities/Registration.htm) | Registration.Tournament - Tournament.Registrations (m:1)
[Round](../../_DefaultGroup/Entities/Round.htm) | Round.Tournament - Tournament.Rounds (m:1)
-[Tournament](../../_DefaultGroup/Entities/Tournament.htm) | Tournament.Tournament - Tournament.Tournaments (m:1)
+[Tournament](../../_DefaultGroup/Entities/Tournament.htm) | Tournament.NextTournament - Tournament.Tournaments (m:1)
[TournamentType](../../_DefaultGroup/Entities/TournamentType.htm) | Tournament.TournamentType - TournamentType.Tournaments (m:1)
## Fields
@@ -139,22 +139,22 @@ Setting name | Value
--|--
Navigator property is public | True
-#### Rankings (NavigatorCollection)
+#### NextTournament (NavigatorSingleValue)
Setting name | Value
--|--
Navigator property is public | True
-#### Registrations (NavigatorCollection)
+#### Rankings (NavigatorCollection)
Setting name | Value
--|--
Navigator property is public | True
-#### Rounds (NavigatorCollection)
+#### Registrations (NavigatorCollection)
Setting name | Value
--|--
Navigator property is public | True
-#### Tournament (NavigatorSingleValue)
+#### Rounds (NavigatorCollection)
Setting name | Value
--|--
Navigator property is public | True
@@ -171,7 +171,7 @@ Navigator property is public | True
### Attribute definitions per element
-#### Tournament (NavigatorSingleValue)
+#### NextTournament (NavigatorSingleValue)
* `Browsable($false)`
diff --git a/TournamentManager/DAL/Code/Markdown/generalinformation.md b/TournamentManager/DAL/Code/Markdown/generalinformation.md
index 93d6376..741d530 100644
--- a/TournamentManager/DAL/Code/Markdown/generalinformation.md
+++ b/TournamentManager/DAL/Code/Markdown/generalinformation.md
@@ -4,7 +4,7 @@
This information is generated from the project `TournamentManager`, using the file `D:\Internet\Websites\League\Src\TournamentManager\DAL\TournamentManager.llblgenproj`.
--|--
-Generated on | 20-Apr-2024
+Generated on | 31-Mai-2024
Using LLBLGen Pro version | LLBLGen Pro v5.11 RTM (v5.11.1)
Project creator | axuno gGmbH
Project name | TournamentManager
diff --git a/TournamentManager/DAL/Code/Markdown/index.md b/TournamentManager/DAL/Code/Markdown/index.md
index 41c2014..e6ec67e 100644
--- a/TournamentManager/DAL/Code/Markdown/index.md
+++ b/TournamentManager/DAL/Code/Markdown/index.md
@@ -2,6 +2,6 @@
TournamentManager Model Documentation
Generated by LLBLGen Pro v5.11
-Generated on 20-Apr-2024
+Generated on 31-Mai-2024
\ No newline at end of file
diff --git a/TournamentManager/DAL/DatabaseGeneric/EntityClasses/TournamentEntity.cs b/TournamentManager/DAL/DatabaseGeneric/EntityClasses/TournamentEntity.cs
index 8c2060d..0b50ca6 100644
--- a/TournamentManager/DAL/DatabaseGeneric/EntityClasses/TournamentEntity.cs
+++ b/TournamentManager/DAL/DatabaseGeneric/EntityClasses/TournamentEntity.cs
@@ -31,7 +31,7 @@ public partial class TournamentEntity : CommonEntityBase
private EntityCollection _registrations;
private EntityCollection _rounds;
private EntityCollection _tournaments;
- private TournamentEntity _tournament;
+ private TournamentEntity _nextTournament;
private TournamentTypeEntity _tournamentType;
// __LLBLGENPRO_USER_CODE_REGION_START PrivateMembers
@@ -42,8 +42,8 @@ public partial class TournamentEntity : CommonEntityBase
/// All names of fields mapped onto a relation. Usable for in-memory filtering
public static partial class MemberNames
{
- /// Member name Tournament
- public static readonly string Tournament = "Tournament";
+ /// Member name NextTournament
+ public static readonly string NextTournament = "NextTournament";
/// Member name TournamentType
public static readonly string TournamentType = "TournamentType";
/// Member name ExcludeMatchDates
@@ -69,7 +69,7 @@ public TournamentEntityStaticMetaData()
AddNavigatorMetaData>("Registrations", a => a._registrations, (a, b) => a._registrations = b, a => a.Registrations, () => new TournamentRelations().RegistrationEntityUsingTournamentId, typeof(RegistrationEntity), (int)TournamentManager.DAL.EntityType.RegistrationEntity);
AddNavigatorMetaData>("Rounds", a => a._rounds, (a, b) => a._rounds = b, a => a.Rounds, () => new TournamentRelations().RoundEntityUsingTournamentId, typeof(RoundEntity), (int)TournamentManager.DAL.EntityType.RoundEntity);
AddNavigatorMetaData>("Tournaments", a => a._tournaments, (a, b) => a._tournaments = b, a => a.Tournaments, () => new TournamentRelations().TournamentEntityUsingNextTournamentId, typeof(TournamentEntity), (int)TournamentManager.DAL.EntityType.TournamentEntity);
- AddNavigatorMetaData("Tournament", "Tournaments", (a, b) => a._tournament = b, a => a._tournament, (a, b) => a.Tournament = b, TournamentManager.DAL.RelationClasses.StaticTournamentRelations.TournamentEntityUsingIdNextTournamentIdStatic, ()=>new TournamentRelations().TournamentEntityUsingIdNextTournamentId, null, new int[] { (int)TournamentFieldIndex.NextTournamentId }, null, true, (int)TournamentManager.DAL.EntityType.TournamentEntity);
+ AddNavigatorMetaData("NextTournament", "Tournaments", (a, b) => a._nextTournament = b, a => a._nextTournament, (a, b) => a.NextTournament = b, TournamentManager.DAL.RelationClasses.StaticTournamentRelations.TournamentEntityUsingIdNextTournamentIdStatic, ()=>new TournamentRelations().TournamentEntityUsingIdNextTournamentId, null, new int[] { (int)TournamentFieldIndex.NextTournamentId }, null, true, (int)TournamentManager.DAL.EntityType.TournamentEntity);
AddNavigatorMetaData("TournamentType", "Tournaments", (a, b) => a._tournamentType = b, a => a._tournamentType, (a, b) => a.TournamentType = b, TournamentManager.DAL.RelationClasses.StaticTournamentRelations.TournamentTypeEntityUsingTypeIdStatic, ()=>new TournamentRelations().TournamentTypeEntityUsingTypeId, null, new int[] { (int)TournamentFieldIndex.TypeId }, null, true, (int)TournamentManager.DAL.EntityType.TournamentTypeEntity);
}
}
@@ -145,7 +145,7 @@ protected TournamentEntity(SerializationInfo info, StreamingContext context) : b
/// Creates a new IRelationPredicateBucket object which contains the predicate expression and relation collection to fetch the related entity of type 'Tournament' to this entity.
///
- public virtual IRelationPredicateBucket GetRelationInfoTournament() { return CreateRelationInfoForNavigator("Tournament"); }
+ public virtual IRelationPredicateBucket GetRelationInfoNextTournament() { return CreateRelationInfoForNavigator("NextTournament"); }
/// Creates a new IRelationPredicateBucket object which contains the predicate expression and relation collection to fetch the related entity of type 'TournamentType' to this entity.
///
@@ -203,7 +203,7 @@ private void InitClassEmpty(IValidator validator, IEntityFields2 fields)
/// Creates a new PrefetchPathElement2 object which contains all the information to prefetch the related entities of type 'Tournament' for this entity.
/// Ready to use IPrefetchPathElement2 implementation.
- public static IPrefetchPathElement2 PrefetchPathTournament { get { return _staticMetaData.GetPrefetchPathElement("Tournament", CommonEntityBase.CreateEntityCollection()); } }
+ public static IPrefetchPathElement2 PrefetchPathNextTournament { get { return _staticMetaData.GetPrefetchPathElement("NextTournament", CommonEntityBase.CreateEntityCollection()); } }
/// Creates a new PrefetchPathElement2 object which contains all the information to prefetch the related entities of type 'TournamentType' for this entity.
/// Ready to use IPrefetchPathElement2 implementation.
@@ -299,14 +299,14 @@ public virtual System.DateTime ModifiedOn
/// Gets the EntityCollection with the related entities of type 'TournamentEntity' which are related to this entity via a relation of type '1:n'. If the EntityCollection hasn't been fetched yet, the collection returned will be empty.
[TypeContainedAttribute(typeof(TournamentEntity))]
- public virtual EntityCollection Tournaments { get { return GetOrCreateEntityCollection("Tournament", true, false, ref _tournaments); } }
+ public virtual EntityCollection Tournaments { get { return GetOrCreateEntityCollection("NextTournament", true, false, ref _tournaments); } }
/// Gets / sets related entity of type 'TournamentEntity' which has to be set using a fetch action earlier. If no related entity is set for this property, null is returned..
[Browsable(false)]
- public virtual TournamentEntity Tournament
+ public virtual TournamentEntity NextTournament
{
- get { return _tournament; }
- set { SetSingleRelatedEntityNavigator(value, "Tournament"); }
+ get { return _nextTournament; }
+ set { SetSingleRelatedEntityNavigator(value, "NextTournament"); }
}
/// Gets / sets related entity of type 'TournamentTypeEntity' which has to be set using a fetch action earlier. If no related entity is set for this property, null is returned..
@@ -388,7 +388,7 @@ public virtual IEntityRelation TournamentEntityUsingNextTournamentId
/// Returns a new IEntityRelation object, between TournamentEntity and TournamentEntity over the m:1 relation they have, using the relation between the fields: Tournament.NextTournamentId - Tournament.Id
public virtual IEntityRelation TournamentEntityUsingIdNextTournamentId
{
- get { return ModelInfoProviderSingleton.GetInstance().CreateRelation(RelationType.ManyToOne, "Tournament", false, new[] { TournamentFields.Id, TournamentFields.NextTournamentId }); }
+ get { return ModelInfoProviderSingleton.GetInstance().CreateRelation(RelationType.ManyToOne, "NextTournament", false, new[] { TournamentFields.Id, TournamentFields.NextTournamentId }); }
}
/// Returns a new IEntityRelation object, between TournamentEntity and TournamentTypeEntity over the m:1 relation they have, using the relation between the fields: Tournament.TypeId - TournamentType.Id
diff --git a/TournamentManager/DAL/TournamentManager.llblgenproj b/TournamentManager/DAL/TournamentManager.llblgenproj
index e060825..4842e41 100644
--- a/TournamentManager/DAL/TournamentManager.llblgenproj
+++ b/TournamentManager/DAL/TournamentManager.llblgenproj
@@ -743,7 +743,7 @@
-
+
diff --git a/TournamentManager/TournamentManager/Data/TournamentRepository.cs b/TournamentManager/TournamentManager/Data/TournamentRepository.cs
index 1121398..f030881 100644
--- a/TournamentManager/TournamentManager/Data/TournamentRepository.cs
+++ b/TournamentManager/TournamentManager/Data/TournamentRepository.cs
@@ -1,3 +1,4 @@
+using System.Data;
using Microsoft.Extensions.Logging;
using SD.LLBLGen.Pro.LinqSupportClasses;
using SD.LLBLGen.Pro.ORMSupportClasses;
@@ -52,7 +53,6 @@ public TournamentRepository(MultiTenancy.IDbContext dbContext)
public virtual async Task> GetTournamentRoundsAsync(long tournamentId, CancellationToken cancellationToken)
{
using var da = _dbContext.GetNewAdapter();
- //var selectedRounds = new EntityCollection();
var metaData = new LinqMetaData(da);
var q = await (from r in metaData.Round
@@ -63,6 +63,18 @@ public virtual async Task> GetTournamentRoundsAsyn
return result;
}
+ public virtual async Task> GetTournamentRoundIdsAsync(long tournamentId, CancellationToken cancellationToken)
+ {
+ using var da = _dbContext.GetNewAdapter();
+ var metaData = new LinqMetaData(da);
+
+ var result = await (from r in metaData.Round
+ where r.TournamentId == tournamentId
+ select r.Id).ToListAsync(cancellationToken);
+
+ return result;
+ }
+
public virtual async Task GetTournamentEntityForMatchSchedulerAsync(long tournamentId, CancellationToken cancellationToken)
{
var bucket = new RelationPredicateBucket(TournamentFields.Id == tournamentId);
@@ -87,4 +99,42 @@ public virtual async Task> GetTournamentRoundsAsyn
await da.FetchEntityCollectionAsync(qp, cancellationToken);
return t.FirstOrDefault();
}
+
+ internal virtual async Task SaveTournamentsAsync(TournamentEntity sourceTournament, TournamentEntity targetTournament, CancellationToken cancellationToken)
+ {
+ using var da = _dbContext.GetNewAdapter();
+
+ try
+ {
+ await da.StartTransactionAsync(IsolationLevel.ReadCommitted,
+ string.Concat(nameof(SaveTournamentsAsync), Guid.NewGuid().ToString("N")), cancellationToken);
+
+ await da.SaveEntityAsync(targetTournament, true, true, cancellationToken);
+
+ if (sourceTournament.NextTournamentId is null)
+ {
+ sourceTournament.NextTournamentId = targetTournament.Id;
+ sourceTournament.ModifiedOn = sourceTournament.ModifiedOn;
+ await da.SaveEntityAsync(sourceTournament, true, false, cancellationToken);
+ _logger.LogDebug("{Property} set to {NextTournamentId}", nameof(TournamentEntity.NextTournamentId), sourceTournament.NextTournamentId);
+ }
+ else
+ {
+ _logger.LogDebug("{Property} was already set to {NextTournamentId}", nameof(TournamentEntity.NextTournamentId), sourceTournament.NextTournamentId);
+ }
+
+ await da.CommitAsync(cancellationToken);
+ return true;
+ }
+ catch (Exception e)
+ {
+ _logger.LogCritical(e, "Error saving transaction for tournament IDs {TargetId} and {SourceId}",
+ targetTournament.Id, sourceTournament.Id);
+
+ if (da.IsTransactionInProgress)
+ da.Rollback();
+
+ return false;
+ }
+ }
}
diff --git a/TournamentManager/TournamentManager/TournamentCreator.cs b/TournamentManager/TournamentManager/TournamentCreator.cs
index ae7fda2..d0d0394 100644
--- a/TournamentManager/TournamentManager/TournamentCreator.cs
+++ b/TournamentManager/TournamentManager/TournamentCreator.cs
@@ -2,183 +2,221 @@
using SD.LLBLGen.Pro.ORMSupportClasses;
using TournamentManager.DAL.EntityClasses;
using TournamentManager.DAL.HelperClasses;
-using TournamentManager.Data;
using TournamentManager.MultiTenancy;
namespace TournamentManager;
///
-/// The Copy class is used to copy an existing tournament
-/// to a new one. Usually, the sequence is as follows:
-/// 1. Copy the tournament e.g. from id 10 to 11:
-/// Copy.Tournament(10, 11);
-/// 2. Copy the rounds of a tournament:
-/// Copy.Round(10, 11, null);
-/// (Rounds not needed can be given in the list of 3rd parameter)
-/// 3. Copy the teams of tournament
-/// and assign the teams to the rounds created in step 2:
-/// Copy.TeamsWithPersons(10, 11, null);
-/// (Teams not needed can be given in the list of 3rd parameter)
+/// The TournamentCreator class is responsible for copying an existing tournament to a new one.
+/// It provides methods to copy the tournament, rounds, and round legs from the source tournament to the target tournament.
///
public class TournamentCreator
{
- private readonly ILogger _logger = AppLogging.CreateLogger();
+ ///
+ /// Arguments for the method.
+ ///
+ /// The ID of the source tournament.
+ /// Try to set the source tournament as completed.
+ /// The name for the target tournament.
+ /// The description for the target tournament.
+ /// The leg dates to use for the target tournament round legs. The list cover maximum number of legs across akk rounds.
+ /// The to use for CreatedOn / ModifiedOn of entities.
+ public record CopyTournamentArgs(
+ long SourceTournamentId,
+ bool SetSourceTournamentCompleted,
+ string TargetName,
+ string? TargetDescription,
+ IList<(DateTime Start, DateTime End)> TargetLegDates,
+ IList RoundsToExclude,
+ DateTime ModifiedOn);
+
+ private readonly ILogger _logger;
private readonly IAppDb _appDb;
+ private DateTime _modifiedOn;
private static TournamentCreator? _instance;
- private TournamentCreator(IAppDb appDb)
+
+ private TournamentCreator(IAppDb appDb, ILogger logger)
{
_appDb = appDb;
+ _logger = logger;
}
- public static TournamentCreator Instance(IAppDb appDb)
+ ///
+ /// Returns the singleton instance of the class.
+ ///
+ ///
+ ///
+ /// The singleton instance of the class.
+ public static TournamentCreator Instance(IAppDb appDb, ILogger logger)
{
- _instance ??= new TournamentCreator(appDb);
+ _instance ??= new TournamentCreator(appDb, logger);
return _instance;
}
///
- /// Copies the tournament basic data and the tournament leg data
- /// from the source to a new target tournament. The new tournament id must
- /// not exist. For start and end date of leg data 1 year is added.
+ /// Creates a new tournament from the source tournament.
+ ///
+ /// Definition of changes from source to new tournament.
+ ///
+ ///
+ /// var copyArgs = new TournamentCreator.CopyTournamentArgs(25, false, "Tournament 2024/25", null,
+ /// new List<(DateTime Start, DateTime End)> {
+ /// (new DateTime(2024, 9, 23), new DateTime(2025, 2, 1)), // 1st leg
+ /// (new DateTime(2025, 2, 3), new DateTime(2025, 5, 30)) // 2nd leg
+ /// }, Array.Empty<long>(), DateTime.UtcNow);
+ ///
+ /// var success = await TournamentCreator
+ /// .Instance(AppDb, AppLogging.CreateLogger<TournamentCreator>())
+ /// .CreateNewFromSourceTournament(copyArgs, CancellationToken.None);
+ ///
+ /// , if the new tournament was created successfully.
+ public async Task CreateNewFromSourceTournament(CopyTournamentArgs copyArgs, CancellationToken cancellationToken)
+ {
+ if (copyArgs.SetSourceTournamentCompleted) await SetTournamentCompleted(copyArgs.SourceTournamentId, cancellationToken);
+
+ var (sourceTournament, targetTournament) = await CopyTournament(copyArgs, cancellationToken);
+
+ var success = await CopyRoundsWithLegsToTarget(copyArgs, targetTournament, cancellationToken);
+
+ if (!success) return success;
+
+ try
+ {
+ await _appDb.TournamentRepository.SaveTournamentsAsync(sourceTournament, targetTournament, cancellationToken);
+ }
+ catch (Exception e)
+ {
+ _logger.LogCritical(e, "Error saving Tournaments from {TournamentCreator}", nameof(TournamentCreator));
+ return false;
+ }
+
+ return true;
+ }
+
+
+ ///
+ /// Copies the tournament basic data from the source to a new target tournament .
///
- /// Existing source tournament id.
- ///
- ///
+ /// The to be used.
///
- /// True, if creation was successful, false otherwise.
- public async Task CopyTournament (long fromTournamentId, string newName, string? newDescription, CancellationToken cancellationToken)
+ /// A with the Source (unchanged) and the new Target .
+ internal async Task<(TournamentEntity Source, TournamentEntity Target)> CopyTournament (CopyTournamentArgs copyArgs, CancellationToken cancellationToken)
{
- var now = DateTime.UtcNow;
- var fromTournament =
+ _modifiedOn = copyArgs.ModifiedOn;
+ var sourceTournament =
await _appDb.TournamentRepository.GetTournamentAsync(
- new PredicateExpression(TournamentFields.Id == fromTournamentId), CancellationToken.None)
- ?? throw new InvalidOperationException($"'{fromTournamentId}' not found.");
+ new PredicateExpression(TournamentFields.Id == copyArgs.SourceTournamentId), CancellationToken.None)
+ ?? throw new InvalidOperationException($"'{copyArgs.SourceTournamentId}' not found.");
- var newTournament = new TournamentEntity
+ // Create the target tournament
+ var targetTournament = new TournamentEntity
{
IsPlanningMode = true,
- Name = newName,
- Description = newDescription,
- TypeId = fromTournament.TypeId,
+ Name = copyArgs.TargetName,
+ Description = copyArgs.TargetDescription,
+ TypeId = sourceTournament.TypeId,
IsComplete = false,
- CreatedOn = now,
- ModifiedOn = now,
+ CreatedOn = _modifiedOn,
+ ModifiedOn = _modifiedOn,
};
- var success = await _appDb.GenericRepository.SaveEntityAsync(newTournament, true, false, cancellationToken);
- _logger.LogInformation("New tournament {newTournament} saved successfully: {success}", newTournament, success);
- if (!success) return null;
-
- fromTournament.NextTournamentId = newTournament.Id;
- fromTournament.ModifiedOn = now;
+ // Update the source tournament
+ // sourceTournament.NextTournamentId (and sourceTournament.ModifiedOn) can be set immediately
+ // after the target tournament is saved, and NextTournamentId is NULL
- // save last tournament
- success = await _appDb.GenericRepository.SaveEntityAsync(fromTournament, true, false, cancellationToken);
- _logger.LogInformation("Setting next tournament {newTournament} for {fromTournament} saved successfully: {success}", newTournament, fromTournament, success);
- if (!success) return null;
-
- return newTournament.Id;
+ return (sourceTournament, targetTournament);
}
-
///
/// Copies the round basic data and the round leg data
- /// from the source to an existing target tournament. The new tournament id must
- /// already exist. Leg data for each round is taken over from target tournament legs
- /// on a 1:1 base (same number of legs, dates/times).
+ /// from the source to an existing target tournament. Leg basic data for each round is copied from the source to the target tournament.
///
- /// Existing source tournament id.
- /// Existing target tournament id.
- /// List of round id's to be excluded (empty list for 'none')
- /// The legs' start and end date.
+ ///
+ ///
///
- /// True, if creation was successful, false otherwise.
- public async Task CopyRoundWithLegs(long fromTournamentId, long toTournamentId, IList excludeRoundId, IList<(DateTime Start, DateTime End)> legDates, CancellationToken cancellationToken)
+ /// The with rounds and round legs added.
+ internal async Task CopyRoundsWithLegsToTarget(CopyTournamentArgs args, TournamentEntity targetTournament, CancellationToken cancellationToken)
{
- var transactionName = Guid.NewGuid().ToString("N");
- var now = DateTime.UtcNow;
+ // get the round IDs of the SOURCE tournament
+ var sourceRoundIds = await _appDb.TournamentRepository.GetTournamentRoundIdsAsync(args.SourceTournamentId, cancellationToken);
- // get the rounds of SOURCE tournament
- var roundIds = (await _appDb.TournamentRepository.GetTournamentRoundsAsync(fromTournamentId, cancellationToken)).Select(r => r.Id).ToList();
-
- using var da = _appDb.DbContext.GetNewAdapter();
-
- var roundsWithLegs = new Queue();
- foreach (var r in roundIds)
+ foreach (var sourceRoundId in sourceRoundIds)
{
- var round = await _appDb.RoundRepository.GetRoundWithLegsAsync(r, cancellationToken);
- if (round != null) roundsWithLegs.Enqueue(round);
- }
+ var sourceRound = await _appDb.RoundRepository.GetRoundWithLegsAsync(sourceRoundId, cancellationToken);
- try
- {
- await da.StartTransactionAsync(System.Data.IsolationLevel.ReadUncommitted, transactionName, cancellationToken);
+ if (sourceRound is null)
+ {
+ _logger.LogCritical("Round {RoundId} not found.", sourceRoundId);
+ return false;
+ }
- foreach (var r in roundIds)
+ // skip excluded round id's
+ if (args.RoundsToExclude.Contains(sourceRoundId))
{
- var round = roundsWithLegs.Dequeue();
+ _logger.LogDebug("Round {RoundId} excluded from copy.", sourceRoundId);
+ continue;
+ }
- // skip excluded round id's
- if (excludeRoundId.Contains(r))
- continue;
+ // Create target round from the source round
+ var targetRound = new RoundEntity
+ {
+ Tournament = targetTournament, // this adds the round to the target tournament
+ Name = sourceRound.Name,
+ Description = sourceRound.Description,
+ TypeId = sourceRound.TypeId,
+ NumOfLegs = sourceRound.NumOfLegs,
+ MatchRuleId = sourceRound.MatchRuleId,
+ SetRuleId = sourceRound.SetRuleId,
+ IsComplete = false,
+ CreatedOn = _modifiedOn,
+ ModifiedOn = _modifiedOn,
+ NextRoundId = null
+ };
+
+ var legDates = args.TargetLegDates;
+
+ if (sourceRound.RoundLegs.Count > legDates.Count)
+ {
+ _logger.LogCritical("Round {RoundId} has {Legs} legs, but only {LegDates} leg dates provided.", sourceRoundId, sourceRound.RoundLegs.Count, legDates.Count);
+ return false;
+ }
- // create new round and use data of source round
- var newRound = new RoundEntity
+ // Create the round leg records based on the TARGET tournament legs, but use new log dates
+ for (var index = 0; index < sourceRound.RoundLegs.Count; index++)
+ {
+ var rl = sourceRound.RoundLegs[index];
+ var targetRoundLeg = new RoundLegEntity
{
- TournamentId = toTournamentId,
- Name = round.Name,
- Description = round.Description,
- TypeId = round.TypeId,
- NumOfLegs = round.NumOfLegs,
- MatchRuleId = round.MatchRuleId,
- SetRuleId = round.SetRuleId,
- IsComplete = false,
- CreatedOn = now,
- ModifiedOn = now,
- NextRoundId = null
+ Round = targetRound, // this adds the round leg to the target round
+ SequenceNo = rl.SequenceNo,
+ Description = rl.Description,
+ StartDateTime = legDates[index].Start,
+ EndDateTime = legDates[index].End,
+ CreatedOn = _modifiedOn,
+ ModifiedOn = _modifiedOn
};
-
- // create the round leg records based on the TARGET tournament legs, but use new log dates
- for (var index = 0; index < round.RoundLegs.Count; index++)
- {
- var rl = round.RoundLegs[index];
- var newRoundLeg = new RoundLegEntity {
- SequenceNo = rl.SequenceNo,
- Description = rl.Description,
- StartDateTime = index < legDates.Count ? legDates[index].Start : now,
- EndDateTime = index < legDates.Count ? legDates[index].End : now,
- CreatedOn = now,
- ModifiedOn = now
- };
- newRound.RoundLegs.Add(newRoundLeg);
- }
-
- // save recursively (new round with the new round legs)
- await da.SaveEntityAsync(newRound, true, true, cancellationToken);
}
- // commit after all rounds are processed successfully
- await da.CommitAsync(cancellationToken);
- _logger.LogInformation("{numOfRounds} rounds with legs saved successfully.", roundIds.Count);
- return true;
+ _logger.LogDebug("Round {RoundId} with {Legs} legs copied to target tournament.", sourceRoundId, sourceRound.RoundLegs.Count);
}
- catch (Exception e)
- {
- _logger.LogCritical(e, "Error cloning rounds in transaction: New TournamentId={TournamentId}", toTournamentId);
- if (da.IsTransactionInProgress)
- da.Rollback(transactionName);
+ _logger.LogDebug("Target tournament contains {RoundCount} rounds.", targetTournament.Rounds.Count);
- return false;
- }
+ return true;
}
+ ///
+ /// Sets the start and end dates for the legs having the of the specified .
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// , if successful.
public async Task SetLegDates(ICollection rounds , int sequenceNo, DateTime start, DateTime end, CancellationToken cancellationToken)
{
- var transactionName = string.Concat(nameof(RankingRepository), nameof(SetLegDates), Guid.NewGuid().ToString("N"));
- var now = DateTime.UtcNow;
-
if (rounds.Count == 0)
return false;
@@ -193,32 +231,27 @@ public async Task SetLegDates(ICollection rounds , int sequen
if (!tournamentId.HasValue)
return false;
- using var da = _appDb.DbContext.GetNewAdapter();
-
try
{
- await da.StartTransactionAsync(System.Data.IsolationLevel.ReadUncommitted, transactionName, cancellationToken);
-
foreach (var round in rounds)
{
foreach (var leg in round.RoundLegs.Where(l => l.SequenceNo == sequenceNo))
{
leg.StartDateTime = start;
leg.EndDateTime = end;
- leg.ModifiedOn = now;
+ leg.ModifiedOn = _modifiedOn;
- await da.SaveEntityAsync(leg, false, false, cancellationToken);
+ await _appDb.GenericRepository.SaveEntityAsync(leg, false, false, cancellationToken);
+ _logger.LogDebug("RoundLeg {RoundLegId} updated with new dates.", leg.Id);
}
}
- await da.CommitAsync(cancellationToken);
+
return true;
}
catch (Exception e)
{
- _logger.LogCritical(e, "Error updating round legs in transaction: TournamentId={TournamentId}", tournamentId);
+ _logger.LogCritical(e, "Error updating round legs: TournamentId={TournamentId}", tournamentId);
- if (da.IsTransactionInProgress)
- da.Rollback();
return false;
}
}
@@ -228,37 +261,45 @@ public async Task SetLegDates(ICollection rounds , int sequen
///
/// The Tournament to be set as "completed"
///
- /// Throws an exception if any match of the tournament is not completed yet.
+ /// Throws an exception if any match of the tournament is not completed yet.
public async Task SetTournamentCompleted(long tournamentId, CancellationToken cancellationToken)
{
if (!await _appDb.MatchRepository.AllMatchesCompletedAsync(new TournamentEntity(tournamentId), cancellationToken))
{
var ex = new InvalidOperationException($@"Tournament {tournamentId} contains incomplete matches.");
- _logger.LogCritical(@"Tournament {TournamentId} contains incomplete matches. {Exception}", tournamentId, ex);
+ _logger.LogCritical(ex,@"Tournament {TournamentId} contains incomplete matches.", tournamentId);
throw ex;
}
var tournament = await _appDb.TournamentRepository.GetTournamentWithRoundsAsync(tournamentId, CancellationToken.None) ?? throw new InvalidOperationException($"Tournament with Id '{tournamentId}' not found.");
- var now = DateTime.UtcNow;
+
+ // Note: Setting rounds and tournament as completed also removes inconsistencies
+ // (e.g. a tournament is marked as completed, but not all rounds are completed)
foreach (var round in tournament.Rounds)
{
await SetRoundCompleted(round, cancellationToken);
}
- tournament.IsComplete = true;
- tournament.ModifiedOn = now;
-
- using var da = _appDb.DbContext.GetNewAdapter();
- if (!await da.SaveEntityAsync(tournament, true, true, cancellationToken))
+ if (!tournament.IsComplete)
{
- var ex = new InvalidOperationException($"Tournament Id {tournamentId} could not be saved to persistent storage.");
- _logger.LogCritical(@"Tournament Id {TournamentId} could not be saved to persistent storage. {Exception}", tournamentId, ex);
- throw ex;
+ tournament.IsComplete = true;
+ tournament.ModifiedOn = _modifiedOn;
+ await _appDb.GenericRepository.SaveEntityAsync(tournament, false, false, cancellationToken);
+ _logger.LogDebug("Tournament {Tournament} set as completed.", tournament);
+ }
+ else
+ {
+ _logger.LogDebug("Tournament {Tournament} was already set as completed.", tournament);
}
- _logger.LogInformation("Tournament {Tournament} set as completed.", tournament);
}
+ ///
+ /// Sets the specified round as completed if all matches of the round are completed.
+ ///
+ /// The round to be set as completed.
+ ///
+ /// Thrown if any match of the round is not completed yet.
public virtual async Task SetRoundCompleted(RoundEntity round, CancellationToken cancellationToken)
{
if (!await _appDb.MatchRepository.AllMatchesCompletedAsync(round, cancellationToken))
@@ -268,11 +309,22 @@ public virtual async Task SetRoundCompleted(RoundEntity round, CancellationToken
throw ex;
}
- using var da = _appDb.DbContext.GetNewAdapter();
- da.FetchEntity(round);
- round.IsComplete = true;
- round.ModifiedOn = DateTime.UtcNow;
- await da.SaveEntityAsync(round, cancellationToken);
- _logger.LogInformation("Round {round} set as complete.", round);
+ if (round.IsDirty || round.IsNew)
+ {
+ round = (await _appDb.RoundRepository.GetRoundWithLegsAsync(round.Id, cancellationToken))!;
+ }
+
+ if (!round.IsComplete)
+ {
+ round.IsComplete = true;
+ round.ModifiedOn = _modifiedOn;
+
+ await _appDb.GenericRepository.SaveEntityAsync(round, false, false, cancellationToken);
+ _logger.LogDebug("Round {round} set as complete.", round);
+ }
+ else
+ {
+ _logger.LogDebug("Round {round} was already set as complete.", round);
+ }
}
}