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>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Models\Alliance\" />
|
|
||||||
<Folder Include="Models\Kingdom\" />
|
|
||||||
<Folder Include="Models\Combat\" />
|
|
||||||
<Folder Include="Models\Events\" />
|
<Folder Include="Models\Events\" />
|
||||||
<Folder Include="Models\Player\" />
|
|
||||||
<Folder Include="Interfaces\" />
|
|
||||||
<Folder Include="Enums\" />
|
<Folder Include="Enums\" />
|
||||||
<Folder Include="Constants\" />
|
<Folder Include="Constants\" />
|
||||||
<Folder Include="Extensions\" />
|
<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>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Contexts\" />
|
|
||||||
<Folder Include="Configurations\" />
|
<Folder Include="Configurations\" />
|
||||||
<Folder Include="Migrations\" />
|
<Folder Include="Migrations\" />
|
||||||
<Folder Include="Seeds\" />
|
<Folder Include="Seeds\" />
|
||||||
<Folder Include="Repositories\Alliance\" />
|
|
||||||
<Folder Include="Repositories\Kingdom\" />
|
|
||||||
<Folder Include="Repositories\Combat\" />
|
|
||||||
<Folder Include="Repositories\Player\" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<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