ConnectionLogdbService.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. using Dapper;
  2. using EVCB_OCPP.Domain;
  3. using EVCB_OCPP.Domain.ConnectionFactory;
  4. using EVCB_OCPP.WSServer.Helper;
  5. using EVCB_OCPP.WSServer.Service.WsService;
  6. using log4net;
  7. using Microsoft.Data.SqlClient;
  8. using Microsoft.EntityFrameworkCore;
  9. using Microsoft.Extensions.Configuration;
  10. using Microsoft.Extensions.Logging;
  11. using NLog.Fluent;
  12. using System;
  13. using System.Collections.Generic;
  14. using System.Data;
  15. using System.Diagnostics;
  16. using System.Linq;
  17. using System.Text;
  18. using System.Threading.Tasks;
  19. using static System.Runtime.InteropServices.JavaScript.JSType;
  20. namespace EVCB_OCPP.WSServer.Service.DbService;
  21. public interface IConnectionLogdbService
  22. {
  23. void WarmUpLog();
  24. void WriteMachineLog(WsClientData clientData, string data, string messageType, string errorMsg = "", bool isSent = false);
  25. }
  26. public class ConnectionLogdbService : IConnectionLogdbService
  27. {
  28. public const string LimitConfigKey = "ConnectionLogDbLimit";
  29. public ConnectionLogdbService(
  30. IDbContextFactory<ConnectionLogDBContext> connectionLogdbContextFactory,
  31. ISqlConnectionFactory<ConnectionLogDBContext> sqlConnectionFactory,
  32. ILogger<ConnectionLogdbService> logger,
  33. IConfiguration configuration)
  34. {
  35. this.connectionLogdbContextFactory = connectionLogdbContextFactory;
  36. this.sqlConnectionFactory = sqlConnectionFactory;
  37. this.logger = logger;
  38. //connectionLogdbConnectionString = configuration.GetConnectionString("MeterValueDBContext");
  39. var opLimit = GetLimit(configuration);
  40. queueHandler = new(WriteMachineLogEF, opLimit);
  41. InitInsertConnectonLogHandler();
  42. }
  43. private readonly IDbContextFactory<ConnectionLogDBContext> connectionLogdbContextFactory;
  44. private readonly ISqlConnectionFactory<ConnectionLogDBContext> sqlConnectionFactory;
  45. private readonly ILogger<ConnectionLogdbService> logger;
  46. private readonly QueueHandler<MachineLog> queueHandler;
  47. //private readonly string connectionLogdbConnectionString;
  48. private readonly Queue<string> _existTables = new();
  49. private GroupHandler<MachineLog> insertConnectonLogHandler;
  50. public void WarmUpLog()
  51. {
  52. try
  53. {
  54. using (var log = connectionLogdbContextFactory.CreateDbContext())
  55. {
  56. log.MachineConnectionLog.ToList();
  57. }
  58. }
  59. catch (Exception ex)
  60. {
  61. logger.LogError(ex.Message);
  62. logger.LogError(ex.StackTrace);
  63. //Console.WriteLine(ex.ToString());
  64. }
  65. }
  66. public void WriteMachineLog(WsClientData clientData, string data, string messageType, string errorMsg = "", bool isSent = false)
  67. {
  68. var log = new MachineLog(clientData, data, messageType, errorMsg, isSent);
  69. //queueHandler.Enqueue(log);
  70. //_ = WriteMachineLogEF(log);
  71. insertConnectonLogHandler.HandleAsync(log);
  72. //_ = InsertWithDapper(log);
  73. }
  74. private void InitInsertConnectonLogHandler()
  75. {
  76. if (insertConnectonLogHandler is not null)
  77. {
  78. throw new Exception($"{nameof(InitInsertConnectonLogHandler)} should only called once");
  79. }
  80. insertConnectonLogHandler = new GroupHandler<MachineLog>(
  81. BundleInsertWithDapper,
  82. logger,
  83. workerCnt: 20
  84. );
  85. }
  86. private async Task WriteMachineLogEF(MachineLog log)
  87. {
  88. var watcher = Stopwatch.StartNew();
  89. try
  90. {
  91. if (log.clientData == null || string.IsNullOrEmpty(log.data)) return;
  92. if (log.clientData.ChargeBoxId == null)
  93. {
  94. logger.LogCritical(log.clientData.Path.ToString() + "]********************session ChargeBoxId null sessionId=" + log.clientData.SessionID);
  95. }
  96. string sp = "[dbo].[uspInsertMachineConnectionLog] @CreatedOn," +
  97. "@ChargeBoxId,@MessageType,@Data,@Msg,@IsSent,@EVSEEndPoint,@Session";
  98. var dd = DateTime.UtcNow;
  99. SqlParameter[] parameter =
  100. {
  101. new SqlParameter("CreatedOn", SqlDbType.DateTime){ Value = dd },
  102. new SqlParameter("ChargeBoxId", SqlDbType.NVarChar, 50){ Value= log.clientData.ChargeBoxId==null?"unknown":log.clientData.ChargeBoxId.Replace("'","''") },
  103. new SqlParameter("MessageType", SqlDbType.NVarChar , 50){ Value = log.messageType.Replace("'","''")},
  104. new SqlParameter("Data", SqlDbType.NVarChar, -1) { Value = log.data.Replace("'", "''") },
  105. new SqlParameter("Msg", SqlDbType.NVarChar, 200) { Value = log.errorMsg.Replace("'", "''") },
  106. new SqlParameter("IsSent", SqlDbType.Bit) { Value = log.isSent },
  107. new SqlParameter("EVSEEndPoint", SqlDbType.NVarChar, 50) { Value = log.clientData.Endpoint == null ? "123" : log.clientData.Endpoint.ToString() },
  108. new SqlParameter("Session", SqlDbType.NVarChar, 36) { Value = log.clientData.SessionID == null ? "123" : log.clientData.SessionID }
  109. };
  110. using (var db = await connectionLogdbContextFactory.CreateDbContextAsync())
  111. {
  112. await db.Database.ExecuteSqlRawAsync(sp, parameter);
  113. }
  114. }
  115. catch (Exception ex)
  116. {
  117. logger.LogError(ex.ToString());
  118. }
  119. watcher.Stop();
  120. if (watcher.ElapsedMilliseconds > 1000)
  121. {
  122. logger.LogWarning("WriteMachineLog too long {0}", watcher.ElapsedMilliseconds);
  123. }
  124. }
  125. private async Task BundleInsertWithDapper(BundleHandlerData<MachineLog> bundleHandlerData)
  126. {
  127. var watch = Stopwatch.StartNew();
  128. var times = new List<long>();
  129. var workTime = DateTime.UtcNow;
  130. var parmsList = bundleHandlerData.Datas.ToList();
  131. if (parmsList.Count == 0)
  132. {
  133. return;
  134. }
  135. var candidate = parmsList[0];
  136. if (!await GetTableExist(workTime))
  137. {
  138. times.Add(watch.ElapsedMilliseconds);
  139. await WriteMachineLogEF(candidate);
  140. times.Add(watch.ElapsedMilliseconds);
  141. if (watch.ElapsedMilliseconds > 500)
  142. {
  143. logger.LogWarning($"ConnectionLog InsertWithDapper {string.Join("/", times)}");
  144. }
  145. parmsList.Remove(candidate);
  146. bundleHandlerData.AddCompletedData(candidate);
  147. }
  148. times.Add(watch.ElapsedMilliseconds);
  149. using SqlConnection sqlConnection = await sqlConnectionFactory.CreateAsync();
  150. //using var trans = await sqlConnection.BeginTransactionAsync();
  151. times.Add(watch.ElapsedMilliseconds);
  152. var tableName = GetTableName(workTime);
  153. string withHourIndexCommand = $"""
  154. INSERT INTO {tableName} (CreatedOn, ChargeBoxId, MessageType, Data, Msg, IsSent, EVSEEndPoint, Session, HourIndex)
  155. VALUES (@CreatedOn, @ChargeBoxId, @MessageType, @Data, @Msg, @IsSent, @EVSEEndPoint, @Session, @HourIndex);
  156. """;
  157. string command = $"""
  158. INSERT INTO {tableName} (CreatedOn, ChargeBoxId, MessageType, Data, Msg, IsSent, EVSEEndPoint, Session)
  159. VALUES (@CreatedOn, @ChargeBoxId, @MessageType, @Data, @Msg, @IsSent, @EVSEEndPoint, @Session);
  160. """;
  161. foreach (var log in parmsList)
  162. {
  163. var parameters = new DynamicParameters();
  164. parameters.Add("CreatedOn", workTime, DbType.DateTime);
  165. parameters.Add("ChargeBoxId", log.clientData.ChargeBoxId == null ? "unknown" : log.clientData.ChargeBoxId.Replace("'", "''"), DbType.String, size: 50); ;
  166. parameters.Add("MessageType", log.messageType.Replace("'", "''"), DbType.String, size: 50);
  167. parameters.Add("Data", log.data.Replace("'", "''"), DbType.String);
  168. parameters.Add("Msg", log.errorMsg.Replace("'", "''"), DbType.String, size: 200);
  169. parameters.Add("IsSent", log.isSent, DbType.Boolean);
  170. parameters.Add("EVSEEndPoint", log.clientData.Endpoint == null ? "123" : log.clientData.Endpoint.ToString(), DbType.String, size: 50);
  171. parameters.Add("Session", log.clientData.SessionID == null ? "123" : log.clientData.SessionID, DbType.String, size: 36);
  172. parameters.Add("HourIndex", workTime.Hour, DbType.Int32);
  173. try
  174. {
  175. await sqlConnection.ExecuteAsync(withHourIndexCommand, parameters);
  176. }
  177. catch
  178. {
  179. logger.LogInformation("Connection Log insert with HourIndex failed, insert without HourIndex");
  180. await sqlConnection.ExecuteAsync(command, parameters);
  181. }
  182. bundleHandlerData.AddCompletedData(log);
  183. }
  184. times.Add(watch.ElapsedMilliseconds);
  185. //await trans.CommitAsync();
  186. watch.Stop();
  187. if (watch.ElapsedMilliseconds > 1000)
  188. {
  189. logger.LogWarning($"MachineLog Bundle Dapper {string.Join("/", times)} count:{bundleHandlerData.Datas.Count()}");
  190. }
  191. return;
  192. }
  193. private async ValueTask<bool> GetTableExist(DateTime tableDateTime)
  194. {
  195. var tableName = GetTableName(tableDateTime);
  196. if (_existTables.Contains(tableName))
  197. {
  198. return true;
  199. }
  200. FormattableString checkTableSql = $"SELECT Count(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = {tableName}";
  201. using var db = await connectionLogdbContextFactory.CreateDbContextAsync();
  202. var resultList = await db.Database.SqlQuery<int>(checkTableSql)?.ToListAsync();
  203. if (resultList is not null && resultList.Count > 0 && resultList[0] > 0)
  204. {
  205. _existTables.Enqueue(tableName);
  206. if (_existTables.Count > 30)
  207. {
  208. _existTables.TryDequeue(out _);
  209. }
  210. return true;
  211. }
  212. return false;
  213. }
  214. private static string GetTableName(DateTime dateTime)
  215. => $"MachineConnectionLog{dateTime:yyMMdd}";
  216. private int GetLimit(IConfiguration configuration)
  217. {
  218. var limitConfig = configuration[LimitConfigKey];
  219. int limit = 10;
  220. if (limitConfig != default)
  221. {
  222. int.TryParse(limitConfig, out limit);
  223. }
  224. return limit;
  225. }
  226. }
  227. internal record MachineLog(WsClientData clientData, string data, string messageType, string errorMsg, bool isSent);