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:
parent
3a45fc73ab
commit
25dbbb5c84
@ -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\" />
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user