fix memory leak issue related to AddDbContext not working as expected
This commit is contained in:
parent
b2d282d412
commit
bd3f0caf60
@ -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<DatabaseContext, SqliteDatabaseContext>(options =>
|
||||
options.UseSqlite(connectionString), ServiceLifetime.Transient);
|
||||
var builder = new DbContextOptionsBuilder<SqliteDatabaseContext>()
|
||||
.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<DatabaseContext, MySqlDatabaseContext>(options =>
|
||||
options.UseMySql(
|
||||
appConfig.ConnectionString + (appendTimeout ? ";default command timeout=0" : ""),
|
||||
mysqlOptions => mysqlOptions.EnableRetryOnFailure()), ServiceLifetime.Transient);
|
||||
break;
|
||||
var mysqlBuilder = new DbContextOptionsBuilder<MySqlDatabaseContext>()
|
||||
.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<DatabaseContext, PostgresqlDatabaseContext>(options =>
|
||||
options.UseNpgsql(appConfig.ConnectionString + (appendTimeout ? ";Command Timeout=0" : ""),
|
||||
postgresqlOptions => postgresqlOptions.EnableRetryOnFailure()), ServiceLifetime.Transient);
|
||||
break;
|
||||
var postgresqlBuilder = new DbContextOptionsBuilder<PostgresqlDatabaseContext>()
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// creates a new database context
|
||||
/// </summary>
|
||||
@ -25,10 +28,10 @@ namespace IW4MAdmin.Application.Factories
|
||||
/// <returns></returns>
|
||||
public DatabaseContext CreateContext(bool? enableTracking = true)
|
||||
{
|
||||
var context = _serviceProvider.GetRequiredService<DatabaseContext>();
|
||||
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}")
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -361,7 +361,7 @@ namespace IW4MAdmin.Application
|
||||
.AddSingleton<SharedLibraryCore.Interfaces.ILogger, Logger>()
|
||||
.AddSingleton<IClientNoticeMessageFormatter, ClientNoticeMessageFormatter>()
|
||||
.AddSingleton(translationLookup)
|
||||
.AddDatabaseContext(appConfig);
|
||||
.AddDatabaseContextOptions(appConfig);
|
||||
|
||||
if (args.Contains("serialevents"))
|
||||
{
|
||||
|
@ -28,7 +28,7 @@ namespace IW4MAdmin.Application.Meta
|
||||
|
||||
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()
|
||||
.Where(_penalty => query.ClientId == _penalty.PunisherId)
|
||||
|
@ -31,7 +31,7 @@ namespace IW4MAdmin.Application.Meta
|
||||
public async Task<ResourceQueryHelperResult<ReceivedPenaltyResponse>> 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)
|
||||
|
@ -28,7 +28,7 @@ namespace IW4MAdmin.Application.Meta
|
||||
|
||||
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;
|
||||
|
||||
var iqAliasUpdates = ctx.Aliases
|
||||
|
@ -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<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
|
||||
.Where(_meta => _meta.Key == metaKey)
|
||||
|
@ -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<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))
|
||||
{
|
||||
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<EFClientStatistics>()
|
||||
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<EFClientStatistics>()
|
||||
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"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -68,121 +68,118 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
/// <returns></returns>
|
||||
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>()
|
||||
.Where(r => r.RatingHistory.ClientId == clientId)
|
||||
.Where(r => r.ServerId == null)
|
||||
.Where(r => r.Newest)
|
||||
.Select(r => r.Performance)
|
||||
.FirstOrDefaultAsync();
|
||||
var iqClientRanking = context.Set<EFRating>()
|
||||
.Where(r => r.RatingHistory.ClientId != clientId)
|
||||
.Where(r => r.Performance > clientPerformance)
|
||||
.Where(GetRankingFunc());
|
||||
|
||||
if (clientPerformance != 0)
|
||||
{
|
||||
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 await iqClientRanking.CountAsync() + 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public async Task<List<TopStatsInfo>> 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<EFRating>()
|
||||
.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
|
||||
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<EFRating>()
|
||||
.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<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)
|
||||
// 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<EFRating>()
|
||||
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<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>
|
||||
@ -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<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>();
|
||||
// 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<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 = 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<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>()
|
||||
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<IW4Info.HitLocation>()
|
||||
.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<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;
|
||||
}
|
||||
|
||||
@ -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<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
|
||||
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();
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -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<EFServerStatistics>();
|
||||
serverStats = serverStatsSet.FirstOrDefault(s => s.ServerId == serverId);
|
||||
|
||||
if (serverStats == null)
|
||||
{
|
||||
var serverStatsSet = ctx.Set<EFServerStatistics>();
|
||||
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<EFServerStatistics>();
|
||||
serverStatsSet.Update(_servers[serverId].ServerStatistics);
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
await using var ctx = _contextFactory.CreateContext();
|
||||
var serverStatsSet = ctx.Set<EFServerStatistics>();
|
||||
serverStatsSet.Update(_servers[serverId].ServerStatistics);
|
||||
await ctx.SaveChangesAsync();
|
||||
|
||||
foreach (var stats in sv.GetClientsAsList()
|
||||
.Select(_client => _client.GetAdditionalProperty<EFClientStatistics>(CLIENT_STATS_KEY))
|
||||
|
@ -26,7 +26,7 @@ namespace Stats.Helpers
|
||||
public async Task<ResourceQueryHelperResult<StatsInfoResult>> QueryResource(StatsInfoRequest query)
|
||||
{
|
||||
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
|
||||
var ratings = await context.Set<EFClientRatingHistory>()
|
||||
|
@ -173,11 +173,9 @@ namespace IW4MAdmin.Plugins.Stats
|
||||
{
|
||||
IList<EFClientStatistics> clientStats;
|
||||
int messageCount = 0;
|
||||
using (var ctx = _databaseContextFactory.CreateContext(enableTracking: false))
|
||||
{
|
||||
clientStats = await ctx.Set<EFClientStatistics>().Where(c => c.ClientId == request.ClientId).ToListAsync();
|
||||
messageCount = await ctx.Set<EFClientMessage>().CountAsync(_message => _message.ClientId == request.ClientId);
|
||||
}
|
||||
await using var ctx = _databaseContextFactory.CreateContext(enableTracking: false);
|
||||
clientStats = await ctx.Set<EFClientStatistics>().Where(c => c.ClientId == request.ClientId).ToListAsync();
|
||||
messageCount = await ctx.Set<EFClientMessage>().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<EFClientStatistics> clientStats;
|
||||
|
||||
using (var ctx = _databaseContextFactory.CreateContext(enableTracking: false))
|
||||
{
|
||||
clientStats = await ctx.Set<EFClientStatistics>()
|
||||
.Include(c => c.HitLocations)
|
||||
.Where(c => c.ClientId == request.ClientId)
|
||||
.ToListAsync();
|
||||
}
|
||||
await using var ctx = _databaseContextFactory.CreateContext(enableTracking: false);
|
||||
clientStats = await ctx.Set<EFClientStatistics>()
|
||||
.Include(c => c.HitLocations)
|
||||
.Where(c => c.ClientId == request.ClientId)
|
||||
.ToListAsync();
|
||||
|
||||
double headRatio = 0;
|
||||
double chestRatio = 0;
|
||||
|
@ -41,7 +41,7 @@ namespace StatsWeb
|
||||
}
|
||||
|
||||
var result = new ResourceQueryHelperResult<MessageResponse>();
|
||||
using var context = _contextFactory.CreateContext(enableTracking: false);
|
||||
await using var context = _contextFactory.CreateContext(enableTracking: false);
|
||||
|
||||
if (serverCache == null)
|
||||
{
|
||||
|
@ -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 () =>
|
||||
{
|
||||
|
@ -13,7 +13,7 @@ namespace SharedLibraryCore.Database.MigrationContext
|
||||
}
|
||||
}
|
||||
|
||||
public MySqlDatabaseContext(DbContextOptions<MySqlDatabaseContext> options) : base(options)
|
||||
public MySqlDatabaseContext(DbContextOptions options) : base(options)
|
||||
{
|
||||
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ namespace SharedLibraryCore.Database.MigrationContext
|
||||
}
|
||||
}
|
||||
|
||||
public PostgresqlDatabaseContext(DbContextOptions<PostgresqlDatabaseContext> options) : base(options)
|
||||
public PostgresqlDatabaseContext(DbContextOptions options) : base(options)
|
||||
{
|
||||
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ namespace SharedLibraryCore.Database.MigrationContext
|
||||
}
|
||||
}
|
||||
|
||||
public SqliteDatabaseContext(DbContextOptions<SqliteDatabaseContext> options) : base(options)
|
||||
public SqliteDatabaseContext(DbContextOptions options) : base(options)
|
||||
{
|
||||
|
||||
}
|
||||
|
@ -91,12 +91,11 @@ namespace SharedLibraryCore.Database.Models
|
||||
SetAdditionalProperty("_reportCount", 0);
|
||||
ReceivedPenalties = new List<EFPenalty>();
|
||||
_processingEvent = new SemaphoreSlim(1, 1);
|
||||
|
||||
}
|
||||
|
||||
~EFClient()
|
||||
{
|
||||
_processingEvent.Dispose();
|
||||
_processingEvent?.Dispose();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
|
@ -22,34 +22,32 @@ namespace SharedLibraryCore.Repositories
|
||||
/// <inheritdoc/>
|
||||
public async Task<IList<AuditInfo>> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user