using Dapper; using EVCB_OCPP.Domain; using EVCB_OCPP.Domain.ConnectionFactory; using EVCB_OCPP.WSServer.Helper; using EVCB_OCPP.WSServer.Service.WsService; using log4net; using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using NLog.Fluent; using System; using System.Collections.Generic; using System.Data; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; using static System.Runtime.InteropServices.JavaScript.JSType; namespace EVCB_OCPP.WSServer.Service.DbService; public interface IConnectionLogdbService { void WarmUpLog(); void WriteMachineLog(WsClientData clientData, string data, string messageType, string errorMsg = "", bool isSent = false); } public class ConnectionLogdbService : IConnectionLogdbService { public const string LimitConfigKey = "ConnectionLogDbLimit"; public ConnectionLogdbService( IDbContextFactory connectionLogdbContextFactory, ISqlConnectionFactory sqlConnectionFactory, ILogger logger, IConfiguration configuration) { this.connectionLogdbContextFactory = connectionLogdbContextFactory; this.sqlConnectionFactory = sqlConnectionFactory; this.logger = logger; //connectionLogdbConnectionString = configuration.GetConnectionString("MeterValueDBContext"); var opLimit = GetLimit(configuration); queueHandler = new(WriteMachineLogEF, opLimit); InitInsertConnectonLogHandler(); } private readonly IDbContextFactory connectionLogdbContextFactory; private readonly ISqlConnectionFactory sqlConnectionFactory; private readonly ILogger logger; private readonly QueueHandler queueHandler; //private readonly string connectionLogdbConnectionString; private readonly Queue _existTables = new(); private GroupHandler insertConnectonLogHandler; public void WarmUpLog() { try { using (var log = connectionLogdbContextFactory.CreateDbContext()) { log.MachineConnectionLog.ToList(); } } catch (Exception ex) { logger.LogError(ex.Message); logger.LogError(ex.StackTrace); //Console.WriteLine(ex.ToString()); } } public void WriteMachineLog(WsClientData clientData, string data, string messageType, string errorMsg = "", bool isSent = false) { var log = new MachineLog(clientData, data, messageType, errorMsg, isSent); //queueHandler.Enqueue(log); //_ = WriteMachineLogEF(log); insertConnectonLogHandler.HandleAsync(log); //_ = InsertWithDapper(log); } private void InitInsertConnectonLogHandler() { if (insertConnectonLogHandler is not null) { throw new Exception($"{nameof(InitInsertConnectonLogHandler)} should only called once"); } insertConnectonLogHandler = new GroupHandler( BundleInsertWithDapper, logger, workerCnt: 20 ); } private async Task WriteMachineLogEF(MachineLog log) { var watcher = Stopwatch.StartNew(); try { if (log.clientData == null || string.IsNullOrEmpty(log.data)) return; if (log.clientData.ChargeBoxId == null) { logger.LogCritical(log.clientData.Path.ToString() + "]********************session ChargeBoxId null sessionId=" + log.clientData.SessionID); } string sp = "[dbo].[uspInsertMachineConnectionLog] @CreatedOn," + "@ChargeBoxId,@MessageType,@Data,@Msg,@IsSent,@EVSEEndPoint,@Session"; var dd = DateTime.UtcNow; SqlParameter[] parameter = { new SqlParameter("CreatedOn", SqlDbType.DateTime){ Value = dd }, new SqlParameter("ChargeBoxId", SqlDbType.NVarChar, 50){ Value= log.clientData.ChargeBoxId==null?"unknown":log.clientData.ChargeBoxId.Replace("'","''") }, new SqlParameter("MessageType", SqlDbType.NVarChar , 50){ Value = log.messageType.Replace("'","''")}, new SqlParameter("Data", SqlDbType.NVarChar, -1) { Value = log.data.Replace("'", "''") }, new SqlParameter("Msg", SqlDbType.NVarChar, 200) { Value = log.errorMsg.Replace("'", "''") }, new SqlParameter("IsSent", SqlDbType.Bit) { Value = log.isSent }, new SqlParameter("EVSEEndPoint", SqlDbType.NVarChar, 50) { Value = log.clientData.Endpoint == null ? "123" : log.clientData.Endpoint.ToString() }, new SqlParameter("Session", SqlDbType.NVarChar, 36) { Value = log.clientData.SessionID == null ? "123" : log.clientData.SessionID } }; using (var db = await connectionLogdbContextFactory.CreateDbContextAsync()) { await db.Database.ExecuteSqlRawAsync(sp, parameter); } } catch (Exception ex) { logger.LogError(ex.ToString()); } watcher.Stop(); if (watcher.ElapsedMilliseconds > 1000) { logger.LogWarning("WriteMachineLog too long {0}", watcher.ElapsedMilliseconds); } } private async Task BundleInsertWithDapper(BundleHandlerData bundleHandlerData) { var watch = Stopwatch.StartNew(); var times = new List(); var workTime = DateTime.UtcNow; var parmsList = bundleHandlerData.Datas.ToList(); if (parmsList.Count == 0) { return; } var candidate = parmsList[0]; if (!await GetTableExist(workTime)) { times.Add(watch.ElapsedMilliseconds); await WriteMachineLogEF(candidate); times.Add(watch.ElapsedMilliseconds); if (watch.ElapsedMilliseconds > 500) { logger.LogWarning($"ConnectionLog InsertWithDapper {string.Join("/", times)}"); } parmsList.Remove(candidate); bundleHandlerData.AddCompletedData(candidate); } times.Add(watch.ElapsedMilliseconds); using SqlConnection sqlConnection = await sqlConnectionFactory.CreateAsync(); //using var trans = await sqlConnection.BeginTransactionAsync(); times.Add(watch.ElapsedMilliseconds); var tableName = GetTableName(workTime); string withHourIndexCommand = $""" INSERT INTO {tableName} (CreatedOn, ChargeBoxId, MessageType, Data, Msg, IsSent, EVSEEndPoint, Session, HourIndex) VALUES (@CreatedOn, @ChargeBoxId, @MessageType, @Data, @Msg, @IsSent, @EVSEEndPoint, @Session, @HourIndex); """; string command = $""" INSERT INTO {tableName} (CreatedOn, ChargeBoxId, MessageType, Data, Msg, IsSent, EVSEEndPoint, Session) VALUES (@CreatedOn, @ChargeBoxId, @MessageType, @Data, @Msg, @IsSent, @EVSEEndPoint, @Session); """; foreach (var log in parmsList) { var parameters = new DynamicParameters(); parameters.Add("CreatedOn", workTime, DbType.DateTime); parameters.Add("ChargeBoxId", log.clientData.ChargeBoxId == null ? "unknown" : log.clientData.ChargeBoxId.Replace("'", "''"), DbType.String, size: 50); ; parameters.Add("MessageType", log.messageType.Replace("'", "''"), DbType.String, size: 50); parameters.Add("Data", log.data.Replace("'", "''"), DbType.String); parameters.Add("Msg", log.errorMsg.Replace("'", "''"), DbType.String, size: 200); parameters.Add("IsSent", log.isSent, DbType.Boolean); parameters.Add("EVSEEndPoint", log.clientData.Endpoint == null ? "123" : log.clientData.Endpoint.ToString(), DbType.String, size: 50); parameters.Add("Session", log.clientData.SessionID == null ? "123" : log.clientData.SessionID, DbType.String, size: 36); parameters.Add("HourIndex", workTime.Hour, DbType.Int32); try { await sqlConnection.ExecuteAsync(withHourIndexCommand, parameters); } catch { logger.LogInformation("Connection Log insert with HourIndex failed, insert without HourIndex"); await sqlConnection.ExecuteAsync(command, parameters); } bundleHandlerData.AddCompletedData(log); } times.Add(watch.ElapsedMilliseconds); //await trans.CommitAsync(); watch.Stop(); if (watch.ElapsedMilliseconds > 1000) { logger.LogWarning($"MachineLog Bundle Dapper {string.Join("/", times)} count:{bundleHandlerData.Datas.Count()}"); } return; } private async ValueTask GetTableExist(DateTime tableDateTime) { var tableName = GetTableName(tableDateTime); if (_existTables.Contains(tableName)) { return true; } FormattableString checkTableSql = $"SELECT Count(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = {tableName}"; using var db = await connectionLogdbContextFactory.CreateDbContextAsync(); var resultList = await db.Database.SqlQuery(checkTableSql)?.ToListAsync(); if (resultList is not null && resultList.Count > 0 && resultList[0] > 0) { _existTables.Enqueue(tableName); if (_existTables.Count > 30) { _existTables.TryDequeue(out _); } return true; } return false; } private static string GetTableName(DateTime dateTime) => $"MachineConnectionLog{dateTime:yyMMdd}"; private int GetLimit(IConfiguration configuration) { var limitConfig = configuration[LimitConfigKey]; int limit = 10; if (limitConfig != default) { int.TryParse(limitConfig, out limit); } return limit; } } internal record MachineLog(WsClientData clientData, string data, string messageType, string errorMsg, bool isSent);