using Dapper; using EVCB_OCPP.Domain; using EVCB_OCPP.Domain.Models.Database; using EVCB_OCPP.WSServer.Helper; using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using OCPPPackage.Profiles; using System.Data; namespace EVCB_OCPP.WSServer.Service; public interface IMainDbService { Task GetMachineAuthorizationKey(string ChargeBoxId, CancellationToken token = default); Task GetMachineConfiguration(string ChargeBoxId, string configName, CancellationToken token = default); Task GetMachineHeartbeatInterval(string ChargeBoxId); Task GetMachineIdAndCustomerInfo(string ChargeBoxId, CancellationToken token = default); Task GetMachineSecurityProfile(string ChargeBoxId, CancellationToken token = default); Task UpdateMachineBasicInfo(string ChargeBoxId, Machine machine); Task AddOCMF(OCMF oCMF); ValueTask GetConnectorStatus(string ChargeBoxId, int ConnectorId); Task UpdateConnectorStatus(string Id, ConnectorStatus connectorStatus); ValueTask AddConnectorStatus(string ChargeBoxId, byte ConnectorId, DateTime CreatedOn, int Status, int ChargePointErrorCodeId, string ErrorInfo, string VendorId, string VendorErrorCode); Task AddServerMessage(ServerMessage message); Task AddServerMessage(string ChargeBoxId, string OutAction, object OutRequest, string CreatedBy = "", DateTime? CreatedOn = null, string SerialNo = "", string InMessage = ""); ValueTask AddMachineError(byte ConnectorId, DateTime CreatedOn, int Status, string ChargeBoxId, int ErrorCodeId, string ErrorInfo, int PreStatus, string VendorErrorCode, string VendorId); ValueTask GetCustomer(string id, CancellationToken token = default); ValueTask GetCustomer(Guid id, CancellationToken token = default); Task GetCustomerIdByChargeBoxId(string chargeboxId); Task TryGetDuplicatedTransactionId(string chargeBoxId, Guid customerId, int connectorId, DateTime timestamp); Task AddNewTransactionRecord(TransactionRecord newTransaction); Task GetTransactionForStopTransaction(int transactionId, string chargeBoxId); Task UpdateTransaction(int transactionId, int meterStop, DateTime stopTime, int stopReasonId, string stopReason, string stopIdTag, string receipt, int cost); Task UpdateHeartBeats(IEnumerable heartBeatsData); Task UpdateHeartBeats(List machineIds); Task UpdateTransactionSOC(int id, string startsoc, string stopsoc); Task UpdateMachineConnectionType(string chargeBoxId, int v); } public class MainDbService : IMainDbService { public MainDbService( IDbContextFactory contextFactory, SqlConnectionFactory sqlConnectionFactory, IMemoryCache memoryCache, IConfiguration configuration, ILoggerFactory loggerFactory, ILogger logger) { this.contextFactory = contextFactory; this.sqlConnectionFactory = sqlConnectionFactory; this.memoryCache = memoryCache; this.loggerFactory = loggerFactory; this.logger = logger; var startupLimit = GetStartupLimit(configuration); //this.connectionString = configuration.GetConnectionString("MainDBContext"); this.startupSemaphore = new(startupLimit); var opLimit = GetOpLimit(configuration); this.opSemaphore = new SemaphoreSlim(opLimit); InitUpdateConnectorStatusHandler(); InitUpdateMachineBasicInfoHandler(); InitAddServerMessageHandler(); } private const string CustomerMemCacheKeyFromat = "Customer_{0}"; //private const string ChargeBoxConnectorIdMemCacheKeyFromat = "Connector_{0}{1}"; private readonly IDbContextFactory contextFactory; private readonly SqlConnectionFactory sqlConnectionFactory; private readonly IMemoryCache memoryCache; private readonly ILoggerFactory loggerFactory; private readonly ILogger logger; //private string connectionString; private readonly QueueSemaphore startupSemaphore; private readonly SemaphoreSlim opSemaphore; private GroupHandler statusNotificationHandler; private GroupHandler updateMachineBasicInfoHandler; private GroupHandler addServerMessageHandler; public async Task GetMachineIdAndCustomerInfo(string ChargeBoxId, CancellationToken token = default) { using var semaphoreWrapper = await startupSemaphore.GetToken(); using var db = await contextFactory.CreateDbContextAsync(token); var machine = await db.Machine.Where(x => x.ChargeBoxId == ChargeBoxId && x.IsDelete == false).Select(x => new { x.CustomerId, x.Id }).AsNoTracking().FirstOrDefaultAsync(); if (machine == null) { return new MachineAndCustomerInfo(string.Empty, Guid.Empty, "Unknown"); } //var customerName = await db.Customer.Where(x => x.Id == machine.CustomerId).Select(x => x.Name).FirstOrDefaultAsync(); var customer = await GetCustomer(machine.CustomerId, token); var customerName = customer?.Name; return new MachineAndCustomerInfo(machine.Id, machine.CustomerId, customerName); } public async Task GetMachineConfiguration(string ChargeBoxId, string configName, CancellationToken token = default) { using var semaphoreWrapper = await startupSemaphore.GetToken(); using var db = await contextFactory.CreateDbContextAsync(); return await db.MachineConfigurations .Where(x => x.ChargeBoxId == ChargeBoxId && x.ConfigureName == configName) .Select(x => x.ConfigureSetting).FirstOrDefaultAsync(); } public Task GetMachineSecurityProfile(string ChargeBoxId, CancellationToken token = default) { return GetMachineConfiguration(ChargeBoxId, StandardConfiguration.SecurityProfile, token); } public Task GetMachineAuthorizationKey(string ChargeBoxId, CancellationToken token = default) { return GetMachineConfiguration(ChargeBoxId, StandardConfiguration.AuthorizationKey , token); } public Task GetMachineHeartbeatInterval(string ChargeBoxId) { return GetMachineConfiguration(ChargeBoxId, StandardConfiguration.HeartbeatInterval); } public Task UpdateMachineBasicInfo(string ChargeBoxId, Machine machine) { //return UpdateMachineBasicInfoEF(ChargeBoxId, machine); return updateMachineBasicInfoHandler.HandleAsync(new UpdateMachineBasicInfoParam(ChargeBoxId, machine)); } public async Task AddOCMF(OCMF oCMF) { using var db = await contextFactory.CreateDbContextAsync(); await db.OCMF.AddAsync(oCMF); await db.SaveChangesAsync(); } public async ValueTask AddConnectorStatus( string ChargeBoxId, byte ConnectorId, DateTime CreatedOn, int Status, int ChargePointErrorCodeId, string ErrorInfo, string VendorId, string VendorErrorCode) { using var db = await contextFactory.CreateDbContextAsync(); var _currentStatus = new Domain.Models.Database.ConnectorStatus() { ChargeBoxId = ChargeBoxId, ConnectorId = ConnectorId, CreatedOn = CreatedOn, Status = Status, ChargePointErrorCodeId = ChargePointErrorCodeId, ErrorInfo = ErrorInfo, VendorId = VendorId, VendorErrorCode = VendorErrorCode, Id = Guid.NewGuid().ToString() }; await db.ConnectorStatus.AddAsync(_currentStatus); await db.SaveChangesAsync(); //memoryCache.Set( // string.Format(ChargeBoxConnectorIdMemCacheKeyFromat, ChargeBoxId, ConnectorId) // , _currentStatus, TimeSpan.FromHours(12)); } public async ValueTask GetConnectorStatus(string ChargeBoxId, int ConnectorId) { //var key = string.Format(ChargeBoxConnectorIdMemCacheKeyFromat, ChargeBoxId, ConnectorId); //if (memoryCache.TryGetValue(key, out var status)) //{ // return status; //} using var db = await contextFactory.CreateDbContextAsync(); var statusFromDb = await db.ConnectorStatus.Where(x => x.ChargeBoxId == ChargeBoxId && x.ConnectorId == ConnectorId).AsNoTracking().FirstOrDefaultAsync(); //memoryCache.Set(key, statusFromDb, TimeSpan.FromHours(12)); return statusFromDb; } public async Task UpdateConnectorStatus(string Id, ConnectorStatus Status) { //await statusNotificationHandler.HandleAsync(new StatusNotificationParam(Id, Status)); //await UpdateConnectorStatusEF(Id, Status); await UpdateConnectorStatusDapper(Id, Status); //var key = string.Format(ChargeBoxConnectorIdMemCacheKeyFromat, Status.ChargeBoxId, Status.ConnectorId); //memoryCache.Set(key, Status, TimeSpan.FromHours(12)); return; } public Task GetCustomerIdByChargeBoxId(string chargeboxId) { //return GetCustomerIdByChargeBoxIdEF(chargeboxId); return GetCustomerIdByChargeBoxIdDapper(chargeboxId); } public Task TryGetDuplicatedTransactionId(string chargeBoxId, Guid customerId, int connectorId, DateTime timestamp) { //return TryGetDuplicatedTransactionIdEF(chargeBoxId, customerId, connectorId, timestamp); return TryGetDuplicatedTransactionIdDapper(chargeBoxId, customerId, connectorId, timestamp); } public ValueTask AddMachineError(byte ConnectorId, DateTime CreatedOn, int Status, string ChargeBoxId, int ErrorCodeId, string ErrorInfo, int PreStatus, string VendorErrorCode, string VendorId) { //return AddMachineErrorEF(ConnectorId, CreatedOn, Status, ChargeBoxId, ErrorCodeId, ErrorInfo, PreStatus, VendorErrorCode, VendorId); return AddMachineErrorDapper(ConnectorId, CreatedOn, Status, ChargeBoxId, ErrorCodeId, ErrorInfo, PreStatus, VendorErrorCode, VendorId); } public Task AddServerMessage(string ChargeBoxId, string OutAction, object OutRequest, string CreatedBy, DateTime? CreatedOn = null, string SerialNo = "", string InMessage = "") { if (string.IsNullOrEmpty(CreatedBy)) { CreatedBy = "Server"; } if (string.IsNullOrEmpty(SerialNo)) { SerialNo = Guid.NewGuid().ToString(); } var _CreatedOn = CreatedOn ?? DateTime.UtcNow; string _OutRequest = ""; if (OutRequest is not null) { _OutRequest = JsonConvert.SerializeObject( OutRequest, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore, Formatting = Formatting.None }); } var data = new ServerMessage() { ChargeBoxId = ChargeBoxId, CreatedBy = CreatedBy, CreatedOn = _CreatedOn, OutAction = OutAction, OutRequest = _OutRequest, SerialNo = SerialNo, InMessage = InMessage }; return AddServerMessage(data); } public Task AddServerMessage(ServerMessage message) { //return AddServerMessageEF(message); //return addServerMessageHandler.HandleAsync(message); return AddServerMessageDapper(message); } public ValueTask GetCustomer(string id, CancellationToken token = default) => GetCustomer(new Guid(id), token); public async ValueTask GetCustomer(Guid id, CancellationToken token = default) { var key = string.Format(CustomerMemCacheKeyFromat, id); if (memoryCache.TryGetValue(key, out var customer)) { return customer; } Customer toReturn = null; using (var db = await contextFactory.CreateDbContextAsync(token)) { toReturn = await db.Customer.FirstOrDefaultAsync(x => x.Id == id, token); } if (toReturn is not null) { memoryCache.Set(key, toReturn, TimeSpan.FromSeconds(15)); } return toReturn; } public Task AddNewTransactionRecord(TransactionRecord newTransaction) { //return AddNewTransactionRecordEF(newTransaction); return AddNewTransactionRecordDapper(newTransaction); } public Task GetTransactionForStopTransaction(int transactionId, string chargeBoxId) { //return GetTransactionForStopTransactionEF(transactionId, chargeBoxId); return GetTransactionForStopTransactionDapper(transactionId, chargeBoxId); } public Task UpdateTransaction(int transactionId, int meterStop, DateTime stopTime, int stopReasonId, string stopReason, string stopIdTag, string receipt, int cost) { //return UpdateTransactionEF(transactionId, meterStop, stopTime, stopReasonId, stopReason, stopIdTag, receipt, cost); return UpdateTransactionDapper(transactionId, meterStop, stopTime, stopReasonId, stopReason, stopIdTag, receipt, cost); } public async Task UpdateTransactionSOC(int id, string startSOC, string stopSOC) { var parameters = new DynamicParameters(); parameters.Add("@TransactionId", id, DbType.Int32, ParameterDirection.Input); parameters.Add("@StartSOC", startSOC, DbType.String, ParameterDirection.Input, 3); parameters.Add("@StopSOC", stopSOC, DbType.String, ParameterDirection.Input, 3); using var conn = await sqlConnectionFactory.CreateAsync(); var resultCnt = await conn.ExecuteAsync(""" UPDATE TransactionRecord SET StartSOC = @StartSOC, StopSOC = @StopSOC WHERE Id = @TransactionId """, parameters); if (resultCnt != 1) { throw new Exception("Update over one columes"); } return; } private async Task UpdateTransactionEF(int transactionId, int meterStop, DateTime stopTime, int stopReasonId, string stopReason, string stopIdTag, string receipt, int cost) { using var db = await contextFactory.CreateDbContextAsync(); var _transaction = db.TransactionRecord.Where(x => x.Id == transactionId //&& x.ChargeBoxId == session.ChargeBoxId ).FirstOrDefault(); _transaction.MeterStop = meterStop; _transaction.StopTime = stopTime; _transaction.StopReasonId = stopReasonId; _transaction.StopReason = stopReason; _transaction.StopIdTag = stopIdTag; _transaction.Receipt = receipt; _transaction.Cost = cost; //await db.SaveChangesAsync(); await db.SaveChangesAsync(); } private async Task UpdateTransactionDapper(int transactionId, int meterStop, DateTime stopTime, int stopReasonId, string stopReason, string stopIdTag, string receipt, int cost) { var parameters = new DynamicParameters(); parameters.Add("@TransactionId", transactionId, DbType.Int32, ParameterDirection.Input); parameters.Add("@MeterStop", meterStop, DbType.Decimal, ParameterDirection.Input, precision: 18, scale: 2); parameters.Add("@StopTime", stopTime, DbType.DateTime, ParameterDirection.Input); parameters.Add("@StopReasonId", stopReasonId, DbType.Int32, ParameterDirection.Input); parameters.Add("@StopReason", stopReason, DbType.String, ParameterDirection.Input, 60); parameters.Add("@StopIdTag", stopIdTag, DbType.String, ParameterDirection.Input, 20); parameters.Add("@Receipt", receipt, DbType.String, ParameterDirection.Input, 3000); parameters.Add("@Cost", cost, DbType.Decimal, ParameterDirection.Input, precision: 18, scale: 2); using var conn = await sqlConnectionFactory.CreateAsync(); var resultCnt = await conn.ExecuteAsync(""" UPDATE TransactionRecord SET MeterStop = @MeterStop, StopTime = @StopTime, StopReasonId = @StopReasonId, StopReason = @StopReason, StopIdTag = @StopIdTag, Receipt = @Receipt, Cost = @Cost WHERE Id = @TransactionId """, parameters); if (resultCnt != 1) { throw new Exception("Update over one columes"); } return; } public Task UpdateHeartBeats(IEnumerable heartBeatsData) { //return UpdateHeartBeatsEF(heartBeatsData); return UpdateHeartBeatsDapper(heartBeatsData); } public Task UpdateHeartBeats(List machineIds) { return UpdateHeartBeatsDapper(machineIds); } public async Task UpdateMachineConnectionType(string chargeBoxId, int connectionType) { using var semaphoreWrapper = await startupSemaphore.GetToken(); using var db = await contextFactory.CreateDbContextAsync(); var machine = await db.Machine.Where(x => x.ChargeBoxId == chargeBoxId).FirstOrDefaultAsync(); if (machine != null) { machine.ConnectionType = connectionType; await db.SaveChangesAsync(); } } private void InitUpdateConnectorStatusHandler() { if (statusNotificationHandler is not null) { throw new Exception($"{nameof(InitUpdateConnectorStatusHandler)} should only called once"); } statusNotificationHandler = new GroupHandler( handleFunc: BundleUpdateConnectorStatusDapper, logger: loggerFactory.CreateLogger("StatusNotificationHandler"), workerCnt: 1); } private void InitAddServerMessageHandler() { if (addServerMessageHandler is not null) { throw new Exception($"{nameof(InitAddServerMessageHandler)} should only called once"); } addServerMessageHandler = new GroupHandler( handleFunc: BundleAddServerMessage, logger: loggerFactory.CreateLogger("AddServerMessageHandler")); } private void InitUpdateMachineBasicInfoHandler() { if (updateMachineBasicInfoHandler is not null) { throw new Exception($"{nameof(InitUpdateMachineBasicInfoHandler)} should only called once"); } updateMachineBasicInfoHandler = new GroupHandler( handleFunc: BundelUpdateMachineBasicInfo, logger: loggerFactory.CreateLogger("UpdateMachineBasicInfoHandler"), workerCnt: 10); } private async Task UpdateMachineBasicInfoEF(string chargeBoxId, Machine machine) { using var semaphoreWrapper = await startupSemaphore.GetToken(); using var db = await contextFactory.CreateDbContextAsync(); var _machine = await db.Machine.FirstOrDefaultAsync(x => x.ChargeBoxId == chargeBoxId); _machine.ChargeBoxSerialNumber = machine.ChargeBoxSerialNumber; _machine.ChargePointSerialNumber = machine.ChargePointSerialNumber; _machine.ChargePointModel = machine.ChargePointModel; _machine.ChargePointVendor = machine.ChargePointVendor; _machine.FW_CurrentVersion = machine.FW_CurrentVersion; _machine.Iccid = machine.Iccid; _machine.Imsi = machine.Imsi; _machine.MeterSerialNumber = machine.MeterSerialNumber; _machine.MeterType = machine.MeterType; await db.SaveChangesAsync(); //using var semaphoreWrapper = await startupSemaphore.GetToken(); } private async Task BundelUpdateMachineBasicInfo(BundleHandlerData bundleHandlerData) { using var db = await contextFactory.CreateDbContextAsync(); using var trans = await db.Database.BeginTransactionAsync(); var pams = bundleHandlerData.Datas.DistinctBy(x => x.ChargeBoxId); foreach (var pam in pams) { var _machine = db.Machine.FirstOrDefault(x => x.ChargeBoxId == pam.ChargeBoxId); _machine.ChargeBoxSerialNumber = pam.machine.ChargeBoxSerialNumber; _machine.ChargePointSerialNumber = pam.machine.ChargePointSerialNumber; _machine.ChargePointModel = pam.machine.ChargePointModel; _machine.ChargePointVendor = pam.machine.ChargePointVendor; _machine.FW_CurrentVersion = pam.machine.FW_CurrentVersion; _machine.Iccid = pam.machine.Iccid; _machine.Imsi = pam.machine.Imsi; _machine.MeterSerialNumber = pam.machine.MeterSerialNumber; _machine.MeterType = pam.machine.MeterType; } await db.SaveChangesAsync(); await trans.CommitAsync(); bundleHandlerData.CompletedDatas.AddRange(bundleHandlerData.Datas); } private async Task UpdateConnectorStatusEF(string Id, ConnectorStatus Status) { using var db = await contextFactory.CreateDbContextAsync(); ConnectorStatus status = new() { Id = Id }; db.ChangeTracker.AutoDetectChangesEnabled = false; db.ConnectorStatus.Attach(status); status.CreatedOn = Status.CreatedOn; status.Status = Status.Status; status.ChargePointErrorCodeId = Status.ChargePointErrorCodeId; status.ErrorInfo = Status.ErrorInfo; status.VendorId = Status.VendorId; status.VendorErrorCode = Status.VendorErrorCode; db.Entry(status).Property(x => x.CreatedOn).IsModified = true; db.Entry(status).Property(x => x.Status).IsModified = true; db.Entry(status).Property(x => x.ChargePointErrorCodeId).IsModified = true; db.Entry(status).Property(x => x.ErrorInfo).IsModified = true; db.Entry(status).Property(x => x.VendorId).IsModified = true; db.Entry(status).Property(x => x.VendorErrorCode).IsModified = true; await db.SaveChangesAsync(); } private async Task UpdateConnectorStatusDapper(string Id, ConnectorStatus Status) { var parameters = new DynamicParameters(); parameters.Add("@Id", Id, DbType.String, ParameterDirection.Input, 36); parameters.Add("@CreatedOn", Status.CreatedOn, DbType.DateTime, ParameterDirection.Input); parameters.Add("@Status", Status.Status, DbType.Int32, ParameterDirection.Input); parameters.Add("@ChargePointErrorCodeId", Status.ChargePointErrorCodeId, DbType.Int32, ParameterDirection.Input); parameters.Add("@ErrorInfo", Status.ErrorInfo, DbType.String, ParameterDirection.Input, 50); parameters.Add("@VendorId", Status.VendorId, DbType.String, ParameterDirection.Input, 255); parameters.Add("@VendorErrorCode", Status.VendorErrorCode, DbType.String, ParameterDirection.Input, 100); using var conn = await sqlConnectionFactory.CreateAsync(); await conn.ExecuteAsync(""" update ConnectorStatus set CreatedOn = @CreatedOn, Status = @Status, ChargePointErrorCodeId = @ChargePointErrorCodeId, ErrorInfo = @ErrorInfo, VendorId = @VendorId, VendorErrorCode = @VendorErrorCode where Id = @Id """, parameters); } private async Task GetCustomerIdByChargeBoxIdEF(string chargeboxId) { using var db = await contextFactory.CreateDbContextAsync(); var _CustomerId = await db.Machine.Where(x => x.ChargeBoxId == chargeboxId).Select(x => x.CustomerId).FirstOrDefaultAsync(); return _CustomerId; } private async Task GetCustomerIdByChargeBoxIdDapper(string chargeboxId) { var parameters = new DynamicParameters(); parameters.Add("@ChargeBoxId", chargeboxId, DbType.String, ParameterDirection.Input, 50); using var conn = await sqlConnectionFactory.CreateAsync(); var _existedTx = await conn.QueryFirstOrDefaultAsync(""" select CustomerId from dbo.Machine where ChargeBoxId = @ChargeBoxId """, parameters); return _existedTx; } private async Task TryGetDuplicatedTransactionIdEF(string chargeBoxId, Guid customerId, int connectorId, DateTime timestamp) { using var db = await contextFactory.CreateDbContextAsync(); var _existedTx = await db.TransactionRecord.Where(x => x.CustomerId == customerId && x.ChargeBoxId == chargeBoxId && x.ConnectorId == connectorId && x.StartTime == timestamp).Select(x => x.Id).FirstOrDefaultAsync(); return _existedTx; } private async Task TryGetDuplicatedTransactionIdDapper(string chargeBoxId, Guid customerId, int connectorId, DateTime timestamp) { var parameters = new DynamicParameters(); parameters.Add("@ChargeBoxId", chargeBoxId, DbType.String, ParameterDirection.Input, 50); parameters.Add("@CustomerId", customerId, DbType.Guid, ParameterDirection.Input); parameters.Add("@ConnectorId", connectorId, DbType.Int16, ParameterDirection.Input); parameters.Add("@TimeStamp", timestamp, DbType.DateTime, ParameterDirection.Input); using var conn = await sqlConnectionFactory.CreateAsync(); var _existedTx = await conn.QueryFirstOrDefaultAsync(""" SELECT Id FROM dbo.TransactionRecord WHERE ChargeBoxId = @ChargeBoxId and CustomerId = @CustomerId and ConnectorId = @ConnectorId and StartTime = @TimeStamp """, parameters); return _existedTx; } private async ValueTask AddMachineErrorEF(byte connectorId, DateTime createdOn, int status, string chargeBoxId, int errorCodeId, string errorInfo, int preStatus, string vendorErrorCode, string vendorId) { using var db = await contextFactory.CreateDbContextAsync(); await db.MachineError.AddAsync(new MachineError() { ConnectorId = connectorId, CreatedOn = createdOn, Status = status, ChargeBoxId = chargeBoxId, ErrorCodeId = errorCodeId, ErrorInfo = errorInfo, PreStatus = preStatus, VendorErrorCode = vendorErrorCode, VendorId = vendorId }); await db.SaveChangesAsync(); } private async ValueTask AddMachineErrorDapper(byte connectorId, DateTime createdOn, int status, string chargeBoxId, int errorCodeId, string errorInfo, int preStatus, string vendorErrorCode, string vendorId) { var parameters = new DynamicParameters(); parameters.Add("@ConnectorId", connectorId, DbType.Int16, ParameterDirection.Input); parameters.Add("@PreStatus", preStatus, DbType.Int32, ParameterDirection.Input); parameters.Add("@Status", status, DbType.Int32, ParameterDirection.Input); parameters.Add("@ErrorInfo", errorInfo, DbType.String, ParameterDirection.Input, 50); parameters.Add("@VendorId", vendorId, DbType.String, ParameterDirection.Input, 255); parameters.Add("@CreatedOn", createdOn, DbType.DateTime, ParameterDirection.Input); parameters.Add("@ErrorCodeId", errorCodeId, DbType.Int32, ParameterDirection.Input); parameters.Add("@VendorErrorCode", vendorErrorCode, DbType.String, ParameterDirection.Input, 100); parameters.Add("@ChargeBoxId", chargeBoxId, DbType.String, ParameterDirection.Input, 50); using var conn = await sqlConnectionFactory.CreateAsync(); await conn.ExecuteAsync(""" INSERT INTO MachineError (ConnectorId, PreStatus, Status, ErrorInfo, VendorId, CreatedOn, ErrorCodeId, VendorErrorCode, ChargeBoxId) VALUES (@ConnectorId, @PreStatus, @Status, @ErrorInfo, @VendorId, @CreatedOn, @ErrorCodeId, @VendorErrorCode, @ChargeBoxId) """, parameters); } private async Task BundleUpdateConnectorStatus(IEnumerable statusNotifications) { using var db = await contextFactory.CreateDbContextAsync(); using var trans = await db.Database.BeginTransactionAsync(); statusNotifications = statusNotifications.OrderBy(x => x.Status.CreatedOn).DistinctBy(x => x.Id); foreach (var param in statusNotifications) { ConnectorStatus status = new() { Id = param.Id }; //db.ChangeTracker.AutoDetectChangesEnabled = false; db.ConnectorStatus.Attach(status); status.CreatedOn = param.Status.CreatedOn; status.Status = param.Status.Status; status.ChargePointErrorCodeId = param.Status.ChargePointErrorCodeId; status.ErrorInfo = param.Status.ErrorInfo; status.VendorId = param.Status.VendorId; status.VendorErrorCode = param.Status.VendorErrorCode; db.Entry(status).Property(x => x.CreatedOn).IsModified = true; db.Entry(status).Property(x => x.Status).IsModified = true; db.Entry(status).Property(x => x.ChargePointErrorCodeId).IsModified = true; db.Entry(status).Property(x => x.ErrorInfo).IsModified = true; db.Entry(status).Property(x => x.VendorId).IsModified = true; db.Entry(status).Property(x => x.VendorErrorCode).IsModified = true; //await db.SaveChangesAsync(); } await db.SaveChangesAsync(); await trans.CommitAsync(); //db.ChangeTracker.Clear(); } private async Task BundleUpdateConnectorStatusDapper(BundleHandlerData bundleHandlerData) { using var conn = sqlConnectionFactory.Create(); foreach (var status in bundleHandlerData.Datas) { var parameters = new DynamicParameters(); parameters.Add("@Id", status.Id, DbType.String, ParameterDirection.Input, 36); parameters.Add("@CreatedOn", status.Status.CreatedOn, DbType.DateTime, ParameterDirection.Input); parameters.Add("@Status", status.Status.Status, DbType.Int32, ParameterDirection.Input); parameters.Add("@ChargePointErrorCodeId", status.Status.ChargePointErrorCodeId, DbType.Int32, ParameterDirection.Input); parameters.Add("@ErrorInfo", status.Status.ErrorInfo, DbType.String, ParameterDirection.Input, 50); parameters.Add("@VendorId", status.Status.VendorId, DbType.String, ParameterDirection.Input, 255); parameters.Add("@VendorErrorCode", status.Status.VendorErrorCode, DbType.String, ParameterDirection.Input, 100); await conn.ExecuteAsync(""" update ConnectorStatus set CreatedOn = @CreatedOn, Status = @Status, ChargePointErrorCodeId = @ChargePointErrorCodeId, ErrorInfo = @ErrorInfo, VendorId = @VendorId, VendorErrorCode = @VendorErrorCode where Id = @Id """, parameters); bundleHandlerData.AddCompletedData(status); } } private async Task BundleAddServerMessage(BundleHandlerData bundleHandlerData) { using var db = await contextFactory.CreateDbContextAsync(); using var trans = await db.Database.BeginTransactionAsync(); foreach (var message in bundleHandlerData.Datas) { await db.ServerMessage.AddAsync(message); } await db.SaveChangesAsync(); await trans.CommitAsync(); bundleHandlerData.CompletedDatas.AddRange(bundleHandlerData.Datas); } private async Task AddServerMessageEF(ServerMessage message) { using var db = await contextFactory.CreateDbContextAsync(); using var trans = await db.Database.BeginTransactionAsync(); await db.ServerMessage.AddAsync(message); await db.SaveChangesAsync(); await trans.CommitAsync(); //db.ChangeTracker.Clear(); } private async Task AddServerMessageDapper(ServerMessage message) { var parameters = new DynamicParameters(); parameters.Add("@SerialNo", message.SerialNo, DbType.String, ParameterDirection.Input, 36); parameters.Add("@OutAction", message.OutAction, DbType.String, ParameterDirection.Input, 30); parameters.Add("@OutRequest", message.OutRequest, DbType.String, ParameterDirection.Input); parameters.Add("@InMessage", message.InMessage, DbType.String, ParameterDirection.Input); parameters.Add("@CreatedOn", message.CreatedOn, DbType.DateTime, ParameterDirection.Input); parameters.Add("@CreatedBy", message.CreatedBy, DbType.String, ParameterDirection.Input, 36); parameters.Add("@ReceivedOn", message.ReceivedOn, DbType.DateTime, ParameterDirection.Input); parameters.Add("@ChargeBoxId", message.ChargeBoxId, DbType.String, ParameterDirection.Input, 30); parameters.Add("@UpdatedOn", message.UpdatedOn, DbType.DateTime, ParameterDirection.Input); using var conn = await sqlConnectionFactory.CreateAsync(); var resultCnt = await conn.ExecuteAsync(""" INSERT INTO ServerMessage (SerialNo, OutAction, OutRequest, InMessage, CreatedOn, CreatedBy, ReceivedOn, ChargeBoxId, UpdatedOn) VALUES (@SerialNo, @OutAction, @OutRequest, @InMessage, @CreatedOn, @CreatedBy, @ReceivedOn, @ChargeBoxId, @UpdatedOn) """, parameters); if (resultCnt != 1) { throw new Exception("Insert failed"); } return; } private async Task AddNewTransactionRecordEF(TransactionRecord newTransaction) { using var db = await contextFactory.CreateDbContextAsync(); await db.TransactionRecord.AddAsync(newTransaction); await db.SaveChangesAsync(); return newTransaction.Id; } private async Task AddNewTransactionRecordDapper(TransactionRecord newTransaction) { var parameters = new DynamicParameters(); parameters.Add("@ChargeBoxId", newTransaction.ChargeBoxId, DbType.String, ParameterDirection.Input, 50); parameters.Add("@ConnectorId", newTransaction.ConnectorId, DbType.Int16, ParameterDirection.Input); parameters.Add("@CreatedOn", newTransaction.CreatedOn, DbType.DateTime, ParameterDirection.Input); parameters.Add("@UpdatedOn", newTransaction.UpdatedOn, DbType.DateTime, ParameterDirection.Input); parameters.Add("@StartTransactionReportedOn", newTransaction.StartTransactionReportedOn, DbType.DateTime, ParameterDirection.Input); parameters.Add("@StopTransactionReportedOn", newTransaction.StopTransactionReportedOn, DbType.DateTime, ParameterDirection.Input); parameters.Add("@StartIdTag", newTransaction.StartIdTag, DbType.String, ParameterDirection.Input, 20); parameters.Add("@MeterStart", newTransaction.MeterStart, DbType.Decimal, ParameterDirection.Input, precision: 18, scale: 2); parameters.Add("@MeterStop", newTransaction.MeterStop, DbType.Decimal, ParameterDirection.Input, precision: 18, scale: 2); parameters.Add("@CustomerId", newTransaction.CustomerId, DbType.Guid, ParameterDirection.Input); parameters.Add("@StartTime", newTransaction.StartTime, DbType.DateTime, ParameterDirection.Input); parameters.Add("@StopTime", newTransaction.StopTime, DbType.DateTime, ParameterDirection.Input); parameters.Add("@ReservationId", newTransaction.ReservationId, DbType.Int32, ParameterDirection.Input); parameters.Add("@RetryStartTransactionTimes", newTransaction.RetryStartTransactionTimes, DbType.Int32, ParameterDirection.Input); parameters.Add("@RetryStopTransactionTimes", newTransaction.RetryStopTransactionTimes, DbType.Int32, ParameterDirection.Input); parameters.Add("@Fee", newTransaction.Fee, DbType.String, ParameterDirection.Input, 1500); using var conn = await sqlConnectionFactory.CreateAsync(); var id = await conn.QuerySingleAsync(""" INSERT INTO TransactionRecord (ChargeBoxId, ConnectorId, CreatedOn, UpdatedOn, StartTransactionReportedOn, StopTransactionReportedOn, StartIdTag, MeterStart, MeterStop, CustomerId, StartTime, StopTime, ReservationId, RetryStartTransactionTimes, RetryStopTransactionTimes, Fee) OUTPUT INSERTED.Id VALUES (@ChargeBoxId, @ConnectorId, @CreatedOn, @UpdatedOn, @StartTransactionReportedOn, @StopTransactionReportedOn, @StartIdTag, @MeterStart, @MeterStop, @CustomerId, @StartTime, @StopTime, @ReservationId, @RetryStartTransactionTimes, @RetryStopTransactionTimes, @Fee) """, parameters); return id; } private async Task GetTransactionForStopTransactionEF(int transactionId, string chargeBoxId) { using var db = await contextFactory.CreateDbContextAsync(); return await db.TransactionRecord.Where(x => x.Id == transactionId && x.ChargeBoxId == chargeBoxId).FirstOrDefaultAsync(); } private async Task GetTransactionForStopTransactionDapper(int transactionId, string chargeBoxId) { var parameters = new DynamicParameters(); parameters.Add("@TransactionId", transactionId, DbType.Int32, ParameterDirection.Input); parameters.Add("@ChargeBoxId", chargeBoxId, DbType.String, ParameterDirection.Input, 50); using var conn = await sqlConnectionFactory.CreateAsync(); var record = await conn.QuerySingleAsync(""" SELECT Id, ConnectorId, MeterStop, MeterStart, StartTime, StopTime FROM TransactionRecord WHERE Id = @TransactionId and ChargeBoxId = @ChargeBoxId """, parameters); return record; } private Task BulkInsertServerMessage(IEnumerable messages) { var table = new DataTable(); table.Columns.Add("ChargeBoxId"); table.Columns.Add("SerialNo"); table.Columns.Add("OutAction"); table.Columns.Add("OutRequest"); table.Columns.Add("InMessage"); table.Columns.Add("CreatedOn"); table.Columns.Add("CreatedBy"); table.Columns.Add("UpdatedOn"); table.Columns.Add("ReceivedOn"); foreach (var param in messages) { var row = table.NewRow(); row["ChargeBoxId"] = param.ChargeBoxId; row["SerialNo"] = param.SerialNo; row["OutAction"] = param.OutAction; row["OutRequest"] = param.OutRequest; row["InMessage"] = param.InMessage; row["CreatedOn"] = param.CreatedOn; row["CreatedBy"] = param.CreatedBy; row["UpdatedOn"] = param.UpdatedOn; row["ReceivedOn"] = param.ReceivedOn; table.Rows.Add(row); } using SqlConnection sqlConnection = sqlConnectionFactory.Create(); using SqlBulkCopy sqlBulkCopy = new SqlBulkCopy(sqlConnection); sqlBulkCopy.BatchSize = messages.Count(); sqlBulkCopy.DestinationTableName = "ServerMessage"; sqlBulkCopy.ColumnMappings.Add("ChargeBoxId", "ChargeBoxId"); sqlBulkCopy.ColumnMappings.Add("SerialNo", "SerialNo"); sqlBulkCopy.ColumnMappings.Add("OutAction", "OutAction"); sqlBulkCopy.ColumnMappings.Add("OutRequest", "OutRequest"); sqlBulkCopy.ColumnMappings.Add("InMessage", "InMessage"); sqlBulkCopy.ColumnMappings.Add("CreatedOn", "CreatedOn"); sqlBulkCopy.ColumnMappings.Add("CreatedBy", "CreatedBy"); sqlBulkCopy.ColumnMappings.Add("UpdatedOn", "UpdatedOn"); sqlBulkCopy.ColumnMappings.Add("ReceivedOn", "ReceivedOn"); return sqlBulkCopy.WriteToServerAsync(table); } private int GetStartupLimit(IConfiguration configuration) { var limitConfig = configuration["MainDbStartupLimit"]; int limit = 5; if (limitConfig != default) { int.TryParse(limitConfig, out limit); } return limit; } private int GetOpLimit(IConfiguration configuration) { var limitConfig = configuration["MainDbOpLimit"]; int limit = 500; if (limitConfig != default) { int.TryParse(limitConfig, out limit); } return limit; } private async Task UpdateHeartBeatsDapper(IEnumerable heartBeatsData) { using var conn = await sqlConnectionFactory.CreateAsync(); using var trans = await conn.BeginTransactionAsync(); try { foreach (var data in heartBeatsData) { var parameters = new DynamicParameters(); parameters.Add("@Id", data.Id, DbType.String, ParameterDirection.Input, 36); parameters.Add("@HeartbeatUpdatedOn", data.HeartbeatUpdatedOn, DbType.DateTime, ParameterDirection.Input); parameters.Add("@ConnectionType", data.ConnectionType, DbType.Int32, ParameterDirection.Input); var resultCnt = await conn.ExecuteAsync(""" UPDATE Machine SET HeartbeatUpdatedOn = @HeartbeatUpdatedOn, ConnectionType = @ConnectionType WHERE Id = @Id """, parameters, trans); if (resultCnt != 1) { throw new Exception("Update over one columes"); } } await trans.CommitAsync(); } catch { logger.LogCritical("HeartBeatCheckTrigger update fail, roll back"); await trans.RollbackAsync(); return false; } return true; } private async Task UpdateHeartBeatsDapper(List machineIds) { using var conn = await sqlConnectionFactory.CreateAsync(); try { var parameters = new DynamicParameters(); parameters.Add("@Ids", machineIds, size: 36); parameters.Add("@HeartbeatUpdatedOn", DateTime.UtcNow, DbType.DateTime, ParameterDirection.Input); var resultCnt = await conn.ExecuteAsync(""" UPDATE Machine SET HeartbeatUpdatedOn = @HeartbeatUpdatedOn WHERE Id in @Ids """, parameters); } catch (Exception e) { logger.LogError(e.Message); logger.LogCritical("HeartBeatCheckTrigger update fail"); return false; } return true; } private async Task UpdateHeartBeatsEF(IEnumerable heartBeatsData) { using var db = await contextFactory.CreateDbContextAsync(); using var transaction = await db.Database.BeginTransactionAsync(); try { foreach (var data in heartBeatsData) { var machine = new Machine() { Id = data.Id }; if (machine != null) { db.Machine.Attach(machine); machine.HeartbeatUpdatedOn = DateTime.UtcNow; machine.ConnectionType = data.ConnectionType; db.Entry(machine).Property(x => x.HeartbeatUpdatedOn).IsModified = true; db.Entry(machine).Property(x => x.ConnectionType).IsModified = true; } } await db.SaveChangesAsync(); await transaction.CommitAsync(); db.ChangeTracker.Clear(); } catch (Exception ex) { logger.LogCritical(ex, "HeartBeatCheckTrigger update fail, roll back"); transaction.Rollback(); return false; } return true; } } public record MachineAndCustomerInfo (string MachineId, Guid CustomerId, string CustomerName); public record StatusNotificationParam(string Id, ConnectorStatus Status); public record UpdateMachineBasicInfoParam(string ChargeBoxId, Machine machine);