From bd3f0caf60a16f4b908f0391fee3f09edee911a7 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Sun, 29 Nov 2020 16:01:52 -0600 Subject: [PATCH] fix memory leak issue related to AddDbContext not working as expected --- Application/Extensions/StartupExtensions.cs | 37 +- .../Factories/DatabaseContextFactory.cs | 32 +- Application/Main.cs | 2 +- .../AdministeredPenaltyResourceQueryHelper.cs | 2 +- .../ReceivedPenaltyResourceQueryHelper.cs | 2 +- .../Meta/UpdatedAliasResourceQueryHelper.cs | 2 +- Application/Misc/MetaService.cs | 4 +- Plugins/Stats/Commands/MostKillsCommand.cs | 55 +- Plugins/Stats/Helpers/StatManager.cs | 781 +++++++++--------- .../Stats/Helpers/StatsResourceQueryHelper.cs | 2 +- Plugins/Stats/Plugin.cs | 20 +- .../Web/StatsWeb/ChatResourceQueryHelper.cs | 2 +- SharedLibraryCore/Database/ContextSeed.cs | 2 +- .../MigrationContext/MySqlDatabaseContext.cs | 2 +- .../PostgresqlDatabaseContext.cs | 2 +- .../MigrationContext/SqliteDatabaseContext.cs | 2 +- SharedLibraryCore/PartialEntities/EFClient.cs | 3 +- .../AuditInformationRepository.cs | 52 +- 18 files changed, 499 insertions(+), 505 deletions(-) diff --git a/Application/Extensions/StartupExtensions.cs b/Application/Extensions/StartupExtensions.cs index 6dd69cd70..764414da7 100644 --- a/Application/Extensions/StartupExtensions.cs +++ b/Application/Extensions/StartupExtensions.cs @@ -48,10 +48,12 @@ namespace IW4MAdmin.Application.Extensions return services; } - public static IServiceCollection AddDatabaseContext(this IServiceCollection services, + public static IServiceCollection AddDatabaseContextOptions(this IServiceCollection services, ApplicationConfiguration appConfig) { - if (string.IsNullOrEmpty(appConfig.ConnectionString) || appConfig.DatabaseProvider == "sqlite") + var activeProvider = appConfig.DatabaseProvider?.ToLower(); + + if (string.IsNullOrEmpty(appConfig.ConnectionString) || activeProvider == "sqlite") { var currentPath = Utilities.OperatingDirectory; currentPath = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) @@ -62,31 +64,34 @@ namespace IW4MAdmin.Application.Extensions {DataSource = Path.Join(currentPath, "Database", "Database.db")}; var connectionString = connectionStringBuilder.ToString(); - services.AddDbContext(options => - options.UseSqlite(connectionString), ServiceLifetime.Transient); + var builder = new DbContextOptionsBuilder() + .UseSqlite(connectionString); + + services.AddSingleton((DbContextOptions) builder.Options); return services; } - switch (appConfig.DatabaseProvider) + switch (activeProvider) { case "mysql": var appendTimeout = !appConfig.ConnectionString.Contains("default command timeout", StringComparison.InvariantCultureIgnoreCase); - services.AddDbContext(options => - options.UseMySql( - appConfig.ConnectionString + (appendTimeout ? ";default command timeout=0" : ""), - mysqlOptions => mysqlOptions.EnableRetryOnFailure()), ServiceLifetime.Transient); - break; + var mysqlBuilder = new DbContextOptionsBuilder() + .UseMySql(appConfig.ConnectionString + (appendTimeout ? ";default command timeout=0" : ""), + mysqlOptions => mysqlOptions.EnableRetryOnFailure()); + services.AddSingleton((DbContextOptions) mysqlBuilder.Options); + return services; case "postgresql": appendTimeout = !appConfig.ConnectionString.Contains("Command Timeout", StringComparison.InvariantCultureIgnoreCase); - services.AddDbContext(options => - options.UseNpgsql(appConfig.ConnectionString + (appendTimeout ? ";Command Timeout=0" : ""), - postgresqlOptions => postgresqlOptions.EnableRetryOnFailure()), ServiceLifetime.Transient); - break; + var postgresqlBuilder = new DbContextOptionsBuilder() + .UseNpgsql(appConfig.ConnectionString + (appendTimeout ? ";Command Timeout=0" : ""), + postgresqlOptions => postgresqlOptions.EnableRetryOnFailure()); + services.AddSingleton((DbContextOptions) postgresqlBuilder.Options); + return services; + default: + throw new ArgumentException($"No context available for {appConfig.DatabaseProvider}"); } - - return services; } } } \ No newline at end of file diff --git a/Application/Factories/DatabaseContextFactory.cs b/Application/Factories/DatabaseContextFactory.cs index 871652a99..87b33f8fb 100644 --- a/Application/Factories/DatabaseContextFactory.cs +++ b/Application/Factories/DatabaseContextFactory.cs @@ -1,7 +1,8 @@ using System; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; +using SharedLibraryCore.Configuration; using SharedLibraryCore.Database; +using SharedLibraryCore.Database.MigrationContext; using SharedLibraryCore.Interfaces; namespace IW4MAdmin.Application.Factories @@ -11,13 +12,15 @@ namespace IW4MAdmin.Application.Factories /// public class DatabaseContextFactory : IDatabaseContextFactory { - private readonly IServiceProvider _serviceProvider; - - public DatabaseContextFactory(IServiceProvider serviceProvider) + private readonly DbContextOptions _contextOptions; + private readonly string _activeProvider; + + public DatabaseContextFactory(ApplicationConfiguration appConfig, DbContextOptions contextOptions) { - _serviceProvider = serviceProvider; + _contextOptions = contextOptions; + _activeProvider = appConfig.DatabaseProvider?.ToLower(); } - + /// /// creates a new database context /// @@ -25,10 +28,10 @@ namespace IW4MAdmin.Application.Factories /// public DatabaseContext CreateContext(bool? enableTracking = true) { - var context = _serviceProvider.GetRequiredService(); + var context = BuildContext(); enableTracking ??= true; - + if (enableTracking.Value) { context.ChangeTracker.AutoDetectChangesEnabled = true; @@ -44,5 +47,16 @@ namespace IW4MAdmin.Application.Factories return context; } + + private DatabaseContext BuildContext() + { + return _activeProvider switch + { + "sqlite" => new SqliteDatabaseContext(_contextOptions), + "mysql" => new MySqlDatabaseContext(_contextOptions), + "postgresql" => new PostgresqlDatabaseContext(_contextOptions), + _ => throw new ArgumentException($"No context found for {_activeProvider}") + }; + } } -} +} \ No newline at end of file diff --git a/Application/Main.cs b/Application/Main.cs index e011618b0..b192ae7cd 100644 --- a/Application/Main.cs +++ b/Application/Main.cs @@ -361,7 +361,7 @@ namespace IW4MAdmin.Application .AddSingleton() .AddSingleton() .AddSingleton(translationLookup) - .AddDatabaseContext(appConfig); + .AddDatabaseContextOptions(appConfig); if (args.Contains("serialevents")) { diff --git a/Application/Meta/AdministeredPenaltyResourceQueryHelper.cs b/Application/Meta/AdministeredPenaltyResourceQueryHelper.cs index 76d9fd76a..616e19dea 100644 --- a/Application/Meta/AdministeredPenaltyResourceQueryHelper.cs +++ b/Application/Meta/AdministeredPenaltyResourceQueryHelper.cs @@ -28,7 +28,7 @@ namespace IW4MAdmin.Application.Meta public async Task> QueryResource(ClientPaginationRequest query) { - using var ctx = _contextFactory.CreateContext(enableTracking: false); + await using var ctx = _contextFactory.CreateContext(enableTracking: false); var iqPenalties = ctx.Penalties.AsNoTracking() .Where(_penalty => query.ClientId == _penalty.PunisherId) diff --git a/Application/Meta/ReceivedPenaltyResourceQueryHelper.cs b/Application/Meta/ReceivedPenaltyResourceQueryHelper.cs index de8b38bf2..f63ad0687 100644 --- a/Application/Meta/ReceivedPenaltyResourceQueryHelper.cs +++ b/Application/Meta/ReceivedPenaltyResourceQueryHelper.cs @@ -31,7 +31,7 @@ namespace IW4MAdmin.Application.Meta public async Task> QueryResource(ClientPaginationRequest query) { var linkedPenaltyType = Utilities.LinkedPenaltyTypes(); - using var ctx = _contextFactory.CreateContext(enableTracking: false); + await using var ctx = _contextFactory.CreateContext(enableTracking: false); var linkId = await ctx.Clients.AsNoTracking() .Where(_client => _client.ClientId == query.ClientId) diff --git a/Application/Meta/UpdatedAliasResourceQueryHelper.cs b/Application/Meta/UpdatedAliasResourceQueryHelper.cs index 0adca9f1c..4765cbf0b 100644 --- a/Application/Meta/UpdatedAliasResourceQueryHelper.cs +++ b/Application/Meta/UpdatedAliasResourceQueryHelper.cs @@ -28,7 +28,7 @@ namespace IW4MAdmin.Application.Meta public async Task> QueryResource(ClientPaginationRequest query) { - using var ctx = _contextFactory.CreateContext(enableTracking: false); + await using var ctx = _contextFactory.CreateContext(enableTracking: false); int linkId = ctx.Clients.First(_client => _client.ClientId == query.ClientId).AliasLinkId; var iqAliasUpdates = ctx.Aliases diff --git a/Application/Misc/MetaService.cs b/Application/Misc/MetaService.cs index 31a621529..fcf0cd48b 100644 --- a/Application/Misc/MetaService.cs +++ b/Application/Misc/MetaService.cs @@ -37,7 +37,7 @@ namespace IW4MAdmin.Application.Misc return; } - using var ctx = _contextFactory.CreateContext(); + await using var ctx = _contextFactory.CreateContext(); var existingMeta = await ctx.EFMeta .Where(_meta => _meta.Key == metaKey) @@ -66,7 +66,7 @@ namespace IW4MAdmin.Application.Misc public async Task GetPersistentMeta(string metaKey, EFClient client) { - using var ctx = _contextFactory.CreateContext(enableTracking: false); + await using var ctx = _contextFactory.CreateContext(enableTracking: false); return await ctx.EFMeta .Where(_meta => _meta.Key == metaKey) diff --git a/Plugins/Stats/Commands/MostKillsCommand.cs b/Plugins/Stats/Commands/MostKillsCommand.cs index bb216562d..71d081ccf 100644 --- a/Plugins/Stats/Commands/MostKillsCommand.cs +++ b/Plugins/Stats/Commands/MostKillsCommand.cs @@ -2,7 +2,6 @@ using System; using System.Linq; using System.Threading.Tasks; - using SharedLibraryCore; using IW4MAdmin.Plugins.Stats.Models; using System.Collections.Generic; @@ -19,7 +18,8 @@ namespace IW4MAdmin.Plugins.Stats.Commands private readonly IDatabaseContextFactory _contextFactory; private readonly CommandConfiguration _config; - public MostKillsCommand(CommandConfiguration config, ITranslationLookup translationLookup, IDatabaseContextFactory contextFactory) : base(config, translationLookup) + public MostKillsCommand(CommandConfiguration config, ITranslationLookup translationLookup, + IDatabaseContextFactory contextFactory) : base(config, translationLookup) { Name = "mostkills"; Description = translationLookup["PLUGINS_STATS_COMMANDS_MOSTKILLS_DESC"]; @@ -32,7 +32,8 @@ namespace IW4MAdmin.Plugins.Stats.Commands public override async Task ExecuteAsync(GameEvent E) { - var mostKills = await GetMostKills(StatManager.GetIdForServer(E.Owner), Plugin.Config.Configuration(), _contextFactory, _translationLookup); + var mostKills = await GetMostKills(StatManager.GetIdForServer(E.Owner), Plugin.Config.Configuration(), + _contextFactory, _translationLookup); if (!E.Message.IsBroadcastCommand(_config.BroadcastCommandPrefix)) { foreach (var stat in mostKills) @@ -50,33 +51,33 @@ namespace IW4MAdmin.Plugins.Stats.Commands } } - public static async Task> GetMostKills(long? serverId, StatsConfiguration config, IDatabaseContextFactory contextFactory, ITranslationLookup translationLookup) + public static async Task> GetMostKills(long? serverId, StatsConfiguration config, + IDatabaseContextFactory contextFactory, ITranslationLookup translationLookup) { - using (var ctx = contextFactory.CreateContext(enableTracking: false)) - { - var dayInPast = DateTime.UtcNow.AddDays(-config.MostKillsMaxInactivityDays); + await using var ctx = contextFactory.CreateContext(enableTracking: false); + var dayInPast = DateTime.UtcNow.AddDays(-config.MostKillsMaxInactivityDays); - var iqStats = (from stats in ctx.Set() - join client in ctx.Clients - on stats.ClientId equals client.ClientId - join alias in ctx.Aliases - on client.CurrentAliasId equals alias.AliasId - where stats.ServerId == serverId - where client.Level != EFClient.Permission.Banned - where client.LastConnection >= dayInPast - orderby stats.Kills descending - select new - { - alias.Name, - stats.Kills - }) - .Take(config.MostKillsClientLimit); + var iqStats = (from stats in ctx.Set() + join client in ctx.Clients + on stats.ClientId equals client.ClientId + join alias in ctx.Aliases + on client.CurrentAliasId equals alias.AliasId + where stats.ServerId == serverId + where client.Level != EFClient.Permission.Banned + where client.LastConnection >= dayInPast + orderby stats.Kills descending + select new + { + alias.Name, + stats.Kills + }) + .Take(config.MostKillsClientLimit); - var iqList = await iqStats.ToListAsync(); + var iqList = await iqStats.ToListAsync(); - return iqList.Select((stats, index) => translationLookup["PLUGINS_STATS_COMMANDS_MOSTKILLS_FORMAT"].FormatExt(index + 1, stats.Name, stats.Kills)) - .Prepend(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_MOSTKILLS_HEADER"]); - } + return iqList.Select((stats, index) => translationLookup["PLUGINS_STATS_COMMANDS_MOSTKILLS_FORMAT"] + .FormatExt(index + 1, stats.Name, stats.Kills)) + .Prepend(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_MOSTKILLS_HEADER"]); } } -} +} \ No newline at end of file diff --git a/Plugins/Stats/Helpers/StatManager.cs b/Plugins/Stats/Helpers/StatManager.cs index 678fa7de2..52dd6de1a 100644 --- a/Plugins/Stats/Helpers/StatManager.cs +++ b/Plugins/Stats/Helpers/StatManager.cs @@ -68,121 +68,118 @@ namespace IW4MAdmin.Plugins.Stats.Helpers /// public async Task GetClientOverallRanking(int clientId) { - using (var context = _contextFactory.CreateContext(enableTracking: false)) + await using var context = _contextFactory.CreateContext(enableTracking: false); + + var clientPerformance = await context.Set() + .Where(r => r.RatingHistory.ClientId == clientId) + .Where(r => r.ServerId == null) + .Where(r => r.Newest) + .Select(r => r.Performance) + .FirstOrDefaultAsync(); + + if (clientPerformance != 0) { - var clientPerformance = await context.Set() - .Where(r => r.RatingHistory.ClientId == clientId) - .Where(r => r.ServerId == null) - .Where(r => r.Newest) - .Select(r => r.Performance) - .FirstOrDefaultAsync(); + var iqClientRanking = context.Set() + .Where(r => r.RatingHistory.ClientId != clientId) + .Where(r => r.Performance > clientPerformance) + .Where(GetRankingFunc()); - if (clientPerformance != 0) - { - var iqClientRanking = context.Set() - .Where(r => r.RatingHistory.ClientId != clientId) - .Where(r => r.Performance > clientPerformance) - .Where(GetRankingFunc()); - - return await iqClientRanking.CountAsync() + 1; - } - - return 0; + return await iqClientRanking.CountAsync() + 1; } + + return 0; } public async Task> GetTopStats(int start, int count, long? serverId = null) { - using (var context = _contextFactory.CreateContext(enableTracking: false)) - { - // setup the query for the clients within the given rating range - var iqClientRatings = (from rating in context.Set() - .Where(GetRankingFunc(serverId)) - select new - { - rating.RatingHistory.ClientId, - rating.RatingHistory.Client.CurrentAlias.Name, - rating.RatingHistory.Client.LastConnection, - rating.Performance, - }) - .OrderByDescending(c => c.Performance) - .Skip(start) - .Take(count); - - // materialized list - var clientRatings = await iqClientRatings.ToListAsync(); - - // get all the unique client ids that are in the top stats - var clientIds = clientRatings - .GroupBy(r => r.ClientId) - .Select(r => r.First().ClientId) - .ToList(); - - var iqRatingInfo = from rating in context.Set() - where clientIds.Contains(rating.RatingHistory.ClientId) - where rating.ServerId == serverId + await using var context = _contextFactory.CreateContext(enableTracking: false); + // setup the query for the clients within the given rating range + var iqClientRatings = (from rating in context.Set() + .Where(GetRankingFunc(serverId)) select new { - rating.Ranking, - rating.Performance, rating.RatingHistory.ClientId, - rating.When - }; + rating.RatingHistory.Client.CurrentAlias.Name, + rating.RatingHistory.Client.LastConnection, + rating.Performance, + }) + .OrderByDescending(c => c.Performance) + .Skip(start) + .Take(count); - var ratingInfo = (await iqRatingInfo.ToListAsync()) - .GroupBy(r => r.ClientId) - .Select(grp => new - { - grp.Key, - Ratings = grp.Select(r => new { r.Performance, r.Ranking, r.When }) - }); + // materialized list + var clientRatings = await iqClientRatings.ToListAsync(); - var iqStatsInfo = (from stat in context.Set() - where clientIds.Contains(stat.ClientId) - where stat.Kills > 0 || stat.Deaths > 0 - where serverId == null ? true : stat.ServerId == serverId - group stat by stat.ClientId into s - select new - { - ClientId = s.Key, - Kills = s.Sum(c => c.Kills), - Deaths = s.Sum(c => c.Deaths), - KDR = s.Sum(c => (c.Kills / (double)(c.Deaths == 0 ? 1 : c.Deaths)) * c.TimePlayed) / s.Sum(c => c.TimePlayed), - TotalTimePlayed = s.Sum(c => c.TimePlayed), - }); - - var topPlayers = await iqStatsInfo.ToListAsync(); - - var clientRatingsDict = clientRatings.ToDictionary(r => r.ClientId); - var finished = topPlayers.Select(s => new TopStatsInfo() - { - ClientId = s.ClientId, - Id = (int?)serverId ?? 0, - Deaths = s.Deaths, - Kills = s.Kills, - KDR = Math.Round(s.KDR, 2), - LastSeen = (DateTime.UtcNow - clientRatingsDict[s.ClientId].LastConnection).HumanizeForCurrentCulture(), - Name = clientRatingsDict[s.ClientId].Name, - Performance = Math.Round(clientRatingsDict[s.ClientId].Performance, 2), - RatingChange = ratingInfo.First(r => r.Key == s.ClientId).Ratings.First().Ranking - ratingInfo.First(r => r.Key == s.ClientId).Ratings.Last().Ranking, - PerformanceHistory = ratingInfo.First(r => r.Key == s.ClientId).Ratings.Count() > 1 ? - ratingInfo.First(r => r.Key == s.ClientId).Ratings.OrderBy(r => r.When).Select(r => r.Performance).ToList() : - new List() { clientRatingsDict[s.ClientId].Performance, clientRatingsDict[s.ClientId].Performance }, - TimePlayed = Math.Round(s.TotalTimePlayed / 3600.0, 1).ToString("#,##0"), - }) - .OrderByDescending(r => r.Performance) + // get all the unique client ids that are in the top stats + var clientIds = clientRatings + .GroupBy(r => r.ClientId) + .Select(r => r.First().ClientId) .ToList(); - // set the ranking numerically - int i = start + 1; - foreach (var stat in finished) - { - stat.Ranking = i; - i++; - } + var iqRatingInfo = from rating in context.Set() + where clientIds.Contains(rating.RatingHistory.ClientId) + where rating.ServerId == serverId + select new + { + rating.Ranking, + rating.Performance, + rating.RatingHistory.ClientId, + rating.When + }; - return finished; + var ratingInfo = (await iqRatingInfo.ToListAsync()) + .GroupBy(r => r.ClientId) + .Select(grp => new + { + grp.Key, + Ratings = grp.Select(r => new { r.Performance, r.Ranking, r.When }) + }); + + var iqStatsInfo = (from stat in context.Set() + where clientIds.Contains(stat.ClientId) + where stat.Kills > 0 || stat.Deaths > 0 + where serverId == null ? true : stat.ServerId == serverId + group stat by stat.ClientId into s + select new + { + ClientId = s.Key, + Kills = s.Sum(c => c.Kills), + Deaths = s.Sum(c => c.Deaths), + KDR = s.Sum(c => (c.Kills / (double)(c.Deaths == 0 ? 1 : c.Deaths)) * c.TimePlayed) / s.Sum(c => c.TimePlayed), + TotalTimePlayed = s.Sum(c => c.TimePlayed), + }); + + var topPlayers = await iqStatsInfo.ToListAsync(); + + var clientRatingsDict = clientRatings.ToDictionary(r => r.ClientId); + var finished = topPlayers.Select(s => new TopStatsInfo() + { + ClientId = s.ClientId, + Id = (int?)serverId ?? 0, + Deaths = s.Deaths, + Kills = s.Kills, + KDR = Math.Round(s.KDR, 2), + LastSeen = (DateTime.UtcNow - clientRatingsDict[s.ClientId].LastConnection).HumanizeForCurrentCulture(), + Name = clientRatingsDict[s.ClientId].Name, + Performance = Math.Round(clientRatingsDict[s.ClientId].Performance, 2), + RatingChange = ratingInfo.First(r => r.Key == s.ClientId).Ratings.First().Ranking - ratingInfo.First(r => r.Key == s.ClientId).Ratings.Last().Ranking, + PerformanceHistory = ratingInfo.First(r => r.Key == s.ClientId).Ratings.Count() > 1 ? + ratingInfo.First(r => r.Key == s.ClientId).Ratings.OrderBy(r => r.When).Select(r => r.Performance).ToList() : + new List() { clientRatingsDict[s.ClientId].Performance, clientRatingsDict[s.ClientId].Performance }, + TimePlayed = Math.Round(s.TotalTimePlayed / 3600.0, 1).ToString("#,##0"), + }) + .OrderByDescending(r => r.Performance) + .ToList(); + + // set the ranking numerically + int i = start + 1; + foreach (var stat in finished) + { + stat.Ranking = i; + i++; } + + return finished; } /// @@ -202,63 +199,61 @@ namespace IW4MAdmin.Plugins.Stats.Helpers long serverId = GetIdForServer(sv); EFServer server; - using (var ctx = _contextFactory.CreateContext(enableTracking: false)) + using var ctx = _contextFactory.CreateContext(enableTracking: false); + var serverSet = ctx.Set(); + // get the server from the database if it exists, otherwise create and insert a new one + server = serverSet.FirstOrDefault(s => s.ServerId == serverId); + + // the server might be using legacy server id + if (server == null) { - var serverSet = ctx.Set(); - // get the server from the database if it exists, otherwise create and insert a new one - server = serverSet.FirstOrDefault(s => s.ServerId == serverId); + server = serverSet.FirstOrDefault(s => s.EndPoint == sv.ToString()); - // the server might be using legacy server id - if (server == null) + if (server != null) { - server = serverSet.FirstOrDefault(s => s.EndPoint == sv.ToString()); - - if (server != null) - { - // this provides a way to identify legacy server entries - server.EndPoint = sv.ToString(); - ctx.Update(server); - ctx.SaveChanges(); - } - } - - // server has never been added before - if (server == null) - { - server = new EFServer() - { - Port = sv.Port, - EndPoint = sv.ToString(), - ServerId = serverId, - GameName = sv.GameName, - HostName = sv.Hostname - }; - - server = serverSet.Add(server).Entity; - // this doesn't need to be async as it's during initialization + // this provides a way to identify legacy server entries + server.EndPoint = sv.ToString(); + ctx.Update(server); ctx.SaveChanges(); } + } - // we want to set the gamename up if it's never been set, or it changed - else if (!server.GameName.HasValue || server.GameName.HasValue && server.GameName.Value != sv.GameName) + // server has never been added before + if (server == null) + { + server = new EFServer() { - server.GameName = sv.GameName; - ctx.Entry(server).Property(_prop => _prop.GameName).IsModified = true; - ctx.SaveChanges(); - } + Port = sv.Port, + EndPoint = sv.ToString(), + ServerId = serverId, + GameName = sv.GameName, + HostName = sv.Hostname + }; - if (server.HostName == null || server.HostName != sv.Hostname) - { - server.HostName = sv.Hostname; - ctx.Entry(server).Property(_prop => _prop.HostName).IsModified = true; - ctx.SaveChanges(); - } - - ctx.Entry(server).Property(_prop => _prop.IsPasswordProtected).IsModified = true; - server.IsPasswordProtected = !string.IsNullOrEmpty(sv.GamePassword); + server = serverSet.Add(server).Entity; + // this doesn't need to be async as it's during initialization ctx.SaveChanges(); } + // we want to set the gamename up if it's never been set, or it changed + else if (!server.GameName.HasValue || server.GameName.HasValue && server.GameName.Value != sv.GameName) + { + server.GameName = sv.GameName; + ctx.Entry(server).Property(_prop => _prop.GameName).IsModified = true; + ctx.SaveChanges(); + } + + if (server.HostName == null || server.HostName != sv.Hostname) + { + server.HostName = sv.Hostname; + ctx.Entry(server).Property(_prop => _prop.HostName).IsModified = true; + ctx.SaveChanges(); + } + + ctx.Entry(server).Property(_prop => _prop.IsPasswordProtected).IsModified = true; + server.IsPasswordProtected = !string.IsNullOrEmpty(sv.GamePassword); + ctx.SaveChanges(); + // check to see if the stats have ever been initialized var serverStats = InitializeServerStats(server.ServerId); @@ -304,79 +299,77 @@ namespace IW4MAdmin.Plugins.Stats.Helpers EFClientStatistics clientStats; - using (var ctx = _contextFactory.CreateContext(enableTracking: false)) + await using var ctx = _contextFactory.CreateContext(enableTracking: false); + var clientStatsSet = ctx.Set(); + clientStats = clientStatsSet + .Include(cl => cl.HitLocations) + .FirstOrDefault(c => c.ClientId == pl.ClientId && c.ServerId == serverId); + + if (clientStats == null) { - var clientStatsSet = ctx.Set(); - clientStats = clientStatsSet - .Include(cl => cl.HitLocations) - .FirstOrDefault(c => c.ClientId == pl.ClientId && c.ServerId == serverId); - - if (clientStats == null) + clientStats = new EFClientStatistics() { - clientStats = new EFClientStatistics() - { - Active = true, - ClientId = pl.ClientId, - Deaths = 0, - Kills = 0, - ServerId = serverId, - Skill = 0.0, - SPM = 0.0, - EloRating = 200.0, - HitLocations = Enum.GetValues(typeof(IW4Info.HitLocation)).OfType() - .Select(hl => new EFHitLocationCount() - { - Active = true, - HitCount = 0, - Location = hl - }).ToList() - }; - - // insert if they've not been added - clientStats = clientStatsSet.Add(clientStats).Entity; - await ctx.SaveChangesAsync(); - } - - pl.SetAdditionalProperty(CLIENT_STATS_KEY, clientStats); - - // migration for previous existing stats - if (clientStats.HitLocations.Count == 0) - { - clientStats.HitLocations = Enum.GetValues(typeof(IW4Info.HitLocation)) - .OfType() + Active = true, + ClientId = pl.ClientId, + Deaths = 0, + Kills = 0, + ServerId = serverId, + Skill = 0.0, + SPM = 0.0, + EloRating = 200.0, + HitLocations = Enum.GetValues(typeof(IW4Info.HitLocation)).OfType() .Select(hl => new EFHitLocationCount() { Active = true, HitCount = 0, Location = hl - }) - .ToList(); + }).ToList() + }; - ctx.Update(clientStats); - await ctx.SaveChangesAsync(); - } - - // for stats before rating - if (clientStats.EloRating == 0.0) - { - clientStats.EloRating = clientStats.Skill; - } - - if (clientStats.RollingWeightedKDR == 0) - { - clientStats.RollingWeightedKDR = clientStats.KDR; - } - - // set these on connecting - clientStats.LastActive = DateTime.UtcNow; - clientStats.LastStatCalculation = DateTime.UtcNow; - clientStats.SessionScore = pl.Score; - clientStats.LastScore = pl.Score; - - pl.SetAdditionalProperty(CLIENT_DETECTIONS_KEY, new Detection(_log, clientStats)); - _log.LogDebug("Added {client} to stats", pl.ToString()); + // insert if they've not been added + clientStats = clientStatsSet.Add(clientStats).Entity; + await ctx.SaveChangesAsync(); } + pl.SetAdditionalProperty(CLIENT_STATS_KEY, clientStats); + + // migration for previous existing stats + if (clientStats.HitLocations.Count == 0) + { + clientStats.HitLocations = Enum.GetValues(typeof(IW4Info.HitLocation)) + .OfType() + .Select(hl => new EFHitLocationCount() + { + Active = true, + HitCount = 0, + Location = hl + }) + .ToList(); + + ctx.Update(clientStats); + await ctx.SaveChangesAsync(); + } + + // for stats before rating + if (clientStats.EloRating == 0.0) + { + clientStats.EloRating = clientStats.Skill; + } + + if (clientStats.RollingWeightedKDR == 0) + { + clientStats.RollingWeightedKDR = clientStats.KDR; + } + + // set these on connecting + clientStats.LastActive = DateTime.UtcNow; + clientStats.LastStatCalculation = DateTime.UtcNow; + clientStats.SessionScore = pl.Score; + clientStats.LastScore = pl.Score; + + pl.SetAdditionalProperty(CLIENT_DETECTIONS_KEY, new Detection(_log, clientStats)); + _log.LogDebug("Added {client} to stats", pl.ToString()); + return clientStats; } @@ -434,11 +427,9 @@ namespace IW4MAdmin.Plugins.Stats.Helpers private async Task SaveClientStats(EFClientStatistics clientStats) { - using (var ctx = _contextFactory.CreateContext()) - { - ctx.Update(clientStats); - await ctx.SaveChangesAsync(); - } + await using var ctx = _contextFactory.CreateContext(); + ctx.Update(clientStats); + await ctx.SaveChangesAsync(); } public void AddDamageEvent(string eventLine, int attackerClientId, int victimClientId, long serverId) @@ -628,13 +619,11 @@ namespace IW4MAdmin.Plugins.Stats.Helpers public async Task SaveHitCache(long serverId) { - using (var ctx = _contextFactory.CreateContext(enableTracking: false)) - { - var server = _servers[serverId]; - ctx.AddRange(server.HitCache.ToList()); - await ctx.SaveChangesAsync(); - server.HitCache.Clear(); - } + await using var ctx = _contextFactory.CreateContext(enableTracking: false); + var server = _servers[serverId]; + ctx.AddRange(server.HitCache.ToList()); + await ctx.SaveChangesAsync(); + server.HitCache.Clear(); } private bool ShouldUseDetection(Server server, DetectionType detectionType, long clientId) @@ -714,14 +703,12 @@ namespace IW4MAdmin.Plugins.Stats.Helpers { EFACSnapshot change; - using (var ctx = _contextFactory.CreateContext(enableTracking: false)) + await using var ctx = _contextFactory.CreateContext(); + while ((change = clientDetection.Tracker.GetNextChange()) != default(EFACSnapshot)) { - while ((change = clientDetection.Tracker.GetNextChange()) != default(EFACSnapshot)) - { - ctx.Add(change); - } - await ctx.SaveChangesAsync(); + ctx.Add(change); } + await ctx.SaveChangesAsync(); } public async Task AddStandardKill(EFClient attacker, EFClient victim) @@ -826,160 +813,158 @@ namespace IW4MAdmin.Plugins.Stats.Helpers int currentServerTotalPlaytime = clientStats.TimePlayed + currentSessionTime; - using (var ctx = _contextFactory.CreateContext(enableTracking: true)) + await using var ctx = _contextFactory.CreateContext(enableTracking: true); + // select the rating history for client + var iqHistoryLink = from history in ctx.Set() + .Include(h => h.Ratings) + where history.ClientId == client.ClientId + select history; + + // get the client ratings + var clientHistory = await iqHistoryLink + .FirstOrDefaultAsync() ?? new EFClientRatingHistory() + { + Active = true, + ClientId = client.ClientId, + Ratings = new List() + }; + + // it's the first time they've played + if (clientHistory.RatingHistoryId == 0) { - // select the rating history for client - var iqHistoryLink = from history in ctx.Set() - .Include(h => h.Ratings) - where history.ClientId == client.ClientId - select history; - - // get the client ratings - var clientHistory = await iqHistoryLink - .FirstOrDefaultAsync() ?? new EFClientRatingHistory() - { - Active = true, - ClientId = client.ClientId, - Ratings = new List() - }; - - // it's the first time they've played - if (clientHistory.RatingHistoryId == 0) - { - ctx.Add(clientHistory); - } - - #region INDIVIDUAL_SERVER_PERFORMANCE - // get the client ranking for the current server - int individualClientRanking = await ctx.Set() - .Where(GetRankingFunc(clientStats.ServerId)) - // ignore themselves in the query - .Where(c => c.RatingHistory.ClientId != client.ClientId) - .Where(c => c.Performance > clientStats.Performance) - .CountAsync() + 1; - - // limit max history per server to 40 - if (clientHistory.Ratings.Count(r => r.ServerId == clientStats.ServerId) >= 40) - { - // select the oldest one - var ratingToRemove = clientHistory.Ratings - .Where(r => r.ServerId == clientStats.ServerId) - .OrderBy(r => r.When) - .First(); - - ctx.Remove(ratingToRemove); - } - - // set the previous newest to false - var ratingToUnsetNewest = clientHistory.Ratings - .Where(r => r.ServerId == clientStats.ServerId) - .OrderByDescending(r => r.When) - .FirstOrDefault(); - - if (ratingToUnsetNewest != null) - { - if (ratingToUnsetNewest.Newest) - { - ctx.Update(ratingToUnsetNewest); - ctx.Entry(ratingToUnsetNewest).Property(r => r.Newest).IsModified = true; - ratingToUnsetNewest.Newest = false; - } - } - - var newServerRating = new EFRating() - { - Performance = clientStats.Performance, - Ranking = individualClientRanking, - Active = true, - Newest = true, - ServerId = clientStats.ServerId, - RatingHistory = clientHistory, - ActivityAmount = currentServerTotalPlaytime, - }; - - // add new rating for current server - ctx.Add(newServerRating); - - #endregion - #region OVERALL_RATING - // select all performance & time played for current client - var iqClientStats = from stats in ctx.Set() - where stats.ClientId == client.ClientId - where stats.ServerId != clientStats.ServerId - select new - { - stats.Performance, - stats.TimePlayed - }; - - var clientStatsList = await iqClientStats.ToListAsync(); - - // add the current server's so we don't have to pull it from the database - clientStatsList.Add(new - { - clientStats.Performance, - TimePlayed = currentServerTotalPlaytime - }); - - // weight the overall performance based on play time - double performanceAverage = clientStatsList.Sum(p => (p.Performance * p.TimePlayed)) / clientStatsList.Sum(p => p.TimePlayed); - - // shouldn't happen but just in case the sum of time played is 0 - if (double.IsNaN(performanceAverage)) - { - performanceAverage = clientStatsList.Average(p => p.Performance); - } - - int overallClientRanking = await ctx.Set() - .Where(GetRankingFunc()) - .Where(r => r.RatingHistory.ClientId != client.ClientId) - .Where(r => r.Performance > performanceAverage) - .CountAsync() + 1; - - // limit max average history to 40 - if (clientHistory.Ratings.Count(r => r.ServerId == null) >= 40) - { - var ratingToRemove = clientHistory.Ratings - .Where(r => r.ServerId == null) - .OrderBy(r => r.When) - .First(); - - ctx.Remove(ratingToRemove); - } - - // set the previous average newest to false - ratingToUnsetNewest = clientHistory.Ratings - .Where(r => r.ServerId == null) - .OrderByDescending(r => r.When) - .FirstOrDefault(); - - if (ratingToUnsetNewest != null) - { - if (ratingToUnsetNewest.Newest) - { - ctx.Update(ratingToUnsetNewest); - ctx.Entry(ratingToUnsetNewest).Property(r => r.Newest).IsModified = true; - ratingToUnsetNewest.Newest = false; - } - } - - // add new average rating - var averageRating = new EFRating() - { - Active = true, - Newest = true, - Performance = performanceAverage, - Ranking = overallClientRanking, - ServerId = null, - RatingHistory = clientHistory, - ActivityAmount = clientStatsList.Sum(s => s.TimePlayed) - }; - - ctx.Add(averageRating); - #endregion - - await ctx.SaveChangesAsync(); + ctx.Add(clientHistory); } + + #region INDIVIDUAL_SERVER_PERFORMANCE + // get the client ranking for the current server + int individualClientRanking = await ctx.Set() + .Where(GetRankingFunc(clientStats.ServerId)) + // ignore themselves in the query + .Where(c => c.RatingHistory.ClientId != client.ClientId) + .Where(c => c.Performance > clientStats.Performance) + .CountAsync() + 1; + + // limit max history per server to 40 + if (clientHistory.Ratings.Count(r => r.ServerId == clientStats.ServerId) >= 40) + { + // select the oldest one + var ratingToRemove = clientHistory.Ratings + .Where(r => r.ServerId == clientStats.ServerId) + .OrderBy(r => r.When) + .First(); + + ctx.Remove(ratingToRemove); + } + + // set the previous newest to false + var ratingToUnsetNewest = clientHistory.Ratings + .Where(r => r.ServerId == clientStats.ServerId) + .OrderByDescending(r => r.When) + .FirstOrDefault(); + + if (ratingToUnsetNewest != null) + { + if (ratingToUnsetNewest.Newest) + { + ctx.Update(ratingToUnsetNewest); + ctx.Entry(ratingToUnsetNewest).Property(r => r.Newest).IsModified = true; + ratingToUnsetNewest.Newest = false; + } + } + + var newServerRating = new EFRating() + { + Performance = clientStats.Performance, + Ranking = individualClientRanking, + Active = true, + Newest = true, + ServerId = clientStats.ServerId, + RatingHistory = clientHistory, + ActivityAmount = currentServerTotalPlaytime, + }; + + // add new rating for current server + ctx.Add(newServerRating); + + #endregion + #region OVERALL_RATING + // select all performance & time played for current client + var iqClientStats = from stats in ctx.Set() + where stats.ClientId == client.ClientId + where stats.ServerId != clientStats.ServerId + select new + { + stats.Performance, + stats.TimePlayed + }; + + var clientStatsList = await iqClientStats.ToListAsync(); + + // add the current server's so we don't have to pull it from the database + clientStatsList.Add(new + { + clientStats.Performance, + TimePlayed = currentServerTotalPlaytime + }); + + // weight the overall performance based on play time + double performanceAverage = clientStatsList.Sum(p => (p.Performance * p.TimePlayed)) / clientStatsList.Sum(p => p.TimePlayed); + + // shouldn't happen but just in case the sum of time played is 0 + if (double.IsNaN(performanceAverage)) + { + performanceAverage = clientStatsList.Average(p => p.Performance); + } + + int overallClientRanking = await ctx.Set() + .Where(GetRankingFunc()) + .Where(r => r.RatingHistory.ClientId != client.ClientId) + .Where(r => r.Performance > performanceAverage) + .CountAsync() + 1; + + // limit max average history to 40 + if (clientHistory.Ratings.Count(r => r.ServerId == null) >= 40) + { + var ratingToRemove = clientHistory.Ratings + .Where(r => r.ServerId == null) + .OrderBy(r => r.When) + .First(); + + ctx.Remove(ratingToRemove); + } + + // set the previous average newest to false + ratingToUnsetNewest = clientHistory.Ratings + .Where(r => r.ServerId == null) + .OrderByDescending(r => r.When) + .FirstOrDefault(); + + if (ratingToUnsetNewest != null) + { + if (ratingToUnsetNewest.Newest) + { + ctx.Update(ratingToUnsetNewest); + ctx.Entry(ratingToUnsetNewest).Property(r => r.Newest).IsModified = true; + ratingToUnsetNewest.Newest = false; + } + } + + // add new average rating + var averageRating = new EFRating() + { + Active = true, + Newest = true, + Performance = performanceAverage, + Ranking = overallClientRanking, + ServerId = null, + RatingHistory = clientHistory, + ActivityAmount = clientStatsList.Sum(s => s.TimePlayed) + }; + + ctx.Add(averageRating); + #endregion + + await ctx.SaveChangesAsync(); } /// @@ -1137,25 +1122,23 @@ namespace IW4MAdmin.Plugins.Stats.Helpers { EFServerStatistics serverStats; - using (var ctx = _contextFactory.CreateContext(enableTracking: false)) + using var ctx = _contextFactory.CreateContext(enableTracking: false); + var serverStatsSet = ctx.Set(); + serverStats = serverStatsSet.FirstOrDefault(s => s.ServerId == serverId); + + if (serverStats == null) { - var serverStatsSet = ctx.Set(); - serverStats = serverStatsSet.FirstOrDefault(s => s.ServerId == serverId); - - if (serverStats == null) + _log.LogDebug("Initializing server stats for {serverId}", serverId); + // server stats have never been generated before + serverStats = new EFServerStatistics() { - _log.LogDebug("Initializing server stats for {serverId}", serverId); - // server stats have never been generated before - serverStats = new EFServerStatistics() - { - ServerId = serverId, - TotalKills = 0, - TotalPlayTime = 0, - }; + ServerId = serverId, + TotalKills = 0, + TotalPlayTime = 0, + }; - serverStats = serverStatsSet.Add(serverStats).Entity; - ctx.SaveChanges(); - } + serverStats = serverStatsSet.Add(serverStats).Entity; + ctx.SaveChanges(); } return serverStats; @@ -1216,12 +1199,10 @@ namespace IW4MAdmin.Plugins.Stats.Helpers { await waiter.WaitAsync(); - using (var ctx = _contextFactory.CreateContext()) - { - var serverStatsSet = ctx.Set(); - serverStatsSet.Update(_servers[serverId].ServerStatistics); - await ctx.SaveChangesAsync(); - } + await using var ctx = _contextFactory.CreateContext(); + var serverStatsSet = ctx.Set(); + serverStatsSet.Update(_servers[serverId].ServerStatistics); + await ctx.SaveChangesAsync(); foreach (var stats in sv.GetClientsAsList() .Select(_client => _client.GetAdditionalProperty(CLIENT_STATS_KEY)) diff --git a/Plugins/Stats/Helpers/StatsResourceQueryHelper.cs b/Plugins/Stats/Helpers/StatsResourceQueryHelper.cs index dc76ce293..219f671e2 100644 --- a/Plugins/Stats/Helpers/StatsResourceQueryHelper.cs +++ b/Plugins/Stats/Helpers/StatsResourceQueryHelper.cs @@ -26,7 +26,7 @@ namespace Stats.Helpers public async Task> QueryResource(StatsInfoRequest query) { var result = new ResourceQueryHelperResult(); - using var context = _contextFactory.CreateContext(enableTracking: false); + await using var context = _contextFactory.CreateContext(enableTracking: false); // we need to get the ratings separately because there's not explicit FK var ratings = await context.Set() diff --git a/Plugins/Stats/Plugin.cs b/Plugins/Stats/Plugin.cs index 19e83b72e..d21e613aa 100644 --- a/Plugins/Stats/Plugin.cs +++ b/Plugins/Stats/Plugin.cs @@ -173,11 +173,9 @@ namespace IW4MAdmin.Plugins.Stats { IList clientStats; int messageCount = 0; - using (var ctx = _databaseContextFactory.CreateContext(enableTracking: false)) - { - clientStats = await ctx.Set().Where(c => c.ClientId == request.ClientId).ToListAsync(); - messageCount = await ctx.Set().CountAsync(_message => _message.ClientId == request.ClientId); - } + await using var ctx = _databaseContextFactory.CreateContext(enableTracking: false); + clientStats = await ctx.Set().Where(c => c.ClientId == request.ClientId).ToListAsync(); + messageCount = await ctx.Set().CountAsync(_message => _message.ClientId == request.ClientId); int kills = clientStats.Sum(c => c.Kills); int deaths = clientStats.Sum(c => c.Deaths); @@ -252,13 +250,11 @@ namespace IW4MAdmin.Plugins.Stats { IList clientStats; - using (var ctx = _databaseContextFactory.CreateContext(enableTracking: false)) - { - clientStats = await ctx.Set() - .Include(c => c.HitLocations) - .Where(c => c.ClientId == request.ClientId) - .ToListAsync(); - } + await using var ctx = _databaseContextFactory.CreateContext(enableTracking: false); + clientStats = await ctx.Set() + .Include(c => c.HitLocations) + .Where(c => c.ClientId == request.ClientId) + .ToListAsync(); double headRatio = 0; double chestRatio = 0; diff --git a/Plugins/Web/StatsWeb/ChatResourceQueryHelper.cs b/Plugins/Web/StatsWeb/ChatResourceQueryHelper.cs index 34586ded8..c8b484baa 100644 --- a/Plugins/Web/StatsWeb/ChatResourceQueryHelper.cs +++ b/Plugins/Web/StatsWeb/ChatResourceQueryHelper.cs @@ -41,7 +41,7 @@ namespace StatsWeb } var result = new ResourceQueryHelperResult(); - using var context = _contextFactory.CreateContext(enableTracking: false); + await using var context = _contextFactory.CreateContext(enableTracking: false); if (serverCache == null) { diff --git a/SharedLibraryCore/Database/ContextSeed.cs b/SharedLibraryCore/Database/ContextSeed.cs index fac72d752..770230fbc 100644 --- a/SharedLibraryCore/Database/ContextSeed.cs +++ b/SharedLibraryCore/Database/ContextSeed.cs @@ -13,7 +13,7 @@ namespace SharedLibraryCore.Database { public static async Task Seed(IDatabaseContextFactory contextFactory, CancellationToken token) { - var context = contextFactory.CreateContext(); + await using var context = contextFactory.CreateContext(); var strategy = context.Database.CreateExecutionStrategy(); await strategy.ExecuteAsync(async () => { diff --git a/SharedLibraryCore/Database/MigrationContext/MySqlDatabaseContext.cs b/SharedLibraryCore/Database/MigrationContext/MySqlDatabaseContext.cs index 946ac39e9..88fd9cef3 100644 --- a/SharedLibraryCore/Database/MigrationContext/MySqlDatabaseContext.cs +++ b/SharedLibraryCore/Database/MigrationContext/MySqlDatabaseContext.cs @@ -13,7 +13,7 @@ namespace SharedLibraryCore.Database.MigrationContext } } - public MySqlDatabaseContext(DbContextOptions options) : base(options) + public MySqlDatabaseContext(DbContextOptions options) : base(options) { } diff --git a/SharedLibraryCore/Database/MigrationContext/PostgresqlDatabaseContext.cs b/SharedLibraryCore/Database/MigrationContext/PostgresqlDatabaseContext.cs index 2167c954f..cf6c57b8d 100644 --- a/SharedLibraryCore/Database/MigrationContext/PostgresqlDatabaseContext.cs +++ b/SharedLibraryCore/Database/MigrationContext/PostgresqlDatabaseContext.cs @@ -13,7 +13,7 @@ namespace SharedLibraryCore.Database.MigrationContext } } - public PostgresqlDatabaseContext(DbContextOptions options) : base(options) + public PostgresqlDatabaseContext(DbContextOptions options) : base(options) { } diff --git a/SharedLibraryCore/Database/MigrationContext/SqliteDatabaseContext.cs b/SharedLibraryCore/Database/MigrationContext/SqliteDatabaseContext.cs index ff30d6d3f..89a8274cc 100644 --- a/SharedLibraryCore/Database/MigrationContext/SqliteDatabaseContext.cs +++ b/SharedLibraryCore/Database/MigrationContext/SqliteDatabaseContext.cs @@ -13,7 +13,7 @@ namespace SharedLibraryCore.Database.MigrationContext } } - public SqliteDatabaseContext(DbContextOptions options) : base(options) + public SqliteDatabaseContext(DbContextOptions options) : base(options) { } diff --git a/SharedLibraryCore/PartialEntities/EFClient.cs b/SharedLibraryCore/PartialEntities/EFClient.cs index df2bc1cb1..957462dd3 100644 --- a/SharedLibraryCore/PartialEntities/EFClient.cs +++ b/SharedLibraryCore/PartialEntities/EFClient.cs @@ -91,12 +91,11 @@ namespace SharedLibraryCore.Database.Models SetAdditionalProperty("_reportCount", 0); ReceivedPenalties = new List(); _processingEvent = new SemaphoreSlim(1, 1); - } ~EFClient() { - _processingEvent.Dispose(); + _processingEvent?.Dispose(); } public override string ToString() diff --git a/SharedLibraryCore/Repositories/AuditInformationRepository.cs b/SharedLibraryCore/Repositories/AuditInformationRepository.cs index fea61c2af..d712ad2f7 100644 --- a/SharedLibraryCore/Repositories/AuditInformationRepository.cs +++ b/SharedLibraryCore/Repositories/AuditInformationRepository.cs @@ -22,34 +22,32 @@ namespace SharedLibraryCore.Repositories /// public async Task> ListAuditInformation(PaginationRequest paginationInfo) { - using (var ctx = _contextFactory.CreateContext(enableTracking: false)) - { - var iqItems = (from change in ctx.EFChangeHistory - where change.TypeOfChange != Database.Models.EFChangeHistory.ChangeType.Ban - orderby change.TimeChanged descending - join originClient in ctx.Clients - on (change.ImpersonationEntityId ?? change.OriginEntityId) equals originClient.ClientId - join targetClient in ctx.Clients - on change.TargetEntityId equals targetClient.ClientId - into targetChange - from targetClient in targetChange.DefaultIfEmpty() - select new AuditInfo() - { - Action = change.TypeOfChange.ToString(), - OriginName = originClient.CurrentAlias.Name, - OriginId = originClient.ClientId, - TargetName = targetClient == null ? "" : targetClient.CurrentAlias.Name, - TargetId = targetClient == null ? new int?() : targetClient.ClientId, - When = change.TimeChanged, - Data = change.Comment, - OldValue = change.PreviousValue, - NewValue = change.CurrentValue - }) - .Skip(paginationInfo.Offset) - .Take(paginationInfo.Count); + await using var ctx = _contextFactory.CreateContext(enableTracking: false); + var iqItems = (from change in ctx.EFChangeHistory + where change.TypeOfChange != Database.Models.EFChangeHistory.ChangeType.Ban + orderby change.TimeChanged descending + join originClient in ctx.Clients + on (change.ImpersonationEntityId ?? change.OriginEntityId) equals originClient.ClientId + join targetClient in ctx.Clients + on change.TargetEntityId equals targetClient.ClientId + into targetChange + from targetClient in targetChange.DefaultIfEmpty() + select new AuditInfo() + { + Action = change.TypeOfChange.ToString(), + OriginName = originClient.CurrentAlias.Name, + OriginId = originClient.ClientId, + TargetName = targetClient == null ? "" : targetClient.CurrentAlias.Name, + TargetId = targetClient == null ? new int?() : targetClient.ClientId, + When = change.TimeChanged, + Data = change.Comment, + OldValue = change.PreviousValue, + NewValue = change.CurrentValue + }) + .Skip(paginationInfo.Offset) + .Take(paginationInfo.Count); - return await iqItems.ToListAsync(); - } + return await iqItems.ToListAsync(); } } }