CRITICAL: Add multi-session development protocol

- Add mandatory steps for context preservation between chat sessions
- Require conversation search before any code implementation
- Mandate verification of existing files and architectural decisions
- Establish protocols to prevent interface/implementation mismatches
- Add critical failure points to avoid in multi-session development

This addresses the fundamental issue of memory loss between chat sessions
that was causing architectural inconsistency and wasted development time.
This commit is contained in:
matt 2025-10-19 13:49:42 -05:00
parent 3a45fc73ab
commit 25dbbb5c84
9 changed files with 9598 additions and 10 deletions

View File

@ -7,12 +7,7 @@
</PropertyGroup>
<ItemGroup>
<Folder Include="Models\Alliance\" />
<Folder Include="Models\Kingdom\" />
<Folder Include="Models\Combat\" />
<Folder Include="Models\Events\" />
<Folder Include="Models\Player\" />
<Folder Include="Interfaces\" />
<Folder Include="Enums\" />
<Folder Include="Constants\" />
<Folder Include="Extensions\" />

View File

@ -0,0 +1,852 @@
/*
* File: ShadowedRealms.Data/Repositories/Kingdom/KingdomRepository.cs
* Created: 2025-10-19
* Last Modified: 2025-10-19
* Description: Kingdom repository implementation providing kingdom-specific operations including population management,
* democratic systems, KvK events, merger mechanics, and tax distribution systems.
* Last Edit Notes: Initial implementation with complete kingdom management, democratic host selection, and population control.
*/
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using ShadowedRealms.Core.Interfaces;
using ShadowedRealms.Core.Interfaces.Repositories;
using ShadowedRealms.Core.Models;
using ShadowedRealms.Core.Models.Alliance;
using ShadowedRealms.Data.Contexts;
using System.Linq.Expressions;
namespace ShadowedRealms.Data.Repositories.Kingdom
{
/// <summary>
/// Kingdom repository implementation providing specialized kingdom operations.
/// Handles population management, democratic systems, KvK events, and kingdom-level statistics.
/// </summary>
public class KingdomRepository : Repository<Core.Models.Kingdom, int>, IKingdomRepository
{
public KingdomRepository(GameDbContext context, ILogger<KingdomRepository> logger)
: base(context, logger)
{
}
#region Population Management
/// <summary>
/// Gets current population count for a kingdom
/// </summary>
public async Task<int> GetPopulationAsync(int kingdomId)
{
try
{
_logger.LogDebug("Getting population count for Kingdom {KingdomId}", kingdomId);
var count = await _context.Players
.Where(p => p.KingdomId == kingdomId && p.IsActive)
.CountAsync();
_logger.LogDebug("Kingdom {KingdomId} has population of {Count}", kingdomId, count);
return count;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting population for Kingdom {KingdomId}", kingdomId);
throw new InvalidOperationException($"Failed to get population for Kingdom {kingdomId}", ex);
}
}
/// <summary>
/// Checks if kingdom needs population management (new kingdom creation)
/// </summary>
public async Task<bool> NeedsNewKingdomAsync(int kingdomId)
{
try
{
_logger.LogDebug("Checking if Kingdom {KingdomId} needs new kingdom creation", kingdomId);
var population = await GetPopulationAsync(kingdomId);
const int maxPopulation = 1500; // Target maximum population per kingdom
bool needsNew = population >= maxPopulation;
_logger.LogDebug("Kingdom {KingdomId} population {Population}, needs new kingdom: {NeedsNew}",
kingdomId, population, needsNew);
return needsNew;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error checking new kingdom need for Kingdom {KingdomId}", kingdomId);
throw new InvalidOperationException($"Failed to check new kingdom need for Kingdom {kingdomId}", ex);
}
}
/// <summary>
/// Gets kingdoms that can accept new players (under population limit)
/// </summary>
public async Task<IEnumerable<Core.Models.Kingdom>> GetAvailableKingdomsAsync()
{
try
{
_logger.LogDebug("Getting available kingdoms for new players");
const int maxPopulation = 1500;
const int targetPopulation = 1200; // Preferred threshold before creating new kingdom
var kingdomsWithCounts = await _context.Kingdoms
.Where(k => k.IsActive)
.Select(k => new
{
Kingdom = k,
PlayerCount = _context.Players.Count(p => p.KingdomId == k.Id && p.IsActive)
})
.Where(x => x.PlayerCount < maxPopulation)
.OrderBy(x => x.PlayerCount) // Prioritize kingdoms with fewer players
.ToListAsync();
var availableKingdoms = kingdomsWithCounts
.Select(x => x.Kingdom)
.ToList();
_logger.LogDebug("Found {Count} available kingdoms for new players", availableKingdoms.Count);
return availableKingdoms;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting available kingdoms");
throw new InvalidOperationException("Failed to get available kingdoms", ex);
}
}
/// <summary>
/// Gets the newest kingdom (for automatic player assignment)
/// </summary>
public async Task<Core.Models.Kingdom?> GetNewestKingdomAsync()
{
try
{
_logger.LogDebug("Getting newest active kingdom");
var kingdom = await _context.Kingdoms
.Where(k => k.IsActive)
.OrderByDescending(k => k.CreatedAt)
.FirstOrDefaultAsync();
if (kingdom != null)
{
_logger.LogDebug("Found newest kingdom: {KingdomId} created at {CreatedAt}",
kingdom.Id, kingdom.CreatedAt);
}
else
{
_logger.LogWarning("No active kingdoms found");
}
return kingdom;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting newest kingdom");
throw new InvalidOperationException("Failed to get newest kingdom", ex);
}
}
/// <summary>
/// Creates a new kingdom with proper initialization
/// </summary>
public async Task<Core.Models.Kingdom> CreateNewKingdomAsync(string name, string description = "")
{
try
{
_logger.LogInformation("Creating new kingdom: {Name}", name);
var kingdom = new Core.Models.Kingdom
{
Name = name,
Description = description,
IsActive = true,
CreatedAt = DateTime.UtcNow,
LastActivity = DateTime.UtcNow,
TotalTaxCollected = 0,
IsInKvK = false,
KvKHostAllianceId = null,
CurrentPowerRank = 0
};
// Note: KingdomId is not set here as this is a root entity operation
// The base repository Add method will handle this appropriately
var addedKingdom = await _context.Kingdoms.AddAsync(kingdom);
await _context.SaveChangesAsync();
_logger.LogInformation("Successfully created new kingdom {KingdomId}: {Name}",
addedKingdom.Entity.Id, name);
return addedKingdom.Entity;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error creating new kingdom: {Name}", name);
throw new InvalidOperationException($"Failed to create new kingdom: {name}", ex);
}
}
#endregion
#region KvK Event Management
/// <summary>
/// Gets kingdoms currently participating in KvK events
/// </summary>
public async Task<IEnumerable<Core.Models.Kingdom>> GetKvKParticipantsAsync()
{
try
{
_logger.LogDebug("Getting kingdoms participating in KvK events");
var kingdoms = await _context.Kingdoms
.Where(k => k.IsInKvK && k.IsActive)
.Include(k => k.Players)
.Include(k => k.Alliances)
.ToListAsync();
_logger.LogDebug("Found {Count} kingdoms participating in KvK", kingdoms.Count);
return kingdoms;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting KvK participants");
throw new InvalidOperationException("Failed to get KvK participants", ex);
}
}
/// <summary>
/// Starts KvK event for specified kingdoms
/// </summary>
public async Task<bool> StartKvKEventAsync(IEnumerable<int> kingdomIds, int hostAllianceId)
{
try
{
var kingdomIdsList = kingdomIds.ToList();
_logger.LogInformation("Starting KvK event for kingdoms: {KingdomIds} with host alliance {HostAllianceId}",
string.Join(", ", kingdomIdsList), hostAllianceId);
// Validate host alliance exists and is in one of the participating kingdoms
var hostAlliance = await _context.Alliances
.FirstOrDefaultAsync(a => a.Id == hostAllianceId && kingdomIdsList.Contains(a.KingdomId));
if (hostAlliance == null)
{
_logger.LogError("Host alliance {HostAllianceId} not found in participating kingdoms", hostAllianceId);
return false;
}
// Update all participating kingdoms
var kingdoms = await _context.Kingdoms
.Where(k => kingdomIdsList.Contains(k.Id) && k.IsActive)
.ToListAsync();
if (kingdoms.Count != kingdomIdsList.Count)
{
_logger.LogError("Not all specified kingdoms found or active. Expected {Expected}, found {Found}",
kingdomIdsList.Count, kingdoms.Count);
return false;
}
foreach (var kingdom in kingdoms)
{
kingdom.IsInKvK = true;
kingdom.KvKHostAllianceId = hostAllianceId;
kingdom.LastActivity = DateTime.UtcNow;
}
await _context.SaveChangesAsync();
_logger.LogInformation("Successfully started KvK event for {Count} kingdoms", kingdoms.Count);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error starting KvK event for kingdoms: {KingdomIds}", string.Join(", ", kingdomIds));
throw new InvalidOperationException("Failed to start KvK event", ex);
}
}
/// <summary>
/// Ends KvK event for specified kingdoms
/// </summary>
public async Task<bool> EndKvKEventAsync(IEnumerable<int> kingdomIds)
{
try
{
var kingdomIdsList = kingdomIds.ToList();
_logger.LogInformation("Ending KvK event for kingdoms: {KingdomIds}", string.Join(", ", kingdomIdsList));
var kingdoms = await _context.Kingdoms
.Where(k => kingdomIdsList.Contains(k.Id) && k.IsInKvK)
.ToListAsync();
foreach (var kingdom in kingdoms)
{
kingdom.IsInKvK = false;
kingdom.KvKHostAllianceId = null;
kingdom.LastActivity = DateTime.UtcNow;
}
await _context.SaveChangesAsync();
_logger.LogInformation("Successfully ended KvK event for {Count} kingdoms", kingdoms.Count);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error ending KvK event for kingdoms: {KingdomIds}", string.Join(", ", kingdomIds));
throw new InvalidOperationException("Failed to end KvK event", ex);
}
}
/// <summary>
/// Gets eligible alliances for KvK host selection in a kingdom
/// </summary>
public async Task<IEnumerable<Alliance>> GetKvKHostCandidatesAsync(int kingdomId)
{
try
{
_logger.LogDebug("Getting KvK host candidates for Kingdom {KingdomId}", kingdomId);
// Host candidates should be top alliances by power/activity
var candidates = await _context.Alliances
.Where(a => a.KingdomId == kingdomId && a.IsActive)
.Where(a => a.MemberCount >= 10) // Minimum members for host responsibility
.OrderByDescending(a => a.TotalPower)
.ThenByDescending(a => a.Level)
.Take(5) // Top 5 candidates for democratic voting
.ToListAsync();
_logger.LogDebug("Found {Count} KvK host candidates for Kingdom {KingdomId}", candidates.Count, kingdomId);
return candidates;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting KvK host candidates for Kingdom {KingdomId}", kingdomId);
throw new InvalidOperationException($"Failed to get KvK host candidates for Kingdom {kingdomId}", ex);
}
}
#endregion
#region Democratic Systems
/// <summary>
/// Conducts democratic vote for KvK host selection
/// </summary>
public async Task<Alliance?> ConductKvKHostVoteAsync(int kingdomId, Dictionary<int, int> votes)
{
try
{
_logger.LogInformation("Conducting KvK host vote for Kingdom {KingdomId}", kingdomId);
if (!votes.Any())
{
_logger.LogWarning("No votes received for Kingdom {KingdomId} KvK host selection", kingdomId);
return null;
}
// Validate all voted alliances are in the kingdom
var allianceIds = votes.Keys.ToList();
var validAlliances = await _context.Alliances
.Where(a => allianceIds.Contains(a.Id) && a.KingdomId == kingdomId && a.IsActive)
.ToListAsync();
if (validAlliances.Count != allianceIds.Count)
{
_logger.LogError("Some voted alliances not found or not in Kingdom {KingdomId}", kingdomId);
return null;
}
// Find alliance with most votes
var winnerVote = votes.OrderByDescending(v => v.Value).First();
var winnerAlliance = validAlliances.First(a => a.Id == winnerVote.Key);
_logger.LogInformation("Alliance {AllianceId} ({AllianceName}) won KvK host vote for Kingdom {KingdomId} with {VoteCount} votes",
winnerAlliance.Id, winnerAlliance.Name, winnerVote.Value, kingdomId);
return winnerAlliance;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error conducting KvK host vote for Kingdom {KingdomId}", kingdomId);
throw new InvalidOperationException($"Failed to conduct KvK host vote for Kingdom {kingdomId}", ex);
}
}
/// <summary>
/// Gets voting statistics for democratic decisions
/// </summary>
public async Task<Dictionary<string, object>> GetVotingStatsAsync(int kingdomId)
{
try
{
_logger.LogDebug("Getting voting statistics for Kingdom {KingdomId}", kingdomId);
var totalPlayers = await _context.Players
.Where(p => p.KingdomId == kingdomId && p.IsActive)
.CountAsync();
var totalAlliances = await _context.Alliances
.Where(a => a.KingdomId == kingdomId && a.IsActive)
.CountAsync();
var stats = new Dictionary<string, object>
{
["TotalPlayers"] = totalPlayers,
["TotalAlliances"] = totalAlliances,
["EligibleVoters"] = totalPlayers, // All active players can vote
["LastVoteDate"] = DateTime.UtcNow // Would track actual last vote in production
};
_logger.LogDebug("Retrieved voting statistics for Kingdom {KingdomId}", kingdomId);
return stats;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting voting statistics for Kingdom {KingdomId}", kingdomId);
throw new InvalidOperationException($"Failed to get voting statistics for Kingdom {kingdomId}", ex);
}
}
#endregion
#region Kingdom Merger System
/// <summary>
/// Checks if two kingdoms are compatible for merging
/// </summary>
public async Task<bool> AreKingdomsCompatibleForMergerAsync(int kingdomId1, int kingdomId2)
{
try
{
_logger.LogDebug("Checking merger compatibility between Kingdom {Kingdom1} and Kingdom {Kingdom2}",
kingdomId1, kingdomId2);
if (kingdomId1 == kingdomId2)
{
_logger.LogDebug("Cannot merge kingdom with itself");
return false;
}
var kingdoms = await _context.Kingdoms
.Where(k => (k.Id == kingdomId1 || k.Id == kingdomId2) && k.IsActive)
.ToListAsync();
if (kingdoms.Count != 2)
{
_logger.LogDebug("One or both kingdoms not found or inactive");
return false;
}
var kingdom1 = kingdoms.First(k => k.Id == kingdomId1);
var kingdom2 = kingdoms.First(k => k.Id == kingdomId2);
// Get population counts
var pop1 = await GetPopulationAsync(kingdomId1);
var pop2 = await GetPopulationAsync(kingdomId2);
var combinedPopulation = pop1 + pop2;
const int maxMergedPopulation = 1500;
// Compatibility checks
bool populationOk = combinedPopulation <= maxMergedPopulation;
bool neitherInKvK = !kingdom1.IsInKvK && !kingdom2.IsInKvK;
bool bothActive = kingdom1.IsActive && kingdom2.IsActive;
bool compatible = populationOk && neitherInKvK && bothActive;
_logger.LogDebug("Kingdom merger compatibility: Population OK: {PopOk}, Neither in KvK: {KvKOk}, Both Active: {ActiveOk}, Overall Compatible: {Compatible}",
populationOk, neitherInKvK, bothActive, compatible);
return compatible;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error checking kingdom merger compatibility between {Kingdom1} and {Kingdom2}",
kingdomId1, kingdomId2);
throw new InvalidOperationException($"Failed to check merger compatibility between kingdoms {kingdomId1} and {kingdomId2}", ex);
}
}
/// <summary>
/// Executes kingdom merger (moves all players and alliances to target kingdom)
/// </summary>
public async Task<bool> ExecuteKingdomMergerAsync(int sourceKingdomId, int targetKingdomId)
{
try
{
_logger.LogInformation("Executing kingdom merger: {SourceKingdom} -> {TargetKingdom}",
sourceKingdomId, targetKingdomId);
// Validate compatibility first
if (!await AreKingdomsCompatibleForMergerAsync(sourceKingdomId, targetKingdomId))
{
_logger.LogError("Kingdoms {SourceKingdom} and {TargetKingdom} are not compatible for merger",
sourceKingdomId, targetKingdomId);
return false;
}
using var transaction = await _context.Database.BeginTransactionAsync();
try
{
// Move all players from source to target kingdom
var playersToMove = await _context.Players
.Where(p => p.KingdomId == sourceKingdomId)
.ToListAsync();
foreach (var player in playersToMove)
{
player.KingdomId = targetKingdomId;
}
// Move all alliances from source to target kingdom
var alliancesToMove = await _context.Alliances
.Where(a => a.KingdomId == sourceKingdomId)
.ToListAsync();
foreach (var alliance in alliancesToMove)
{
alliance.KingdomId = targetKingdomId;
}
// Deactivate source kingdom
var sourceKingdom = await _context.Kingdoms
.FirstOrDefaultAsync(k => k.Id == sourceKingdomId);
if (sourceKingdom != null)
{
sourceKingdom.IsActive = false;
sourceKingdom.LastActivity = DateTime.UtcNow;
}
// Update target kingdom activity
var targetKingdom = await _context.Kingdoms
.FirstOrDefaultAsync(k => k.Id == targetKingdomId);
if (targetKingdom != null)
{
targetKingdom.LastActivity = DateTime.UtcNow;
}
await _context.SaveChangesAsync();
await transaction.CommitAsync();
_logger.LogInformation("Successfully merged Kingdom {SourceKingdom} into Kingdom {TargetKingdom}. Moved {PlayerCount} players and {AllianceCount} alliances",
sourceKingdomId, targetKingdomId, playersToMove.Count, alliancesToMove.Count);
return true;
}
catch
{
await transaction.RollbackAsync();
throw;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error executing kingdom merger: {SourceKingdom} -> {TargetKingdom}",
sourceKingdomId, targetKingdomId);
throw new InvalidOperationException($"Failed to execute kingdom merger from {sourceKingdomId} to {targetKingdomId}", ex);
}
}
/// <summary>
/// Gets potential merger candidates for a kingdom
/// </summary>
public async Task<IEnumerable<Core.Models.Kingdom>> GetMergerCandidatesAsync(int kingdomId)
{
try
{
_logger.LogDebug("Getting merger candidates for Kingdom {KingdomId}", kingdomId);
var currentPopulation = await GetPopulationAsync(kingdomId);
const int maxMergedPopulation = 1500;
var candidates = new List<Core.Models.Kingdom>();
var otherKingdoms = await _context.Kingdoms
.Where(k => k.Id != kingdomId && k.IsActive && !k.IsInKvK)
.ToListAsync();
foreach (var kingdom in otherKingdoms)
{
var otherPopulation = await GetPopulationAsync(kingdom.Id);
if (currentPopulation + otherPopulation <= maxMergedPopulation)
{
candidates.Add(kingdom);
}
}
_logger.LogDebug("Found {Count} merger candidates for Kingdom {KingdomId}", candidates.Count, kingdomId);
return candidates.OrderBy(k => k.CreatedAt); // Prefer older kingdoms
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting merger candidates for Kingdom {KingdomId}", kingdomId);
throw new InvalidOperationException($"Failed to get merger candidates for Kingdom {kingdomId}", ex);
}
}
#endregion
#region Tax and Economic Systems
/// <summary>
/// Collects tax from all players in conquered kingdoms
/// </summary>
public async Task<decimal> CollectKingdomTaxAsync(int kingdomId, decimal taxRate = 0.04m)
{
try
{
_logger.LogInformation("Collecting kingdom tax for Kingdom {KingdomId} at rate {TaxRate}%",
kingdomId, taxRate * 100);
// In a full implementation, this would calculate tax based on player resources
// For now, we'll simulate based on player count and activity
var activePlayers = await _context.Players
.Where(p => p.KingdomId == kingdomId && p.IsActive)
.CountAsync();
// Simulate tax collection (in production this would be based on actual resources)
var estimatedTaxPerPlayer = 1000m; // Base tax per active player
var totalTaxCollected = activePlayers * estimatedTaxPerPlayer * taxRate;
// Update kingdom tax total
var kingdom = await _context.Kingdoms
.FirstOrDefaultAsync(k => k.Id == kingdomId);
if (kingdom != null)
{
kingdom.TotalTaxCollected += totalTaxCollected;
kingdom.LastActivity = DateTime.UtcNow;
await _context.SaveChangesAsync();
}
_logger.LogInformation("Collected {TaxAmount} tax from Kingdom {KingdomId} with {PlayerCount} active players",
totalTaxCollected, kingdomId, activePlayers);
return totalTaxCollected;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error collecting kingdom tax for Kingdom {KingdomId}", kingdomId);
throw new InvalidOperationException($"Failed to collect kingdom tax for Kingdom {kingdomId}", ex);
}
}
/// <summary>
/// Distributes collected tax to royal treasury and development
/// </summary>
public async Task<bool> DistributeTaxRevenueAsync(int kingdomId, decimal amount)
{
try
{
_logger.LogInformation("Distributing tax revenue {Amount} for Kingdom {KingdomId}", amount, kingdomId);
// Tax distribution: 50% royal treasury, 30% kingdom development, 20% alliance rewards
var royalTreasuryShare = amount * 0.5m;
var developmentShare = amount * 0.3m;
var allianceRewardShare = amount * 0.2m;
// Update kingdom with distribution (in production, this would update actual resource pools)
var kingdom = await _context.Kingdoms
.FirstOrDefaultAsync(k => k.Id == kingdomId);
if (kingdom != null)
{
// In production, these would be actual resource fields
kingdom.LastActivity = DateTime.UtcNow;
await _context.SaveChangesAsync();
}
_logger.LogInformation("Distributed tax revenue for Kingdom {KingdomId}: Royal {Royal}, Development {Development}, Alliance {Alliance}",
kingdomId, royalTreasuryShare, developmentShare, allianceRewardShare);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error distributing tax revenue for Kingdom {KingdomId}", kingdomId);
throw new InvalidOperationException($"Failed to distribute tax revenue for Kingdom {kingdomId}", ex);
}
}
#endregion
#region Kingdom Statistics and Analytics
/// <summary>
/// Gets comprehensive kingdom statistics
/// </summary>
public async Task<Dictionary<string, object>> GetKingdomStatsAsync(int kingdomId)
{
try
{
_logger.LogDebug("Getting comprehensive statistics for Kingdom {KingdomId}", kingdomId);
var kingdom = await GetByIdAsync(kingdomId, kingdomId);
if (kingdom == null)
{
throw new InvalidOperationException($"Kingdom {kingdomId} not found");
}
var population = await GetPopulationAsync(kingdomId);
var allianceCount = await _context.Alliances
.Where(a => a.KingdomId == kingdomId && a.IsActive)
.CountAsync();
var totalPower = await _context.Players
.Where(p => p.KingdomId == kingdomId && p.IsActive)
.SumAsync(p => p.Power);
var averageCastleLevel = await _context.Players
.Where(p => p.KingdomId == kingdomId && p.IsActive)
.AverageAsync(p => (double)p.CastleLevel);
var stats = new Dictionary<string, object>
{
["KingdomId"] = kingdomId,
["KingdomName"] = kingdom.Name,
["Population"] = population,
["AllianceCount"] = allianceCount,
["TotalPower"] = totalPower,
["AverageCastleLevel"] = Math.Round(averageCastleLevel, 2),
["IsInKvK"] = kingdom.IsInKvK,
["TotalTaxCollected"] = kingdom.TotalTaxCollected,
["CreatedAt"] = kingdom.CreatedAt,
["LastActivity"] = kingdom.LastActivity,
["CurrentPowerRank"] = kingdom.CurrentPowerRank
};
_logger.LogDebug("Retrieved comprehensive statistics for Kingdom {KingdomId}", kingdomId);
return stats;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting kingdom statistics for Kingdom {KingdomId}", kingdomId);
throw new InvalidOperationException($"Failed to get kingdom statistics for Kingdom {kingdomId}", ex);
}
}
/// <summary>
/// Updates kingdom power rankings
/// </summary>
public async Task UpdatePowerRankingsAsync()
{
try
{
_logger.LogInformation("Updating power rankings for all kingdoms");
var kingdomPowers = await _context.Kingdoms
.Where(k => k.IsActive)
.Select(k => new
{
Kingdom = k,
TotalPower = _context.Players
.Where(p => p.KingdomId == k.Id && p.IsActive)
.Sum(p => p.Power)
})
.OrderByDescending(x => x.TotalPower)
.ToListAsync();
int rank = 1;
foreach (var item in kingdomPowers)
{
item.Kingdom.CurrentPowerRank = rank++;
}
await _context.SaveChangesAsync();
_logger.LogInformation("Updated power rankings for {Count} kingdoms", kingdomPowers.Count);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error updating kingdom power rankings");
throw new InvalidOperationException("Failed to update kingdom power rankings", ex);
}
}
#endregion
#region Teleportation and Movement Control
/// <summary>
/// Validates if player can teleport to target kingdom
/// </summary>
public async Task<(bool CanTeleport, string Reason)> ValidateTeleportAsync(int playerId, int targetKingdomId)
{
try
{
_logger.LogDebug("Validating teleport for Player {PlayerId} to Kingdom {TargetKingdomId}",
playerId, targetKingdomId);
var player = await _context.Players
.FirstOrDefaultAsync(p => p.Id == playerId && p.IsActive);
if (player == null)
{
return (false, "Player not found or inactive");
}
var targetKingdom = await _context.Kingdoms
.FirstOrDefaultAsync(k => k.Id == targetKingdomId && k.IsActive);
if (targetKingdom == null)
{
return (false, "Target kingdom not found or inactive");
}
if (player.KingdomId == targetKingdomId)
{
return (false, "Player is already in target kingdom");
}
// Check if target kingdom has space
var targetPopulation = await GetPopulationAsync(targetKingdomId);
if (targetPopulation >= 1500)
{
return (false, "Target kingdom is at maximum capacity");
}
// Check if either kingdom is in KvK (teleportation restrictions during events)
if (targetKingdom.IsInKvK)
{
return (false, "Cannot teleport to kingdom participating in KvK");
}
var currentKingdom = await _context.Kingdoms
.FirstOrDefaultAsync(k => k.Id == player.KingdomId);
if (currentKingdom?.IsInKvK == true)
{
return (false, "Cannot teleport while your kingdom is participating in KvK");
}
_logger.LogDebug("Teleport validation successful for Player {PlayerId} to Kingdom {TargetKingdomId}",
playerId, targetKingdomId);
return (true, "Teleport allowed");
}
catch (Exception ex)
{
_logger.LogError(ex, "Error validating teleport for Player {PlayerId} to Kingdom {TargetKingdomId}",
playerId, targetKingdomId);
throw new InvalidOperationException($"Failed to validate teleport for player {playerId}", ex);
}
}
#endregion
}
}

View File

@ -0,0 +1,637 @@
/*
* File: ShadowedRealms.Data/Repositories/Repository.cs
* Created: 2025-10-19
* Last Modified: 2025-10-19
* Description: Base repository implementation providing kingdom-scoped data access operations using Entity Framework Core.
* Implements common CRUD operations with automatic kingdom filtering, transaction safety, and performance optimization.
* Last Edit Notes: Initial implementation with complete async operations, kingdom security, and EF Core integration.
*/
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using ShadowedRealms.Core.Interfaces;
using ShadowedRealms.Core.Interfaces.Repositories;
using ShadowedRealms.Core.Models;
using ShadowedRealms.Data.Contexts;
using System.Linq.Expressions;
namespace ShadowedRealms.Data.Repositories
{
/// <summary>
/// Base repository implementation providing kingdom-scoped data access operations.
/// All derived repositories inherit kingdom security, transaction safety, and performance optimization.
/// </summary>
/// <typeparam name="T">Entity type that implements IKingdomScoped</typeparam>
/// <typeparam name="K">Primary key type</typeparam>
public class Repository<T, K> : IRepository<T, K> where T : class, IKingdomScoped
{
protected readonly GameDbContext _context;
protected readonly DbSet<T> _dbSet;
protected readonly ILogger<Repository<T, K>> _logger;
public Repository(GameDbContext context, ILogger<Repository<T, K>> logger)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_dbSet = _context.Set<T>();
}
/// <summary>
/// Gets entity by ID with kingdom validation
/// </summary>
public virtual async Task<T?> GetByIdAsync(K id, int kingdomId)
{
try
{
_logger.LogDebug("Getting {EntityType} with ID {Id} for Kingdom {KingdomId}", typeof(T).Name, id, kingdomId);
var entity = await _dbSet
.Where(e => e.KingdomId == kingdomId)
.FirstOrDefaultAsync(e => EF.Property<K>(e, "Id").Equals(id));
if (entity == null)
{
_logger.LogDebug("Entity {EntityType} with ID {Id} not found in Kingdom {KingdomId}", typeof(T).Name, id, kingdomId);
}
return entity;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting {EntityType} with ID {Id} for Kingdom {KingdomId}", typeof(T).Name, id, kingdomId);
throw new InvalidOperationException($"Failed to retrieve {typeof(T).Name} with ID {id}", ex);
}
}
/// <summary>
/// Gets entity by ID with kingdom validation and custom includes
/// </summary>
public virtual async Task<T?> GetByIdAsync(K id, int kingdomId, params Expression<Func<T, object>>[] includes)
{
try
{
_logger.LogDebug("Getting {EntityType} with ID {Id} for Kingdom {KingdomId} with {IncludeCount} includes",
typeof(T).Name, id, kingdomId, includes.Length);
var query = _dbSet.Where(e => e.KingdomId == kingdomId);
// Apply includes
foreach (var include in includes)
{
query = query.Include(include);
}
var entity = await query.FirstOrDefaultAsync(e => EF.Property<K>(e, "Id").Equals(id));
if (entity == null)
{
_logger.LogDebug("Entity {EntityType} with ID {Id} not found in Kingdom {KingdomId}", typeof(T).Name, id, kingdomId);
}
return entity;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting {EntityType} with ID {Id} and includes for Kingdom {KingdomId}", typeof(T).Name, id, kingdomId);
throw new InvalidOperationException($"Failed to retrieve {typeof(T).Name} with ID {id} and includes", ex);
}
}
/// <summary>
/// Gets all entities in kingdom with optional filtering
/// </summary>
public virtual async Task<IEnumerable<T>> GetAllAsync(int kingdomId, Expression<Func<T, bool>>? filter = null)
{
try
{
_logger.LogDebug("Getting all {EntityType} for Kingdom {KingdomId} with filter: {HasFilter}",
typeof(T).Name, kingdomId, filter != null);
var query = _dbSet.Where(e => e.KingdomId == kingdomId);
if (filter != null)
{
query = query.Where(filter);
}
var entities = await query.ToListAsync();
_logger.LogDebug("Retrieved {Count} {EntityType} entities for Kingdom {KingdomId}",
entities.Count, typeof(T).Name, kingdomId);
return entities;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting all {EntityType} for Kingdom {KingdomId}", typeof(T).Name, kingdomId);
throw new InvalidOperationException($"Failed to retrieve {typeof(T).Name} entities", ex);
}
}
/// <summary>
/// Gets all entities in kingdom with custom includes and filtering
/// </summary>
public virtual async Task<IEnumerable<T>> GetAllAsync(int kingdomId, Expression<Func<T, bool>>? filter = null,
params Expression<Func<T, object>>[] includes)
{
try
{
_logger.LogDebug("Getting all {EntityType} for Kingdom {KingdomId} with filter: {HasFilter} and {IncludeCount} includes",
typeof(T).Name, kingdomId, filter != null, includes.Length);
var query = _dbSet.Where(e => e.KingdomId == kingdomId);
// Apply includes
foreach (var include in includes)
{
query = query.Include(include);
}
if (filter != null)
{
query = query.Where(filter);
}
var entities = await query.ToListAsync();
_logger.LogDebug("Retrieved {Count} {EntityType} entities with includes for Kingdom {KingdomId}",
entities.Count, typeof(T).Name, kingdomId);
return entities;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting all {EntityType} with includes for Kingdom {KingdomId}", typeof(T).Name, kingdomId);
throw new InvalidOperationException($"Failed to retrieve {typeof(T).Name} entities with includes", ex);
}
}
/// <summary>
/// Finds entities matching predicate with kingdom scoping
/// </summary>
public virtual async Task<IEnumerable<T>> FindAsync(int kingdomId, Expression<Func<T, bool>> predicate)
{
try
{
_logger.LogDebug("Finding {EntityType} entities matching predicate for Kingdom {KingdomId}", typeof(T).Name, kingdomId);
var entities = await _dbSet
.Where(e => e.KingdomId == kingdomId)
.Where(predicate)
.ToListAsync();
_logger.LogDebug("Found {Count} {EntityType} entities matching predicate for Kingdom {KingdomId}",
entities.Count, typeof(T).Name, kingdomId);
return entities;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error finding {EntityType} entities for Kingdom {KingdomId}", typeof(T).Name, kingdomId);
throw new InvalidOperationException($"Failed to find {typeof(T).Name} entities", ex);
}
}
/// <summary>
/// Finds entities matching predicate with kingdom scoping and includes
/// </summary>
public virtual async Task<IEnumerable<T>> FindAsync(int kingdomId, Expression<Func<T, bool>> predicate,
params Expression<Func<T, object>>[] includes)
{
try
{
_logger.LogDebug("Finding {EntityType} entities with {IncludeCount} includes matching predicate for Kingdom {KingdomId}",
typeof(T).Name, includes.Length, kingdomId);
var query = _dbSet.Where(e => e.KingdomId == kingdomId);
// Apply includes
foreach (var include in includes)
{
query = query.Include(include);
}
var entities = await query.Where(predicate).ToListAsync();
_logger.LogDebug("Found {Count} {EntityType} entities with includes matching predicate for Kingdom {KingdomId}",
entities.Count, typeof(T).Name, kingdomId);
return entities;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error finding {EntityType} entities with includes for Kingdom {KingdomId}", typeof(T).Name, kingdomId);
throw new InvalidOperationException($"Failed to find {typeof(T).Name} entities with includes", ex);
}
}
/// <summary>
/// Gets first entity matching predicate or null
/// </summary>
public virtual async Task<T?> FirstOrDefaultAsync(int kingdomId, Expression<Func<T, bool>> predicate)
{
try
{
_logger.LogDebug("Getting first {EntityType} matching predicate for Kingdom {KingdomId}", typeof(T).Name, kingdomId);
var entity = await _dbSet
.Where(e => e.KingdomId == kingdomId)
.FirstOrDefaultAsync(predicate);
if (entity == null)
{
_logger.LogDebug("No {EntityType} found matching predicate for Kingdom {KingdomId}", typeof(T).Name, kingdomId);
}
return entity;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting first {EntityType} for Kingdom {KingdomId}", typeof(T).Name, kingdomId);
throw new InvalidOperationException($"Failed to get first {typeof(T).Name}", ex);
}
}
/// <summary>
/// Gets first entity matching predicate with includes or null
/// </summary>
public virtual async Task<T?> FirstOrDefaultAsync(int kingdomId, Expression<Func<T, bool>> predicate,
params Expression<Func<T, object>>[] includes)
{
try
{
_logger.LogDebug("Getting first {EntityType} with {IncludeCount} includes matching predicate for Kingdom {KingdomId}",
typeof(T).Name, includes.Length, kingdomId);
var query = _dbSet.Where(e => e.KingdomId == kingdomId);
// Apply includes
foreach (var include in includes)
{
query = query.Include(include);
}
var entity = await query.FirstOrDefaultAsync(predicate);
if (entity == null)
{
_logger.LogDebug("No {EntityType} with includes found matching predicate for Kingdom {KingdomId}", typeof(T).Name, kingdomId);
}
return entity;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting first {EntityType} with includes for Kingdom {KingdomId}", typeof(T).Name, kingdomId);
throw new InvalidOperationException($"Failed to get first {typeof(T).Name} with includes", ex);
}
}
/// <summary>
/// Checks if any entity matches predicate in kingdom
/// </summary>
public virtual async Task<bool> AnyAsync(int kingdomId, Expression<Func<T, bool>>? predicate = null)
{
try
{
_logger.LogDebug("Checking if any {EntityType} exists for Kingdom {KingdomId} with predicate: {HasPredicate}",
typeof(T).Name, kingdomId, predicate != null);
var query = _dbSet.Where(e => e.KingdomId == kingdomId);
bool result = predicate != null
? await query.AnyAsync(predicate)
: await query.AnyAsync();
_logger.LogDebug("Any {EntityType} exists for Kingdom {KingdomId}: {Result}", typeof(T).Name, kingdomId, result);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error checking if any {EntityType} exists for Kingdom {KingdomId}", typeof(T).Name, kingdomId);
throw new InvalidOperationException($"Failed to check existence of {typeof(T).Name}", ex);
}
}
/// <summary>
/// Counts entities matching predicate in kingdom
/// </summary>
public virtual async Task<int> CountAsync(int kingdomId, Expression<Func<T, bool>>? predicate = null)
{
try
{
_logger.LogDebug("Counting {EntityType} entities for Kingdom {KingdomId} with predicate: {HasPredicate}",
typeof(T).Name, kingdomId, predicate != null);
var query = _dbSet.Where(e => e.KingdomId == kingdomId);
int count = predicate != null
? await query.CountAsync(predicate)
: await query.CountAsync();
_logger.LogDebug("Count of {EntityType} for Kingdom {KingdomId}: {Count}", typeof(T).Name, kingdomId, count);
return count;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error counting {EntityType} entities for Kingdom {KingdomId}", typeof(T).Name, kingdomId);
throw new InvalidOperationException($"Failed to count {typeof(T).Name} entities", ex);
}
}
/// <summary>
/// Gets paginated results with kingdom scoping
/// </summary>
public virtual async Task<(IEnumerable<T> Items, int TotalCount)> GetPagedAsync(int kingdomId, int page, int pageSize,
Expression<Func<T, bool>>? filter = null, Expression<Func<T, object>>? orderBy = null, bool ascending = true)
{
try
{
_logger.LogDebug("Getting paged {EntityType} for Kingdom {KingdomId}: Page {Page}, Size {PageSize}",
typeof(T).Name, kingdomId, page, pageSize);
if (page < 1) page = 1;
if (pageSize < 1) pageSize = 10;
if (pageSize > 1000) pageSize = 1000; // Prevent excessive page sizes
var query = _dbSet.Where(e => e.KingdomId == kingdomId);
if (filter != null)
{
query = query.Where(filter);
}
var totalCount = await query.CountAsync();
if (orderBy != null)
{
query = ascending ? query.OrderBy(orderBy) : query.OrderByDescending(orderBy);
}
var items = await query
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
_logger.LogDebug("Retrieved {ItemCount} of {TotalCount} {EntityType} entities for Kingdom {KingdomId}",
items.Count, totalCount, typeof(T).Name, kingdomId);
return (items, totalCount);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting paged {EntityType} for Kingdom {KingdomId}", typeof(T).Name, kingdomId);
throw new InvalidOperationException($"Failed to get paged {typeof(T).Name} results", ex);
}
}
/// <summary>
/// Adds new entity with kingdom validation
/// </summary>
public virtual async Task<T> AddAsync(T entity)
{
try
{
if (entity == null)
throw new ArgumentNullException(nameof(entity));
_logger.LogDebug("Adding new {EntityType} to Kingdom {KingdomId}", typeof(T).Name, entity.KingdomId);
// Validate kingdom ID is set
if (entity.KingdomId <= 0)
{
throw new InvalidOperationException($"KingdomId must be set before adding {typeof(T).Name}");
}
var addedEntity = await _dbSet.AddAsync(entity);
_logger.LogDebug("Successfully added {EntityType} to Kingdom {KingdomId}", typeof(T).Name, entity.KingdomId);
return addedEntity.Entity;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error adding {EntityType} to Kingdom {KingdomId}", typeof(T).Name, entity?.KingdomId ?? 0);
throw new InvalidOperationException($"Failed to add {typeof(T).Name}", ex);
}
}
/// <summary>
/// Adds multiple entities with kingdom validation
/// </summary>
public virtual async Task AddRangeAsync(IEnumerable<T> entities)
{
try
{
if (entities == null)
throw new ArgumentNullException(nameof(entities));
var entitiesList = entities.ToList();
if (!entitiesList.Any())
return;
_logger.LogDebug("Adding {Count} {EntityType} entities", entitiesList.Count, typeof(T).Name);
// Validate all entities have kingdom ID set
foreach (var entity in entitiesList)
{
if (entity.KingdomId <= 0)
{
throw new InvalidOperationException($"All entities must have KingdomId set before adding {typeof(T).Name}");
}
}
await _dbSet.AddRangeAsync(entitiesList);
_logger.LogDebug("Successfully added {Count} {EntityType} entities", entitiesList.Count, typeof(T).Name);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error adding multiple {EntityType} entities", typeof(T).Name);
throw new InvalidOperationException($"Failed to add multiple {typeof(T).Name} entities", ex);
}
}
/// <summary>
/// Updates existing entity with kingdom validation
/// </summary>
public virtual async Task UpdateAsync(T entity)
{
try
{
if (entity == null)
throw new ArgumentNullException(nameof(entity));
_logger.LogDebug("Updating {EntityType} in Kingdom {KingdomId}", typeof(T).Name, entity.KingdomId);
// Validate kingdom ID is set
if (entity.KingdomId <= 0)
{
throw new InvalidOperationException($"KingdomId must be set before updating {typeof(T).Name}");
}
_context.Entry(entity).State = EntityState.Modified;
_logger.LogDebug("Successfully updated {EntityType} in Kingdom {KingdomId}", typeof(T).Name, entity.KingdomId);
await Task.CompletedTask; // Keep async signature consistent
}
catch (Exception ex)
{
_logger.LogError(ex, "Error updating {EntityType} in Kingdom {KingdomId}", typeof(T).Name, entity?.KingdomId ?? 0);
throw new InvalidOperationException($"Failed to update {typeof(T).Name}", ex);
}
}
/// <summary>
/// Updates multiple entities with kingdom validation
/// </summary>
public virtual async Task UpdateRangeAsync(IEnumerable<T> entities)
{
try
{
if (entities == null)
throw new ArgumentNullException(nameof(entities));
var entitiesList = entities.ToList();
if (!entitiesList.Any())
return;
_logger.LogDebug("Updating {Count} {EntityType} entities", entitiesList.Count, typeof(T).Name);
// Validate all entities have kingdom ID set
foreach (var entity in entitiesList)
{
if (entity.KingdomId <= 0)
{
throw new InvalidOperationException($"All entities must have KingdomId set before updating {typeof(T).Name}");
}
}
_dbSet.UpdateRange(entitiesList);
_logger.LogDebug("Successfully updated {Count} {EntityType} entities", entitiesList.Count, typeof(T).Name);
await Task.CompletedTask; // Keep async signature consistent
}
catch (Exception ex)
{
_logger.LogError(ex, "Error updating multiple {EntityType} entities", typeof(T).Name);
throw new InvalidOperationException($"Failed to update multiple {typeof(T).Name} entities", ex);
}
}
/// <summary>
/// Removes entity with kingdom validation
/// </summary>
public virtual async Task RemoveAsync(T entity)
{
try
{
if (entity == null)
throw new ArgumentNullException(nameof(entity));
_logger.LogDebug("Removing {EntityType} from Kingdom {KingdomId}", typeof(T).Name, entity.KingdomId);
_dbSet.Remove(entity);
_logger.LogDebug("Successfully removed {EntityType} from Kingdom {KingdomId}", typeof(T).Name, entity.KingdomId);
await Task.CompletedTask; // Keep async signature consistent
}
catch (Exception ex)
{
_logger.LogError(ex, "Error removing {EntityType} from Kingdom {KingdomId}", typeof(T).Name, entity?.KingdomId ?? 0);
throw new InvalidOperationException($"Failed to remove {typeof(T).Name}", ex);
}
}
/// <summary>
/// Removes entity by ID with kingdom validation
/// </summary>
public virtual async Task RemoveByIdAsync(K id, int kingdomId)
{
try
{
_logger.LogDebug("Removing {EntityType} with ID {Id} from Kingdom {KingdomId}", typeof(T).Name, id, kingdomId);
var entity = await GetByIdAsync(id, kingdomId);
if (entity != null)
{
await RemoveAsync(entity);
_logger.LogDebug("Successfully removed {EntityType} with ID {Id} from Kingdom {KingdomId}", typeof(T).Name, id, kingdomId);
}
else
{
_logger.LogWarning("Attempted to remove non-existent {EntityType} with ID {Id} from Kingdom {KingdomId}",
typeof(T).Name, id, kingdomId);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error removing {EntityType} with ID {Id} from Kingdom {KingdomId}", typeof(T).Name, id, kingdomId);
throw new InvalidOperationException($"Failed to remove {typeof(T).Name} with ID {id}", ex);
}
}
/// <summary>
/// Removes multiple entities with kingdom validation
/// </summary>
public virtual async Task RemoveRangeAsync(IEnumerable<T> entities)
{
try
{
if (entities == null)
throw new ArgumentNullException(nameof(entities));
var entitiesList = entities.ToList();
if (!entitiesList.Any())
return;
_logger.LogDebug("Removing {Count} {EntityType} entities", entitiesList.Count, typeof(T).Name);
_dbSet.RemoveRange(entitiesList);
_logger.LogDebug("Successfully removed {Count} {EntityType} entities", entitiesList.Count, typeof(T).Name);
await Task.CompletedTask; // Keep async signature consistent
}
catch (Exception ex)
{
_logger.LogError(ex, "Error removing multiple {EntityType} entities", typeof(T).Name);
throw new InvalidOperationException($"Failed to remove multiple {typeof(T).Name} entities", ex);
}
}
/// <summary>
/// Gets queryable for advanced querying with kingdom scoping
/// WARNING: Use with caution - ensure kingdom filtering is applied in queries
/// </summary>
public virtual IQueryable<T> GetQueryable(int kingdomId)
{
_logger.LogDebug("Getting queryable for {EntityType} in Kingdom {KingdomId}", typeof(T).Name, kingdomId);
return _dbSet.Where(e => e.KingdomId == kingdomId);
}
/// <summary>
/// Saves changes to database
/// </summary>
public virtual async Task<int> SaveChangesAsync()
{
try
{
_logger.LogDebug("Saving changes to database");
var result = await _context.SaveChangesAsync();
_logger.LogDebug("Successfully saved {Count} changes to database", result);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error saving changes to database");
throw new InvalidOperationException("Failed to save changes to database", ex);
}
}
}
}

View File

@ -7,14 +7,9 @@
</PropertyGroup>
<ItemGroup>
<Folder Include="Contexts\" />
<Folder Include="Configurations\" />
<Folder Include="Migrations\" />
<Folder Include="Seeds\" />
<Folder Include="Repositories\Alliance\" />
<Folder Include="Repositories\Kingdom\" />
<Folder Include="Repositories\Combat\" />
<Folder Include="Repositories\Player\" />
</ItemGroup>
<ItemGroup>

View File

@ -0,0 +1,976 @@
/*
* File: ShadowedRealms.Data/UnitOfWork.cs
* Created: 2025-10-19
* Last Modified: 2025-10-19
* Description: Unit of Work implementation providing transaction coordination across all repositories,
* lifecycle management, and bulk operations with kingdom-scoped security integration.
* Last Edit Notes: Initial implementation with complete repository coordination and transaction management.
*/
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.Logging;
using ShadowedRealms.Core.Interfaces;
using ShadowedRealms.Core.Interfaces.Repositories;
using ShadowedRealms.Core.Models;
using ShadowedRealms.Core.Models.Combat;
using ShadowedRealms.Core.Models.Purchase;
using ShadowedRealms.Data.Contexts;
using ShadowedRealms.Data.Repositories.Alliance;
using ShadowedRealms.Data.Repositories.Combat;
using ShadowedRealms.Data.Repositories.Kingdom;
using ShadowedRealms.Data.Repositories.Player;
using ShadowedRealms.Data.Repositories.Purchase;
namespace ShadowedRealms.Data
{
/// <summary>
/// Unit of Work implementation providing centralized transaction management and repository coordination.
/// Ensures data consistency across all game systems while maintaining kingdom-scoped security.
/// </summary>
public class UnitOfWork : IUnitOfWork, IDisposable
{
private readonly GameDbContext _context;
private readonly ILogger<UnitOfWork> _logger;
private IDbContextTransaction? _transaction;
private bool _disposed = false;
// Repository instances - lazy loaded
private IKingdomRepository? _kingdomRepository;
private IPlayerRepository? _playerRepository;
private IAllianceRepository? _allianceRepository;
private ICombatLogRepository? _combatLogRepository;
private IPurchaseLogRepository? _purchaseLogRepository;
public UnitOfWork(GameDbContext context, ILogger<UnitOfWork> logger)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
#region Repository Properties
/// <summary>
/// Gets the Kingdom repository instance
/// </summary>
public IKingdomRepository KingdomRepository
{
get
{
_kingdomRepository ??= new KingdomRepository(_context,
_logger.CreateLogger<KingdomRepository>());
return _kingdomRepository;
}
}
/// <summary>
/// Gets the Player repository instance
/// </summary>
public IPlayerRepository PlayerRepository
{
get
{
_playerRepository ??= new PlayerRepository(_context,
_logger.CreateLogger<PlayerRepository>());
return _playerRepository;
}
}
/// <summary>
/// Gets the Alliance repository instance
/// </summary>
public IAllianceRepository AllianceRepository
{
get
{
_allianceRepository ??= new AllianceRepository(_context,
_logger.CreateLogger<AllianceRepository>());
return _allianceRepository;
}
}
/// <summary>
/// Gets the CombatLog repository instance
/// </summary>
public ICombatLogRepository CombatLogRepository
{
get
{
_combatLogRepository ??= new CombatLogRepository(_context,
_logger.CreateLogger<CombatLogRepository>());
return _combatLogRepository;
}
}
/// <summary>
/// Gets the PurchaseLog repository instance
/// </summary>
public IPurchaseLogRepository PurchaseLogRepository
{
get
{
_purchaseLogRepository ??= new PurchaseLogRepository(_context,
_logger.CreateLogger<PurchaseLogRepository>());
return _purchaseLogRepository;
}
}
#endregion
#region Transaction Management
/// <summary>
/// Begins a new database transaction for coordinated operations
/// </summary>
public async Task BeginTransactionAsync()
{
try
{
if (_transaction != null)
{
_logger.LogWarning("Transaction already active - disposing existing transaction before beginning new one");
await _transaction.DisposeAsync();
}
_transaction = await _context.Database.BeginTransactionAsync();
_logger.LogDebug("Database transaction started: {TransactionId}", _transaction.TransactionId);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error beginning database transaction");
throw new InvalidOperationException("Failed to begin database transaction", ex);
}
}
/// <summary>
/// Commits the current transaction and saves all changes
/// </summary>
public async Task CommitTransactionAsync()
{
try
{
if (_transaction == null)
{
throw new InvalidOperationException("No active transaction to commit");
}
await _context.SaveChangesAsync();
await _transaction.CommitAsync();
_logger.LogDebug("Database transaction committed successfully: {TransactionId}", _transaction.TransactionId);
await _transaction.DisposeAsync();
_transaction = null;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error committing database transaction: {TransactionId}", _transaction?.TransactionId);
await RollbackTransactionAsync();
throw new InvalidOperationException("Failed to commit database transaction", ex);
}
}
/// <summary>
/// Rolls back the current transaction and discards all changes
/// </summary>
public async Task RollbackTransactionAsync()
{
try
{
if (_transaction == null)
{
_logger.LogWarning("No active transaction to rollback");
return;
}
await _transaction.RollbackAsync();
_logger.LogWarning("Database transaction rolled back: {TransactionId}", _transaction.TransactionId);
await _transaction.DisposeAsync();
_transaction = null;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error rolling back database transaction: {TransactionId}", _transaction?.TransactionId);
throw new InvalidOperationException("Failed to rollback database transaction", ex);
}
}
/// <summary>
/// Executes an operation within a transaction with automatic rollback on failure
/// </summary>
public async Task<T> ExecuteInTransactionAsync<T>(Func<Task<T>> operation)
{
var transactionStartedHere = _transaction == null;
try
{
if (transactionStartedHere)
{
await BeginTransactionAsync();
}
_logger.LogDebug("Executing operation within transaction: {TransactionId}", _transaction?.TransactionId);
var result = await operation();
if (transactionStartedHere)
{
await CommitTransactionAsync();
}
_logger.LogDebug("Operation completed successfully within transaction");
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error executing operation within transaction");
if (transactionStartedHere && _transaction != null)
{
await RollbackTransactionAsync();
}
throw;
}
}
/// <summary>
/// Executes an operation within a transaction with automatic rollback on failure (no return value)
/// </summary>
public async Task ExecuteInTransactionAsync(Func<Task> operation)
{
await ExecuteInTransactionAsync(async () =>
{
await operation();
return Task.CompletedTask;
});
}
#endregion
#region Bulk Operations
/// <summary>
/// Performs bulk insert operations across multiple entity types
/// </summary>
public async Task<int> BulkInsertAsync<T>(IEnumerable<T> entities, int kingdomId) where T : class, IKingdomScoped
{
try
{
var entitiesList = entities.ToList();
if (!entitiesList.Any())
{
return 0;
}
_logger.LogDebug("Performing bulk insert of {Count} {EntityType} entities for Kingdom {KingdomId}",
entitiesList.Count, typeof(T).Name, kingdomId);
// Validate all entities belong to the correct kingdom
var invalidEntities = entitiesList.Where(e => e.KingdomId != kingdomId).ToList();
if (invalidEntities.Any())
{
throw new InvalidOperationException($"Found {invalidEntities.Count} entities with incorrect KingdomId. Expected: {kingdomId}");
}
await _context.Set<T>().AddRangeAsync(entitiesList);
var result = await _context.SaveChangesAsync();
_logger.LogInformation("Bulk insert completed: {Count} {EntityType} entities added for Kingdom {KingdomId}",
result, typeof(T).Name, kingdomId);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error performing bulk insert of {EntityType} for Kingdom {KingdomId}", typeof(T).Name, kingdomId);
throw new InvalidOperationException($"Failed to bulk insert {typeof(T).Name} entities", ex);
}
}
/// <summary>
/// Performs bulk update operations across multiple entity types
/// </summary>
public async Task<int> BulkUpdateAsync<T>(IEnumerable<T> entities, int kingdomId) where T : class, IKingdomScoped
{
try
{
var entitiesList = entities.ToList();
if (!entitiesList.Any())
{
return 0;
}
_logger.LogDebug("Performing bulk update of {Count} {EntityType} entities for Kingdom {KingdomId}",
entitiesList.Count, typeof(T).Name, kingdomId);
// Validate all entities belong to the correct kingdom
var invalidEntities = entitiesList.Where(e => e.KingdomId != kingdomId).ToList();
if (invalidEntities.Any())
{
throw new InvalidOperationException($"Found {invalidEntities.Count} entities with incorrect KingdomId. Expected: {kingdomId}");
}
_context.Set<T>().UpdateRange(entitiesList);
var result = await _context.SaveChangesAsync();
_logger.LogInformation("Bulk update completed: {Count} {EntityType} entities updated for Kingdom {KingdomId}",
result, typeof(T).Name, kingdomId);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error performing bulk update of {EntityType} for Kingdom {KingdomId}", typeof(T).Name, kingdomId);
throw new InvalidOperationException($"Failed to bulk update {typeof(T).Name} entities", ex);
}
}
/// <summary>
/// Performs bulk delete operations with kingdom validation
/// </summary>
public async Task<int> BulkDeleteAsync<T>(IEnumerable<T> entities, int kingdomId) where T : class, IKingdomScoped
{
try
{
var entitiesList = entities.ToList();
if (!entitiesList.Any())
{
return 0;
}
_logger.LogDebug("Performing bulk delete of {Count} {EntityType} entities for Kingdom {KingdomId}",
entitiesList.Count, typeof(T).Name, kingdomId);
// Validate all entities belong to the correct kingdom
var invalidEntities = entitiesList.Where(e => e.KingdomId != kingdomId).ToList();
if (invalidEntities.Any())
{
throw new InvalidOperationException($"Found {invalidEntities.Count} entities with incorrect KingdomId. Expected: {kingdomId}");
}
_context.Set<T>().RemoveRange(entitiesList);
var result = await _context.SaveChangesAsync();
_logger.LogInformation("Bulk delete completed: {Count} {EntityType} entities removed for Kingdom {KingdomId}",
result, typeof(T).Name, kingdomId);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error performing bulk delete of {EntityType} for Kingdom {KingdomId}", typeof(T).Name, kingdomId);
throw new InvalidOperationException($"Failed to bulk delete {typeof(T).Name} entities", ex);
}
}
#endregion
#region Complex Cross-Repository Operations
/// <summary>
/// Creates a new player with complete initialization across multiple systems
/// </summary>
public async Task<Core.Models.Player> CreatePlayerAsync(string playerName, int kingdomId, string email = "")
{
return await ExecuteInTransactionAsync(async () =>
{
_logger.LogInformation("Creating new player: {PlayerName} in Kingdom {KingdomId}", playerName, kingdomId);
// Validate kingdom exists and can accept new players
var kingdom = await KingdomRepository.GetByIdAsync(kingdomId, kingdomId);
if (kingdom == null)
{
throw new InvalidOperationException($"Kingdom {kingdomId} not found");
}
var population = await KingdomRepository.GetPopulationAsync(kingdomId);
if (population >= 1500)
{
throw new InvalidOperationException($"Kingdom {kingdomId} is at maximum capacity ({population}/1500)");
}
// Create player entity
var player = new Core.Models.Player
{
PlayerName = playerName,
Email = email,
KingdomId = kingdomId,
CastleLevel = 1,
Power = 100, // Starting power
VipTier = 0,
TotalSpent = 0,
IsActive = true,
CreatedAt = DateTime.UtcNow,
LastActivity = DateTime.UtcNow,
TeleportCount = 0,
AttackWins = 0,
AttackLosses = 0,
DefenseWins = 0,
DefenseLosses = 0
};
var createdPlayer = await PlayerRepository.AddAsync(player);
await _context.SaveChangesAsync();
_logger.LogInformation("Successfully created player: {PlayerId} ({PlayerName}) in Kingdom {KingdomId}",
createdPlayer.Id, playerName, kingdomId);
return createdPlayer;
});
}
/// <summary>
/// Processes combat resolution with integrated player stat updates and logging
/// </summary>
public async Task<CombatLog> ProcessCombatResolutionAsync(int combatLogId, int kingdomId)
{
return await ExecuteInTransactionAsync(async () =>
{
_logger.LogInformation("Processing combat resolution for CombatLog {CombatLogId} in Kingdom {KingdomId}",
combatLogId, kingdomId);
// Resolve the combat
var combatLog = await CombatLogRepository.ResolveFieldInterceptionAsync(combatLogId, kingdomId);
// Update player statistics based on combat results
var attackerUpdated = await PlayerRepository.RecordCombatParticipationAsync(
combatLog.AttackerPlayerId, kingdomId, true, combatLog.Winner == "Attacker",
combatLog.Winner == "Attacker" ? combatLog.PowerGained ?? 0 : 0,
combatLog.Winner == "Attacker" ? 0 : combatLog.PowerLost ?? 0
);
var defenderUpdated = await PlayerRepository.RecordCombatParticipationAsync(
combatLog.DefenderPlayerId, kingdomId, false, combatLog.Winner == "Defender",
combatLog.Winner == "Defender" ? combatLog.PowerGained ?? 0 : 0,
combatLog.Winner == "Defender" ? 0 : combatLog.PowerLost ?? 0
);
if (!attackerUpdated || !defenderUpdated)
{
throw new InvalidOperationException("Failed to update player combat statistics");
}
await _context.SaveChangesAsync();
_logger.LogInformation("Combat resolution completed for CombatLog {CombatLogId}: Winner {Winner}, Duration {Duration}min",
combatLogId, combatLog.Winner, combatLog.BattleDuration);
return combatLog;
});
}
/// <summary>
/// Processes purchase with VIP tier updates and anti-pay-to-win monitoring
/// </summary>
public async Task<(PurchaseLog Purchase, bool VipTierChanged, Dictionary<string, object> BalanceAlert)> ProcessPurchaseAsync(
int playerId, int kingdomId, decimal amount, string purchaseType, string transactionId,
Dictionary<string, object> purchaseDetails)
{
return await ExecuteInTransactionAsync(async () =>
{
_logger.LogInformation("Processing purchase for Player {PlayerId}: Amount {Amount}, Type {PurchaseType}",
playerId, amount, purchaseType);
// Record the purchase
var purchase = await PurchaseLogRepository.RecordPurchaseAsync(playerId, kingdomId, amount,
purchaseType, transactionId, purchaseDetails);
// Update VIP tier and check for changes
var player = await PlayerRepository.GetByIdAsync(playerId, kingdomId);
if (player == null)
{
throw new InvalidOperationException($"Player {playerId} not found");
}
var oldVipTier = player.VipTier;
var (tierUpdated, newTier, chargebackRisk) = await PlayerRepository.UpdateVipTierAsync(
playerId, kingdomId, amount);
// Check for pay-to-win concerns
var balanceAlert = new Dictionary<string, object>();
if (amount > 100) // Monitor large purchases
{
var payToWinAnalysis = await PurchaseLogRepository.AnalyzePayToWinImpactAsync(playerId, kingdomId, 30);
var riskLevel = ((Dictionary<string, object>)payToWinAnalysis["PayToWinRisk"]);
if (riskLevel.ToString() == "High Risk" || riskLevel.ToString() == "Critical Risk")
{
balanceAlert["HasConcern"] = true;
balanceAlert["RiskLevel"] = riskLevel;
balanceAlert["RecommendedAction"] = "Monitor player combat performance closely";
_logger.LogWarning("Pay-to-win concern flagged for Player {PlayerId}: Risk level {RiskLevel}",
playerId, riskLevel);
}
}
await _context.SaveChangesAsync();
_logger.LogInformation("Purchase processed successfully for Player {PlayerId}: PurchaseLog {PurchaseLogId}, VIP {OldTier}->{NewTier}",
playerId, purchase.Id, oldVipTier, newTier);
return (purchase, tierUpdated, balanceAlert);
});
}
/// <summary>
/// Creates alliance coalition for KvK events with validation and coordination
/// </summary>
public async Task<bool> CreateAllianceCoalitionAsync(int kingdomId, IEnumerable<int> allianceIds,
string coalitionName, int leadAllianceId)
{
return await ExecuteInTransactionAsync(async () =>
{
_logger.LogInformation("Creating alliance coalition '{CoalitionName}' in Kingdom {KingdomId}",
coalitionName, kingdomId);
// Validate kingdom is not already in KvK
var kingdom = await KingdomRepository.GetByIdAsync(kingdomId, kingdomId);
if (kingdom?.IsInKvK == true)
{
throw new InvalidOperationException($"Kingdom {kingdomId} is already participating in KvK");
}
// Create the coalition
var coalitionCreated = await AllianceRepository.CreateCoalitionAsync(kingdomId, allianceIds,
coalitionName, leadAllianceId);
if (!coalitionCreated)
{
throw new InvalidOperationException("Failed to create alliance coalition");
}
// Award experience to participating alliances
foreach (var allianceId in allianceIds)
{
await AllianceRepository.AwardExperienceAsync(allianceId, kingdomId, 500, "Coalition Formation");
}
await _context.SaveChangesAsync();
_logger.LogInformation("Successfully created alliance coalition '{CoalitionName}' with {AllianceCount} alliances",
coalitionName, allianceIds.Count());
return true;
});
}
/// <summary>
/// Executes kingdom merger with comprehensive data migration
/// </summary>
public async Task<bool> ExecuteKingdomMergerAsync(int sourceKingdomId, int targetKingdomId)
{
return await ExecuteInTransactionAsync(async () =>
{
_logger.LogInformation("Executing kingdom merger: {SourceKingdom} -> {TargetKingdom}",
sourceKingdomId, targetKingdomId);
// Validate merger compatibility
var compatible = await KingdomRepository.AreKingdomsCompatibleForMergerAsync(sourceKingdomId, targetKingdomId);
if (!compatible)
{
throw new InvalidOperationException($"Kingdoms {sourceKingdomId} and {targetKingdomId} are not compatible for merger");
}
// Execute the merger
var mergerResult = await KingdomRepository.ExecuteKingdomMergerAsync(sourceKingdomId, targetKingdomId);
if (!mergerResult)
{
throw new InvalidOperationException("Kingdom merger execution failed");
}
// Migrate combat logs to target kingdom
var combatLogs = await _context.CombatLogs
.Where(c => c.KingdomId == sourceKingdomId)
.ToListAsync();
foreach (var log in combatLogs)
{
log.KingdomId = targetKingdomId;
}
// Migrate purchase logs to target kingdom
var purchaseLogs = await _context.PurchaseLogs
.Where(p => p.KingdomId == sourceKingdomId)
.ToListAsync();
foreach (var log in purchaseLogs)
{
log.KingdomId = targetKingdomId;
}
await _context.SaveChangesAsync();
_logger.LogInformation("Kingdom merger completed successfully: {SourceKingdom} merged into {TargetKingdom}",
sourceKingdomId, targetKingdomId);
return true;
});
}
#endregion
#region Data Consistency and Maintenance
/// <summary>
/// Validates data consistency across all repositories for a kingdom
/// </summary>
public async Task<Dictionary<string, object>> ValidateDataConsistencyAsync(int kingdomId)
{
try
{
_logger.LogDebug("Validating data consistency for Kingdom {KingdomId}", kingdomId);
var issues = new List<string>();
var statistics = new Dictionary<string, object>();
// Validate kingdom exists
var kingdom = await KingdomRepository.GetByIdAsync(kingdomId, kingdomId);
if (kingdom == null)
{
issues.Add($"Kingdom {kingdomId} not found");
return new Dictionary<string, object> { ["Issues"] = issues };
}
// Validate player-alliance relationships
var playersWithInvalidAlliances = await _context.Players
.Where(p => p.KingdomId == kingdomId && p.AllianceId.HasValue)
.Where(p => !_context.Alliances.Any(a => a.Id == p.AllianceId && a.KingdomId == kingdomId))
.CountAsync();
if (playersWithInvalidAlliances > 0)
{
issues.Add($"{playersWithInvalidAlliances} players have invalid alliance references");
}
// Validate combat log player references
var combatLogsWithInvalidPlayers = await _context.CombatLogs
.Where(c => c.KingdomId == kingdomId)
.Where(c => !_context.Players.Any(p => p.Id == c.AttackerPlayerId && p.KingdomId == kingdomId) ||
!_context.Players.Any(p => p.Id == c.DefenderPlayerId && p.KingdomId == kingdomId))
.CountAsync();
if (combatLogsWithInvalidPlayers > 0)
{
issues.Add($"{combatLogsWithInvalidPlayers} combat logs have invalid player references");
}
// Validate purchase log player references
var purchaseLogsWithInvalidPlayers = await _context.PurchaseLogs
.Where(p => p.KingdomId == kingdomId)
.Where(p => !_context.Players.Any(player => player.Id == p.PlayerId && player.KingdomId == kingdomId))
.CountAsync();
if (purchaseLogsWithInvalidPlayers > 0)
{
issues.Add($"{purchaseLogsWithInvalidPlayers} purchase logs have invalid player references");
}
// Validate VIP tier consistency
var playersWithInconsistentVip = await _context.Players
.Where(p => p.KingdomId == kingdomId)
.Where(p => p.VipTier != CalculateExpectedVipTier(p.TotalSpent))
.CountAsync();
if (playersWithInconsistentVip > 0)
{
issues.Add($"{playersWithInconsistentVip} players have inconsistent VIP tiers");
}
// Gather statistics
statistics["TotalPlayers"] = await _context.Players.CountAsync(p => p.KingdomId == kingdomId);
statistics["TotalAlliances"] = await _context.Alliances.CountAsync(a => a.KingdomId == kingdomId);
statistics["TotalCombatLogs"] = await _context.CombatLogs.CountAsync(c => c.KingdomId == kingdomId);
statistics["TotalPurchaseLogs"] = await _context.PurchaseLogs.CountAsync(p => p.KingdomId == kingdomId);
var result = new Dictionary<string, object>
{
["KingdomId"] = kingdomId,
["IsConsistent"] = !issues.Any(),
["Issues"] = issues,
["Statistics"] = statistics,
["ValidationTimestamp"] = DateTime.UtcNow
};
_logger.LogInformation("Data consistency validation completed for Kingdom {KingdomId}: {IssueCount} issues found",
kingdomId, issues.Count);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error validating data consistency for Kingdom {KingdomId}", kingdomId);
throw new InvalidOperationException($"Failed to validate data consistency for Kingdom {kingdomId}", ex);
}
}
/// <summary>
/// Performs cleanup and maintenance operations for a kingdom
/// </summary>
public async Task<Dictionary<string, object>> PerformMaintenanceAsync(int kingdomId, bool dryRun = true)
{
return await ExecuteInTransactionAsync(async () =>
{
_logger.LogInformation("Performing maintenance for Kingdom {KingdomId} (DryRun: {DryRun})", kingdomId, dryRun);
var maintenanceResults = new Dictionary<string, object>();
var cleanedItems = new List<string>();
// Clean up old combat logs (older than 90 days)
var oldCombatLogs = await _context.CombatLogs
.Where(c => c.KingdomId == kingdomId && c.BattleStartTime < DateTime.UtcNow.AddDays(-90))
.ToListAsync();
if (oldCombatLogs.Any())
{
if (!dryRun)
{
_context.CombatLogs.RemoveRange(oldCombatLogs);
}
cleanedItems.Add($"Cleaned {oldCombatLogs.Count} old combat logs (90+ days)");
}
// Clean up inactive players (inactive for 180+ days)
var inactivePlayers = await _context.Players
.Where(p => p.KingdomId == kingdomId && p.IsActive)
.Where(p => !p.LastActivity.HasValue || p.LastActivity < DateTime.UtcNow.AddDays(-180))
.ToListAsync();
if (inactivePlayers.Any())
{
if (!dryRun)
{
foreach (var player in inactivePlayers)
{
player.IsActive = false;
}
}
cleanedItems.Add($"Deactivated {inactivePlayers.Count} inactive players (180+ days)");
}
// Clean up empty alliances (no active members)
var emptyAlliances = await _context.Alliances
.Where(a => a.KingdomId == kingdomId && a.IsActive)
.Where(a => !_context.Players.Any(p => p.AllianceId == a.Id && p.IsActive))
.ToListAsync();
if (emptyAlliances.Any())
{
if (!dryRun)
{
foreach (var alliance in emptyAlliances)
{
alliance.IsActive = false;
}
}
cleanedItems.Add($"Deactivated {emptyAlliances.Count} empty alliances");
}
// Recalculate alliance member counts and total power
var alliances = await _context.Alliances
.Where(a => a.KingdomId == kingdomId && a.IsActive)
.ToListAsync();
var allianceUpdates = 0;
foreach (var alliance in alliances)
{
var actualMemberCount = await _context.Players
.Where(p => p.AllianceId == alliance.Id && p.IsActive)
.CountAsync();
var actualTotalPower = await _context.Players
.Where(p => p.AllianceId == alliance.Id && p.IsActive)
.SumAsync(p => p.Power);
if (alliance.MemberCount != actualMemberCount || alliance.TotalPower != actualTotalPower)
{
if (!dryRun)
{
alliance.MemberCount = actualMemberCount;
alliance.TotalPower = actualTotalPower;
}
allianceUpdates++;
}
}
if (allianceUpdates > 0)
{
cleanedItems.Add($"Updated statistics for {allianceUpdates} alliances");
}
if (!dryRun)
{
await _context.SaveChangesAsync();
}
maintenanceResults["KingdomId"] = kingdomId;
maintenanceResults["DryRun"] = dryRun;
maintenanceResults["CleanedItems"] = cleanedItems;
maintenanceResults["MaintenanceTimestamp"] = DateTime.UtcNow;
_logger.LogInformation("Maintenance completed for Kingdom {KingdomId}: {ItemCount} items processed (DryRun: {DryRun})",
kingdomId, cleanedItems.Count, dryRun);
return maintenanceResults;
});
}
#endregion
#region Save Operations
/// <summary>
/// Saves all pending changes across all repositories
/// </summary>
public async Task<int> SaveChangesAsync()
{
try
{
_logger.LogDebug("Saving changes across all repositories");
var result = await _context.SaveChangesAsync();
_logger.LogDebug("Successfully saved {Count} changes", result);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error saving changes");
throw new InvalidOperationException("Failed to save changes", ex);
}
}
/// <summary>
/// Saves changes with retry logic for handling concurrency conflicts
/// </summary>
public async Task<int> SaveChangesWithRetryAsync(int maxRetries = 3)
{
var attempt = 0;
while (attempt < maxRetries)
{
try
{
return await SaveChangesAsync();
}
catch (DbUpdateConcurrencyException ex)
{
attempt++;
_logger.LogWarning("Concurrency conflict occurred on attempt {Attempt}/{MaxRetries}: {Message}",
attempt, maxRetries, ex.Message);
if (attempt >= maxRetries)
{
_logger.LogError("Maximum retry attempts reached for save operation");
throw;
}
// Wait before retrying
await Task.Delay(TimeSpan.FromMilliseconds(100 * attempt));
// Refresh conflicted entries
foreach (var entry in ex.Entries)
{
await entry.ReloadAsync();
}
}
}
return 0; // Should never reach here
}
#endregion
#region Helper Methods
/// <summary>
/// Calculates expected VIP tier from spending amount
/// </summary>
private int CalculateExpectedVipTier(decimal totalSpent)
{
return totalSpent switch
{
>= 10000 => 15, // Secret VIP 15
>= 5000 => 14, // Secret VIP 14
>= 2500 => 13, // Secret VIP 13
>= 1000 => 12, // VIP 12
>= 500 => 11, // VIP 11
>= 250 => 10, // VIP 10
>= 100 => 9, // VIP 9
>= 75 => 8, // VIP 8
>= 50 => 7, // VIP 7
>= 35 => 6, // VIP 6
>= 25 => 5, // VIP 5
>= 15 => 4, // VIP 4
>= 10 => 3, // VIP 3
>= 5 => 2, // VIP 2
>= 1 => 1, // VIP 1
_ => 0 // Free player
};
}
#endregion
#region Disposal
/// <summary>
/// Disposes of the UnitOfWork and all associated resources
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Protected dispose method
/// </summary>
protected virtual void Dispose(bool disposing)
{
if (!_disposed && disposing)
{
try
{
_transaction?.Dispose();
_context.Dispose();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error disposing UnitOfWork resources");
}
_disposed = true;
_logger.LogDebug("UnitOfWork disposed successfully");
}
}
/// <summary>
/// Async disposal for cleanup of async resources
/// </summary>
public async ValueTask DisposeAsync()
{
if (!_disposed)
{
try
{
if (_transaction != null)
{
await _transaction.DisposeAsync();
}
await _context.DisposeAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error disposing UnitOfWork resources asynchronously");
}
_disposed = true;
_logger.LogDebug("UnitOfWork disposed asynchronously");
}
GC.SuppressFinalize(this);
}
#endregion
}
}