fix memory leak issue related to AddDbContext not working as expected

This commit is contained in:
RaidMax 2020-11-29 16:01:52 -06:00
parent b2d282d412
commit bd3f0caf60
18 changed files with 499 additions and 505 deletions

View File

@ -48,10 +48,12 @@ namespace IW4MAdmin.Application.Extensions
return services; return services;
} }
public static IServiceCollection AddDatabaseContext(this IServiceCollection services, public static IServiceCollection AddDatabaseContextOptions(this IServiceCollection services,
ApplicationConfiguration appConfig) 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; var currentPath = Utilities.OperatingDirectory;
currentPath = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) currentPath = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
@ -62,31 +64,34 @@ namespace IW4MAdmin.Application.Extensions
{DataSource = Path.Join(currentPath, "Database", "Database.db")}; {DataSource = Path.Join(currentPath, "Database", "Database.db")};
var connectionString = connectionStringBuilder.ToString(); var connectionString = connectionStringBuilder.ToString();
services.AddDbContext<DatabaseContext, SqliteDatabaseContext>(options => var builder = new DbContextOptionsBuilder<SqliteDatabaseContext>()
options.UseSqlite(connectionString), ServiceLifetime.Transient); .UseSqlite(connectionString);
services.AddSingleton((DbContextOptions) builder.Options);
return services; return services;
} }
switch (appConfig.DatabaseProvider) switch (activeProvider)
{ {
case "mysql": case "mysql":
var appendTimeout = !appConfig.ConnectionString.Contains("default command timeout", var appendTimeout = !appConfig.ConnectionString.Contains("default command timeout",
StringComparison.InvariantCultureIgnoreCase); StringComparison.InvariantCultureIgnoreCase);
services.AddDbContext<DatabaseContext, MySqlDatabaseContext>(options => var mysqlBuilder = new DbContextOptionsBuilder<MySqlDatabaseContext>()
options.UseMySql( .UseMySql(appConfig.ConnectionString + (appendTimeout ? ";default command timeout=0" : ""),
appConfig.ConnectionString + (appendTimeout ? ";default command timeout=0" : ""), mysqlOptions => mysqlOptions.EnableRetryOnFailure());
mysqlOptions => mysqlOptions.EnableRetryOnFailure()), ServiceLifetime.Transient); services.AddSingleton((DbContextOptions) mysqlBuilder.Options);
break; return services;
case "postgresql": case "postgresql":
appendTimeout = !appConfig.ConnectionString.Contains("Command Timeout", appendTimeout = !appConfig.ConnectionString.Contains("Command Timeout",
StringComparison.InvariantCultureIgnoreCase); StringComparison.InvariantCultureIgnoreCase);
services.AddDbContext<DatabaseContext, PostgresqlDatabaseContext>(options => var postgresqlBuilder = new DbContextOptionsBuilder<PostgresqlDatabaseContext>()
options.UseNpgsql(appConfig.ConnectionString + (appendTimeout ? ";Command Timeout=0" : ""), .UseNpgsql(appConfig.ConnectionString + (appendTimeout ? ";Command Timeout=0" : ""),
postgresqlOptions => postgresqlOptions.EnableRetryOnFailure()), ServiceLifetime.Transient); postgresqlOptions => postgresqlOptions.EnableRetryOnFailure());
break; services.AddSingleton((DbContextOptions) postgresqlBuilder.Options);
return services;
default:
throw new ArgumentException($"No context available for {appConfig.DatabaseProvider}");
} }
return services;
} }
} }
} }

View File

@ -1,7 +1,8 @@
using System; using System;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection; using SharedLibraryCore.Configuration;
using SharedLibraryCore.Database; using SharedLibraryCore.Database;
using SharedLibraryCore.Database.MigrationContext;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Interfaces;
namespace IW4MAdmin.Application.Factories namespace IW4MAdmin.Application.Factories
@ -11,13 +12,15 @@ namespace IW4MAdmin.Application.Factories
/// </summary> /// </summary>
public class DatabaseContextFactory : IDatabaseContextFactory public class DatabaseContextFactory : IDatabaseContextFactory
{ {
private readonly IServiceProvider _serviceProvider; private readonly DbContextOptions _contextOptions;
private readonly string _activeProvider;
public DatabaseContextFactory(IServiceProvider serviceProvider)
public DatabaseContextFactory(ApplicationConfiguration appConfig, DbContextOptions contextOptions)
{ {
_serviceProvider = serviceProvider; _contextOptions = contextOptions;
_activeProvider = appConfig.DatabaseProvider?.ToLower();
} }
/// <summary> /// <summary>
/// creates a new database context /// creates a new database context
/// </summary> /// </summary>
@ -25,10 +28,10 @@ namespace IW4MAdmin.Application.Factories
/// <returns></returns> /// <returns></returns>
public DatabaseContext CreateContext(bool? enableTracking = true) public DatabaseContext CreateContext(bool? enableTracking = true)
{ {
var context = _serviceProvider.GetRequiredService<DatabaseContext>(); var context = BuildContext();
enableTracking ??= true; enableTracking ??= true;
if (enableTracking.Value) if (enableTracking.Value)
{ {
context.ChangeTracker.AutoDetectChangesEnabled = true; context.ChangeTracker.AutoDetectChangesEnabled = true;
@ -44,5 +47,16 @@ namespace IW4MAdmin.Application.Factories
return context; 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}")
};
}
} }
} }

View File

@ -361,7 +361,7 @@ namespace IW4MAdmin.Application
.AddSingleton<SharedLibraryCore.Interfaces.ILogger, Logger>() .AddSingleton<SharedLibraryCore.Interfaces.ILogger, Logger>()
.AddSingleton<IClientNoticeMessageFormatter, ClientNoticeMessageFormatter>() .AddSingleton<IClientNoticeMessageFormatter, ClientNoticeMessageFormatter>()
.AddSingleton(translationLookup) .AddSingleton(translationLookup)
.AddDatabaseContext(appConfig); .AddDatabaseContextOptions(appConfig);
if (args.Contains("serialevents")) if (args.Contains("serialevents"))
{ {

View File

@ -28,7 +28,7 @@ namespace IW4MAdmin.Application.Meta
public async Task<ResourceQueryHelperResult<AdministeredPenaltyResponse>> QueryResource(ClientPaginationRequest query) public async Task<ResourceQueryHelperResult<AdministeredPenaltyResponse>> QueryResource(ClientPaginationRequest query)
{ {
using var ctx = _contextFactory.CreateContext(enableTracking: false); await using var ctx = _contextFactory.CreateContext(enableTracking: false);
var iqPenalties = ctx.Penalties.AsNoTracking() var iqPenalties = ctx.Penalties.AsNoTracking()
.Where(_penalty => query.ClientId == _penalty.PunisherId) .Where(_penalty => query.ClientId == _penalty.PunisherId)

View File

@ -31,7 +31,7 @@ namespace IW4MAdmin.Application.Meta
public async Task<ResourceQueryHelperResult<ReceivedPenaltyResponse>> QueryResource(ClientPaginationRequest query) public async Task<ResourceQueryHelperResult<ReceivedPenaltyResponse>> QueryResource(ClientPaginationRequest query)
{ {
var linkedPenaltyType = Utilities.LinkedPenaltyTypes(); 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() var linkId = await ctx.Clients.AsNoTracking()
.Where(_client => _client.ClientId == query.ClientId) .Where(_client => _client.ClientId == query.ClientId)

View File

@ -28,7 +28,7 @@ namespace IW4MAdmin.Application.Meta
public async Task<ResourceQueryHelperResult<UpdatedAliasResponse>> QueryResource(ClientPaginationRequest query) public async Task<ResourceQueryHelperResult<UpdatedAliasResponse>> 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; int linkId = ctx.Clients.First(_client => _client.ClientId == query.ClientId).AliasLinkId;
var iqAliasUpdates = ctx.Aliases var iqAliasUpdates = ctx.Aliases

View File

@ -37,7 +37,7 @@ namespace IW4MAdmin.Application.Misc
return; return;
} }
using var ctx = _contextFactory.CreateContext(); await using var ctx = _contextFactory.CreateContext();
var existingMeta = await ctx.EFMeta var existingMeta = await ctx.EFMeta
.Where(_meta => _meta.Key == metaKey) .Where(_meta => _meta.Key == metaKey)
@ -66,7 +66,7 @@ namespace IW4MAdmin.Application.Misc
public async Task<EFMeta> GetPersistentMeta(string metaKey, EFClient client) public async Task<EFMeta> GetPersistentMeta(string metaKey, EFClient client)
{ {
using var ctx = _contextFactory.CreateContext(enableTracking: false); await using var ctx = _contextFactory.CreateContext(enableTracking: false);
return await ctx.EFMeta return await ctx.EFMeta
.Where(_meta => _meta.Key == metaKey) .Where(_meta => _meta.Key == metaKey)

View File

@ -2,7 +2,6 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using SharedLibraryCore; using SharedLibraryCore;
using IW4MAdmin.Plugins.Stats.Models; using IW4MAdmin.Plugins.Stats.Models;
using System.Collections.Generic; using System.Collections.Generic;
@ -19,7 +18,8 @@ namespace IW4MAdmin.Plugins.Stats.Commands
private readonly IDatabaseContextFactory _contextFactory; private readonly IDatabaseContextFactory _contextFactory;
private readonly CommandConfiguration _config; 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"; Name = "mostkills";
Description = translationLookup["PLUGINS_STATS_COMMANDS_MOSTKILLS_DESC"]; Description = translationLookup["PLUGINS_STATS_COMMANDS_MOSTKILLS_DESC"];
@ -32,7 +32,8 @@ namespace IW4MAdmin.Plugins.Stats.Commands
public override async Task ExecuteAsync(GameEvent E) 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)) if (!E.Message.IsBroadcastCommand(_config.BroadcastCommandPrefix))
{ {
foreach (var stat in mostKills) foreach (var stat in mostKills)
@ -50,33 +51,33 @@ namespace IW4MAdmin.Plugins.Stats.Commands
} }
} }
public static async Task<IEnumerable<string>> GetMostKills(long? serverId, StatsConfiguration config, IDatabaseContextFactory contextFactory, ITranslationLookup translationLookup) public static async Task<IEnumerable<string>> GetMostKills(long? serverId, StatsConfiguration config,
IDatabaseContextFactory contextFactory, ITranslationLookup translationLookup)
{ {
using (var ctx = contextFactory.CreateContext(enableTracking: false)) await using var ctx = contextFactory.CreateContext(enableTracking: false);
{ var dayInPast = DateTime.UtcNow.AddDays(-config.MostKillsMaxInactivityDays);
var dayInPast = DateTime.UtcNow.AddDays(-config.MostKillsMaxInactivityDays);
var iqStats = (from stats in ctx.Set<EFClientStatistics>() var iqStats = (from stats in ctx.Set<EFClientStatistics>()
join client in ctx.Clients join client in ctx.Clients
on stats.ClientId equals client.ClientId on stats.ClientId equals client.ClientId
join alias in ctx.Aliases join alias in ctx.Aliases
on client.CurrentAliasId equals alias.AliasId on client.CurrentAliasId equals alias.AliasId
where stats.ServerId == serverId where stats.ServerId == serverId
where client.Level != EFClient.Permission.Banned where client.Level != EFClient.Permission.Banned
where client.LastConnection >= dayInPast where client.LastConnection >= dayInPast
orderby stats.Kills descending orderby stats.Kills descending
select new select new
{ {
alias.Name, alias.Name,
stats.Kills stats.Kills
}) })
.Take(config.MostKillsClientLimit); .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)) return iqList.Select((stats, index) => translationLookup["PLUGINS_STATS_COMMANDS_MOSTKILLS_FORMAT"]
.Prepend(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_MOSTKILLS_HEADER"]); .FormatExt(index + 1, stats.Name, stats.Kills))
} .Prepend(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_MOSTKILLS_HEADER"]);
} }
} }
} }

View File

@ -68,121 +68,118 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
/// <returns></returns> /// <returns></returns>
public async Task<int> GetClientOverallRanking(int clientId) public async Task<int> GetClientOverallRanking(int clientId)
{ {
using (var context = _contextFactory.CreateContext(enableTracking: false)) await using var context = _contextFactory.CreateContext(enableTracking: false);
var clientPerformance = await context.Set<EFRating>()
.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<EFRating>() var iqClientRanking = context.Set<EFRating>()
.Where(r => r.RatingHistory.ClientId == clientId) .Where(r => r.RatingHistory.ClientId != clientId)
.Where(r => r.ServerId == null) .Where(r => r.Performance > clientPerformance)
.Where(r => r.Newest) .Where(GetRankingFunc());
.Select(r => r.Performance)
.FirstOrDefaultAsync();
if (clientPerformance != 0) return await iqClientRanking.CountAsync() + 1;
{
var iqClientRanking = context.Set<EFRating>()
.Where(r => r.RatingHistory.ClientId != clientId)
.Where(r => r.Performance > clientPerformance)
.Where(GetRankingFunc());
return await iqClientRanking.CountAsync() + 1;
}
return 0;
} }
return 0;
} }
public async Task<List<TopStatsInfo>> GetTopStats(int start, int count, long? serverId = null) public async Task<List<TopStatsInfo>> GetTopStats(int start, int count, long? serverId = null)
{ {
using (var context = _contextFactory.CreateContext(enableTracking: false)) await using var context = _contextFactory.CreateContext(enableTracking: false);
{ // setup the query for the clients within the given rating range
// setup the query for the clients within the given rating range var iqClientRatings = (from rating in context.Set<EFRating>()
var iqClientRatings = (from rating in context.Set<EFRating>() .Where(GetRankingFunc(serverId))
.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<EFRating>()
where clientIds.Contains(rating.RatingHistory.ClientId)
where rating.ServerId == serverId
select new select new
{ {
rating.Ranking,
rating.Performance,
rating.RatingHistory.ClientId, 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()) // materialized list
.GroupBy(r => r.ClientId) var clientRatings = await iqClientRatings.ToListAsync();
.Select(grp => new
{
grp.Key,
Ratings = grp.Select(r => new { r.Performance, r.Ranking, r.When })
});
var iqStatsInfo = (from stat in context.Set<EFClientStatistics>() // get all the unique client ids that are in the top stats
where clientIds.Contains(stat.ClientId) var clientIds = clientRatings
where stat.Kills > 0 || stat.Deaths > 0 .GroupBy(r => r.ClientId)
where serverId == null ? true : stat.ServerId == serverId .Select(r => r.First().ClientId)
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<double>() { clientRatingsDict[s.ClientId].Performance, clientRatingsDict[s.ClientId].Performance },
TimePlayed = Math.Round(s.TotalTimePlayed / 3600.0, 1).ToString("#,##0"),
})
.OrderByDescending(r => r.Performance)
.ToList(); .ToList();
// set the ranking numerically var iqRatingInfo = from rating in context.Set<EFRating>()
int i = start + 1; where clientIds.Contains(rating.RatingHistory.ClientId)
foreach (var stat in finished) where rating.ServerId == serverId
{ select new
stat.Ranking = i; {
i++; 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<EFClientStatistics>()
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<double>() { 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;
} }
/// <summary> /// <summary>
@ -202,63 +199,61 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
long serverId = GetIdForServer(sv); long serverId = GetIdForServer(sv);
EFServer server; EFServer server;
using (var ctx = _contextFactory.CreateContext(enableTracking: false)) using var ctx = _contextFactory.CreateContext(enableTracking: false);
var serverSet = ctx.Set<EFServer>();
// 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<EFServer>(); server = serverSet.FirstOrDefault(s => s.EndPoint == sv.ToString());
// 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)
if (server == null)
{ {
server = serverSet.FirstOrDefault(s => s.EndPoint == sv.ToString()); // this provides a way to identify legacy server entries
server.EndPoint = sv.ToString();
if (server != null) ctx.Update(server);
{
// 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
ctx.SaveChanges(); ctx.SaveChanges();
} }
}
// we want to set the gamename up if it's never been set, or it changed // server has never been added before
else if (!server.GameName.HasValue || server.GameName.HasValue && server.GameName.Value != sv.GameName) if (server == null)
{
server = new EFServer()
{ {
server.GameName = sv.GameName; Port = sv.Port,
ctx.Entry(server).Property(_prop => _prop.GameName).IsModified = true; EndPoint = sv.ToString(),
ctx.SaveChanges(); ServerId = serverId,
} GameName = sv.GameName,
HostName = sv.Hostname
};
if (server.HostName == null || server.HostName != sv.Hostname) server = serverSet.Add(server).Entity;
{ // this doesn't need to be async as it's during initialization
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(); 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 // check to see if the stats have ever been initialized
var serverStats = InitializeServerStats(server.ServerId); var serverStats = InitializeServerStats(server.ServerId);
@ -304,79 +299,77 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
EFClientStatistics clientStats; EFClientStatistics clientStats;
using (var ctx = _contextFactory.CreateContext(enableTracking: false)) await using var ctx = _contextFactory.CreateContext(enableTracking: false);
var clientStatsSet = ctx.Set<EFClientStatistics>();
clientStats = clientStatsSet
.Include(cl => cl.HitLocations)
.FirstOrDefault(c => c.ClientId == pl.ClientId && c.ServerId == serverId);
if (clientStats == null)
{ {
var clientStatsSet = ctx.Set<EFClientStatistics>(); clientStats = new EFClientStatistics()
clientStats = clientStatsSet
.Include(cl => cl.HitLocations)
.FirstOrDefault(c => c.ClientId == pl.ClientId && c.ServerId == serverId);
if (clientStats == null)
{ {
clientStats = new EFClientStatistics() Active = true,
{ ClientId = pl.ClientId,
Active = true, Deaths = 0,
ClientId = pl.ClientId, Kills = 0,
Deaths = 0, ServerId = serverId,
Kills = 0, Skill = 0.0,
ServerId = serverId, SPM = 0.0,
Skill = 0.0, EloRating = 200.0,
SPM = 0.0, HitLocations = Enum.GetValues(typeof(IW4Info.HitLocation)).OfType<IW4Info.HitLocation>()
EloRating = 200.0,
HitLocations = Enum.GetValues(typeof(IW4Info.HitLocation)).OfType<IW4Info.HitLocation>()
.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<IW4Info.HitLocation>()
.Select(hl => new EFHitLocationCount() .Select(hl => new EFHitLocationCount()
{ {
Active = true, Active = true,
HitCount = 0, HitCount = 0,
Location = hl Location = hl
}) }).ToList()
.ToList(); };
ctx.Update(clientStats); // insert if they've not been added
await ctx.SaveChangesAsync(); clientStats = clientStatsSet.Add(clientStats).Entity;
} 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());
} }
pl.SetAdditionalProperty(CLIENT_STATS_KEY, clientStats);
// migration for previous existing stats
if (clientStats.HitLocations.Count == 0)
{
clientStats.HitLocations = Enum.GetValues(typeof(IW4Info.HitLocation))
.OfType<IW4Info.HitLocation>()
.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; return clientStats;
} }
@ -434,11 +427,9 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
private async Task SaveClientStats(EFClientStatistics clientStats) private async Task SaveClientStats(EFClientStatistics clientStats)
{ {
using (var ctx = _contextFactory.CreateContext()) await using var ctx = _contextFactory.CreateContext();
{ ctx.Update(clientStats);
ctx.Update(clientStats); await ctx.SaveChangesAsync();
await ctx.SaveChangesAsync();
}
} }
public void AddDamageEvent(string eventLine, int attackerClientId, int victimClientId, long serverId) 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) public async Task SaveHitCache(long serverId)
{ {
using (var ctx = _contextFactory.CreateContext(enableTracking: false)) await using var ctx = _contextFactory.CreateContext(enableTracking: false);
{ var server = _servers[serverId];
var server = _servers[serverId]; ctx.AddRange(server.HitCache.ToList());
ctx.AddRange(server.HitCache.ToList()); await ctx.SaveChangesAsync();
await ctx.SaveChangesAsync(); server.HitCache.Clear();
server.HitCache.Clear();
}
} }
private bool ShouldUseDetection(Server server, DetectionType detectionType, long clientId) private bool ShouldUseDetection(Server server, DetectionType detectionType, long clientId)
@ -714,14 +703,12 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
{ {
EFACSnapshot change; 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);
{
ctx.Add(change);
}
await ctx.SaveChangesAsync();
} }
await ctx.SaveChangesAsync();
} }
public async Task AddStandardKill(EFClient attacker, EFClient victim) public async Task AddStandardKill(EFClient attacker, EFClient victim)
@ -826,160 +813,158 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
int currentServerTotalPlaytime = clientStats.TimePlayed + currentSessionTime; 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<EFClientRatingHistory>()
.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<EFRating>()
};
// it's the first time they've played
if (clientHistory.RatingHistoryId == 0)
{ {
// select the rating history for client ctx.Add(clientHistory);
var iqHistoryLink = from history in ctx.Set<EFClientRatingHistory>()
.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<EFRating>()
};
// 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<EFRating>()
.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<EFClientStatistics>()
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<EFRating>()
.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();
} }
#region INDIVIDUAL_SERVER_PERFORMANCE
// get the client ranking for the current server
int individualClientRanking = await ctx.Set<EFRating>()
.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<EFClientStatistics>()
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<EFRating>()
.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();
} }
/// <summary> /// <summary>
@ -1137,25 +1122,23 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
{ {
EFServerStatistics serverStats; EFServerStatistics serverStats;
using (var ctx = _contextFactory.CreateContext(enableTracking: false)) using var ctx = _contextFactory.CreateContext(enableTracking: false);
var serverStatsSet = ctx.Set<EFServerStatistics>();
serverStats = serverStatsSet.FirstOrDefault(s => s.ServerId == serverId);
if (serverStats == null)
{ {
var serverStatsSet = ctx.Set<EFServerStatistics>(); _log.LogDebug("Initializing server stats for {serverId}", serverId);
serverStats = serverStatsSet.FirstOrDefault(s => s.ServerId == serverId); // server stats have never been generated before
serverStats = new EFServerStatistics()
if (serverStats == null)
{ {
_log.LogDebug("Initializing server stats for {serverId}", serverId); ServerId = serverId,
// server stats have never been generated before TotalKills = 0,
serverStats = new EFServerStatistics() TotalPlayTime = 0,
{ };
ServerId = serverId,
TotalKills = 0,
TotalPlayTime = 0,
};
serverStats = serverStatsSet.Add(serverStats).Entity; serverStats = serverStatsSet.Add(serverStats).Entity;
ctx.SaveChanges(); ctx.SaveChanges();
}
} }
return serverStats; return serverStats;
@ -1216,12 +1199,10 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
{ {
await waiter.WaitAsync(); await waiter.WaitAsync();
using (var ctx = _contextFactory.CreateContext()) await using var ctx = _contextFactory.CreateContext();
{ var serverStatsSet = ctx.Set<EFServerStatistics>();
var serverStatsSet = ctx.Set<EFServerStatistics>(); serverStatsSet.Update(_servers[serverId].ServerStatistics);
serverStatsSet.Update(_servers[serverId].ServerStatistics); await ctx.SaveChangesAsync();
await ctx.SaveChangesAsync();
}
foreach (var stats in sv.GetClientsAsList() foreach (var stats in sv.GetClientsAsList()
.Select(_client => _client.GetAdditionalProperty<EFClientStatistics>(CLIENT_STATS_KEY)) .Select(_client => _client.GetAdditionalProperty<EFClientStatistics>(CLIENT_STATS_KEY))

View File

@ -26,7 +26,7 @@ namespace Stats.Helpers
public async Task<ResourceQueryHelperResult<StatsInfoResult>> QueryResource(StatsInfoRequest query) public async Task<ResourceQueryHelperResult<StatsInfoResult>> QueryResource(StatsInfoRequest query)
{ {
var result = new ResourceQueryHelperResult<StatsInfoResult>(); var result = new ResourceQueryHelperResult<StatsInfoResult>();
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 // we need to get the ratings separately because there's not explicit FK
var ratings = await context.Set<EFClientRatingHistory>() var ratings = await context.Set<EFClientRatingHistory>()

View File

@ -173,11 +173,9 @@ namespace IW4MAdmin.Plugins.Stats
{ {
IList<EFClientStatistics> clientStats; IList<EFClientStatistics> clientStats;
int messageCount = 0; int messageCount = 0;
using (var ctx = _databaseContextFactory.CreateContext(enableTracking: false)) await using var ctx = _databaseContextFactory.CreateContext(enableTracking: false);
{ clientStats = await ctx.Set<EFClientStatistics>().Where(c => c.ClientId == request.ClientId).ToListAsync();
clientStats = await ctx.Set<EFClientStatistics>().Where(c => c.ClientId == request.ClientId).ToListAsync(); messageCount = await ctx.Set<EFClientMessage>().CountAsync(_message => _message.ClientId == request.ClientId);
messageCount = await ctx.Set<EFClientMessage>().CountAsync(_message => _message.ClientId == request.ClientId);
}
int kills = clientStats.Sum(c => c.Kills); int kills = clientStats.Sum(c => c.Kills);
int deaths = clientStats.Sum(c => c.Deaths); int deaths = clientStats.Sum(c => c.Deaths);
@ -252,13 +250,11 @@ namespace IW4MAdmin.Plugins.Stats
{ {
IList<EFClientStatistics> clientStats; IList<EFClientStatistics> clientStats;
using (var ctx = _databaseContextFactory.CreateContext(enableTracking: false)) await using var ctx = _databaseContextFactory.CreateContext(enableTracking: false);
{ clientStats = await ctx.Set<EFClientStatistics>()
clientStats = await ctx.Set<EFClientStatistics>() .Include(c => c.HitLocations)
.Include(c => c.HitLocations) .Where(c => c.ClientId == request.ClientId)
.Where(c => c.ClientId == request.ClientId) .ToListAsync();
.ToListAsync();
}
double headRatio = 0; double headRatio = 0;
double chestRatio = 0; double chestRatio = 0;

View File

@ -41,7 +41,7 @@ namespace StatsWeb
} }
var result = new ResourceQueryHelperResult<MessageResponse>(); var result = new ResourceQueryHelperResult<MessageResponse>();
using var context = _contextFactory.CreateContext(enableTracking: false); await using var context = _contextFactory.CreateContext(enableTracking: false);
if (serverCache == null) if (serverCache == null)
{ {

View File

@ -13,7 +13,7 @@ namespace SharedLibraryCore.Database
{ {
public static async Task Seed(IDatabaseContextFactory contextFactory, CancellationToken token) public static async Task Seed(IDatabaseContextFactory contextFactory, CancellationToken token)
{ {
var context = contextFactory.CreateContext(); await using var context = contextFactory.CreateContext();
var strategy = context.Database.CreateExecutionStrategy(); var strategy = context.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(async () => await strategy.ExecuteAsync(async () =>
{ {

View File

@ -13,7 +13,7 @@ namespace SharedLibraryCore.Database.MigrationContext
} }
} }
public MySqlDatabaseContext(DbContextOptions<MySqlDatabaseContext> options) : base(options) public MySqlDatabaseContext(DbContextOptions options) : base(options)
{ {
} }

View File

@ -13,7 +13,7 @@ namespace SharedLibraryCore.Database.MigrationContext
} }
} }
public PostgresqlDatabaseContext(DbContextOptions<PostgresqlDatabaseContext> options) : base(options) public PostgresqlDatabaseContext(DbContextOptions options) : base(options)
{ {
} }

View File

@ -13,7 +13,7 @@ namespace SharedLibraryCore.Database.MigrationContext
} }
} }
public SqliteDatabaseContext(DbContextOptions<SqliteDatabaseContext> options) : base(options) public SqliteDatabaseContext(DbContextOptions options) : base(options)
{ {
} }

View File

@ -91,12 +91,11 @@ namespace SharedLibraryCore.Database.Models
SetAdditionalProperty("_reportCount", 0); SetAdditionalProperty("_reportCount", 0);
ReceivedPenalties = new List<EFPenalty>(); ReceivedPenalties = new List<EFPenalty>();
_processingEvent = new SemaphoreSlim(1, 1); _processingEvent = new SemaphoreSlim(1, 1);
} }
~EFClient() ~EFClient()
{ {
_processingEvent.Dispose(); _processingEvent?.Dispose();
} }
public override string ToString() public override string ToString()

View File

@ -22,34 +22,32 @@ namespace SharedLibraryCore.Repositories
/// <inheritdoc/> /// <inheritdoc/>
public async Task<IList<AuditInfo>> ListAuditInformation(PaginationRequest paginationInfo) public async Task<IList<AuditInfo>> ListAuditInformation(PaginationRequest paginationInfo)
{ {
using (var ctx = _contextFactory.CreateContext(enableTracking: false)) await using var ctx = _contextFactory.CreateContext(enableTracking: false);
{ var iqItems = (from change in ctx.EFChangeHistory
var iqItems = (from change in ctx.EFChangeHistory where change.TypeOfChange != Database.Models.EFChangeHistory.ChangeType.Ban
where change.TypeOfChange != Database.Models.EFChangeHistory.ChangeType.Ban orderby change.TimeChanged descending
orderby change.TimeChanged descending join originClient in ctx.Clients
join originClient in ctx.Clients on (change.ImpersonationEntityId ?? change.OriginEntityId) equals originClient.ClientId
on (change.ImpersonationEntityId ?? change.OriginEntityId) equals originClient.ClientId join targetClient in ctx.Clients
join targetClient in ctx.Clients on change.TargetEntityId equals targetClient.ClientId
on change.TargetEntityId equals targetClient.ClientId into targetChange
into targetChange from targetClient in targetChange.DefaultIfEmpty()
from targetClient in targetChange.DefaultIfEmpty() select new AuditInfo()
select new AuditInfo() {
{ Action = change.TypeOfChange.ToString(),
Action = change.TypeOfChange.ToString(), OriginName = originClient.CurrentAlias.Name,
OriginName = originClient.CurrentAlias.Name, OriginId = originClient.ClientId,
OriginId = originClient.ClientId, TargetName = targetClient == null ? "" : targetClient.CurrentAlias.Name,
TargetName = targetClient == null ? "" : targetClient.CurrentAlias.Name, TargetId = targetClient == null ? new int?() : targetClient.ClientId,
TargetId = targetClient == null ? new int?() : targetClient.ClientId, When = change.TimeChanged,
When = change.TimeChanged, Data = change.Comment,
Data = change.Comment, OldValue = change.PreviousValue,
OldValue = change.PreviousValue, NewValue = change.CurrentValue
NewValue = change.CurrentValue })
}) .Skip(paginationInfo.Offset)
.Skip(paginationInfo.Offset) .Take(paginationInfo.Count);
.Take(paginationInfo.Count);
return await iqItems.ToListAsync(); return await iqItems.ToListAsync();
}
} }
} }
} }