Robert před 1 rokem
rodič
revize
0e4cb67cd0

+ 8 - 4
EVCB_OCPP.WSServer/Helper/AddPortalDbContext.cs

@@ -19,7 +19,7 @@ public static class AddPortalDbContext
         const string DbPassKey = "MainDbPass";
         const string DbConnectionStringKey = "MainDBContext";
 
-        AddPortalDbContextInternal<MainDBContext>(services,configuration, DbUserIdKey, DbPassKey, DbConnectionStringKey);
+        AddPortalDbContextInternal<MainDBContext>(services,configuration, DbUserIdKey, DbPassKey, DbConnectionStringKey, logToConsole: true);
         return services;
     }
 
@@ -29,7 +29,7 @@ public static class AddPortalDbContext
         const string DbPassKey = "MeterValueDbPass";
         const string DbConnectionStringKey = "MeterValueDBContext";
 
-        AddPortalDbContextInternal<MeterValueDBContext>(services, configuration, DbUserIdKey, DbPassKey, DbConnectionStringKey);
+        AddPortalDbContextInternal<MeterValueDBContext>(services, configuration, DbUserIdKey, DbPassKey, DbConnectionStringKey, logToConsole: true);
         return services;
     }
 
@@ -45,20 +45,24 @@ public static class AddPortalDbContext
 
     private static void AddPortalDbContextInternal<T>(
         IServiceCollection services, IConfiguration configuration,
-        string UserIdKey,string DbPassKey, string ConnectionStringKey) where T : DbContext
+        string UserIdKey,string DbPassKey, string ConnectionStringKey,bool logToConsole = false) where T : DbContext
     {
 
         var commandTimeout = int.TryParse(configuration[CommandTimeoutKey], out var temp) ? temp : 180;
         string mainDbUserId = string.IsNullOrEmpty(configuration[UserIdKey]) ? string.Empty : $"user id={configuration[UserIdKey]};";
         string mainDbUserPass = string.IsNullOrEmpty(configuration[DbPassKey]) ? string.Empty : $"password={configuration[DbPassKey]};";
 
-        services.AddPooledDbContextFactory<T>((options) => {
+        services.AddPooledDbContextFactory<T>((serviceProvider, options) => {
             var cString = configuration.GetConnectionString(ConnectionStringKey);
             cString = $"{cString}{mainDbUserId}{mainDbUserPass}";
             options.UseSqlServer(cString, dbOptions =>
             {
                 dbOptions.CommandTimeout(commandTimeout);
             });
+            if(logToConsole)
+            {
+                options.LogTo(Console.WriteLine);
+            }
         });
     }
 }

+ 100 - 59
EVCB_OCPP.WSServer/Helper/GroupSingleHandler.cs

@@ -10,94 +10,135 @@ namespace EVCB_OCPP.WSServer.Helper;
 
 public class GroupSingleHandler<T>
 {
-    public GroupSingleHandler(Func<IEnumerable<T>, Task> handleFunc, ILogger logger)
+    public GroupSingleHandler(Func<IEnumerable<T>, Task> handleFunc, ILogger logger, int workerCnt = 1)
     {
         this.handleFunc = handleFunc;
         this.logger = logger;
 
-        singleWorkLock = new (_WorkerCnt);
-    }
+        //singleWorkLock = new (_WorkerCnt);
+        singleHandleTask = StartHandleTask();
 
-    private int _WorkerCnt = 1;
-    public int WorkerCnt
-    {
-        get => _WorkerCnt;
-        set
+        _handleTasks = new Task[workerCnt];
+        for (int cnt = 0; cnt < workerCnt; cnt++)
         {
-            if (IsStarted)
-            {
-                throw new Exception($"{nameof(WorkerCnt)} must not be changed afted {nameof(HandleAsync)} is called");
-            }
-
-            _WorkerCnt = value;
-            singleWorkLock = new (_WorkerCnt);
+            _handleTasks[cnt] = StartHandleTask();
         }
     }
 
+    //private int _WorkerCnt = 1;
+    //public int WorkerCnt
+    //{
+    //    get => _WorkerCnt;
+    //    set
+    //    {
+    //        if (IsStarted)
+    //        {
+    //            throw new Exception($"{nameof(WorkerCnt)} must not be changed afted {nameof(HandleAsync)} is called");
+    //        }
+
+    //        _WorkerCnt = value;
+    //        singleWorkLock = new (_WorkerCnt);
+    //    }
+    //}
+
     private readonly Func<IEnumerable<T>, Task> handleFunc;
     private readonly ILogger logger;
-    private readonly ConcurrentQueue<(T param, SemaphoreSlim waitLock)> waitList = new();
-    private SemaphoreSlim singleWorkLock;// = new SemaphoreSlim(1);
+    private readonly BlockingCollection<(T param, SemaphoreSlim waitLock)> blockQueue = new();
+    //private SemaphoreSlim singleWorkLock;// = new SemaphoreSlim(1);
     private bool IsStarted = false;
     private Task singleHandleTask;
+    private Task[] _handleTasks;
 
     public Task HandleAsync(T param)
     {
         IsStarted = true;
 
         SemaphoreSlim reqLock = new(0);
-        waitList.Enqueue((param, reqLock));
-        TryStartHandler();
+        blockQueue.Add((param, reqLock));
+        //TryStartHandler();
         return reqLock.WaitAsync();
     }
 
-    private void TryStartHandler()
-    {
-        if (!singleWorkLock.Wait(0))
-        {
-            return;
-        }
+    //private void TryStartHandler()
+    //{
+    //    if (!singleWorkLock.Wait(0))
+    //    {
+    //        return;
+    //    }
 
-        if (waitList.Count == 0)
-        {
-            singleWorkLock.Release();
-            return;
-        }
+    //    if (waitList.Count == 0)
+    //    {
+    //        singleWorkLock.Release();
+    //        return;
+    //    }
 
-        singleHandleTask = StartHandleTask();
-    }
+    //    singleHandleTask = StartHandleTask();
+    //}
 
-    private async Task StartHandleTask()
+    private Task StartHandleTask()
     {
-        var handleList = new List<(T param, SemaphoreSlim waitLock)>();
-
-        while (waitList.TryDequeue(out var handle))
-        {
-            handleList.Add(handle);
-        }
-
-        int cnt = 0;
-        do
-        {
-            cnt++;
-            try
+        return Task.Run(async () => {
+            while (!blockQueue.IsCompleted)
             {
+                var handleList = new List<(T param, SemaphoreSlim waitLock)>();
+                try
+                {
+                    //若blockQueue沒有資料,Take動作會被Block住,有資料時再往下執行
+                    //若
+                    var  startData = blockQueue.Take();
+                    handleList.Add(startData);
+                }
+                catch (InvalidOperationException e) {
+                    logger.LogError(e, "blockQueue.Take Error");
+                    break;
+                }
+                while(blockQueue.TryTake(out var data))
+                {
+                    handleList.Add(data);
+                }
+
                 var task = handleFunc(handleList.Select(x => x.param));
                 await task;
-                break;
-            }
-            catch (Exception e)
-            {
-                logger.LogError(e, "Trying Cnt {0}", cnt);
-            }
-        }
-        while (true);
 
-        foreach (var handled in handleList)
-        {
-            handled.waitLock.Release();
-        }
-        singleWorkLock.Release();
-        TryStartHandler();
+                foreach (var handled in handleList)
+                {
+                    handled.waitLock.Release();
+                }
+            }
+        });
     }
+
+    //private async Task StartHandleTask()
+    //{
+    //    var handleList = new List<(T param, SemaphoreSlim waitLock)>();
+
+    //    while (waitList.TryDequeue(out var handle))
+    //    {
+    //        handleList.Add(handle);
+    //    }
+
+    //    int cnt = 0;
+    //    do
+    //    {
+    //        cnt++;
+    //        try
+    //        {
+    //            var task = handleFunc(handleList.Select(x => x.param));
+    //            await task;
+    //            break;
+    //        }
+    //        catch (Exception e)
+    //        {
+    //            logger.LogError(e, "Trying Cnt {0}", cnt);
+    //        }
+    //    }
+    //    while (true);
+
+    //    foreach (var handled in handleList)
+    //    {
+    //        handled.waitLock.Release();
+    //    }
+    //    singleWorkLock.Release();
+    //    TryStartHandler();
+    //}
 }

+ 21 - 4
EVCB_OCPP.WSServer/Message/CoreProfileHandler.cs

@@ -364,12 +364,13 @@ internal partial class ProfileHandler
                     break;
                 case Actions.MeterValues:
                     {
-
+                        var meterValueTimer = Stopwatch.StartNew();
+                        long s1 = 0, s2 = 0, s3 = 0, s4 = 0, s5 = 0, insertTasksCnt = 0;
                         MeterValuesRequest _request = request as MeterValuesRequest;
 
                         if (_request.meterValue.Count > 0)
                         {
-
+                            s1 = meterValueTimer.ElapsedMilliseconds;
                             foreach (var item in _request.meterValue)
                             {
                                 if (_request.transactionId.HasValue)
@@ -406,6 +407,7 @@ internal partial class ProfileHandler
 
                             }
 
+                            s2 = meterValueTimer.ElapsedMilliseconds;
                             List<Task> insertTasks = new();
                             foreach (var item in _request.meterValue)
                             {
@@ -424,10 +426,15 @@ internal partial class ProfileHandler
                                         , locationId: sampleVaule.location.HasValue ? (int)sampleVaule.location : 0
                                         , unitId: sampleVaule.unit.HasValue ? (int)sampleVaule.unit : 0
                                         , transactionId: _request.transactionId.HasValue ? _request.transactionId.Value : -1);
+
+                                    //var task = Task.Delay(2_000); 
                                     insertTasks.Add(task);
                                 }
                             }
+                            insertTasksCnt = insertTasks.Count;
+                            s3 = meterValueTimer.ElapsedMilliseconds;
                             await Task.WhenAll(insertTasks);
+                            s4 = meterValueTimer.ElapsedMilliseconds;
                         }
 
                         //  if (energy_kwh > 0)
@@ -458,6 +465,14 @@ internal partial class ProfileHandler
 
                         }
 
+                        s5 = meterValueTimer.ElapsedMilliseconds;
+                        meterValueTimer.Stop();
+                        if (meterValueTimer.ElapsedMilliseconds / 1000 > 1)
+                        {
+
+                            logger.LogCritical(string.Format("MeterValues took {0}/{1}/{2}/{3}/{4}:{5}", s1 / 1000, s2 / 1000, s3 / 1000, s4 / 1000, s5 / 1000, insertTasksCnt));
+                        }
+
                         var confirm = new MeterValuesConfirmation() { };
                         result.Message = confirm;
                         result.Success = true;
@@ -585,7 +600,7 @@ internal partial class ProfileHandler
                     break;
                 case Actions.StopTransaction:
                     {
-                        long getDateTimeTime, getServiceTime, getTagInfoTime, dbOpTime = 0;
+                        long getDateTimeTime, getServiceTime, getTagInfoTime, dbOpTime = 0, meterValueTime = 0;
                         var stopTrasactionTimer = Stopwatch.StartNew();
 
                         StopTransactionRequest _request = request as StopTransactionRequest;
@@ -741,6 +756,8 @@ internal partial class ProfileHandler
                             }
 
                             #endregion
+
+                            meterValueTime = watch.ElapsedMilliseconds;
                         }
                         catch (Exception ex)
                         {
@@ -755,7 +772,7 @@ internal partial class ProfileHandler
                         if (stopTrasactionTimer.ElapsedMilliseconds > 1000)
                         {
                             logger.Log(LogLevel.Critical, "ExecuteCoreRequest {action} {sessisonId} took {time} sec", action.ToString(), session.SessionID, stopTrasactionTimer.ElapsedMilliseconds / 1000);
-                            logger.Log(LogLevel.Critical, "{action} {sessisonId} time {getDateTime}/{serviceTime}/{tagInfoTime}/{dbOpTime}", action.ToString(), session.SessionID, getDateTimeTime / 1000, getServiceTime / 1000, getTagInfoTime / 1000, dbOpTime / 1000);
+                            logger.Log(LogLevel.Critical, "{action} {sessisonId} time {getDateTime}/{serviceTime}/{tagInfoTime}/{dbOpTime}/{meterValueTime}", action.ToString(), session.SessionID, getDateTimeTime, getServiceTime, getTagInfoTime, dbOpTime, meterValueTime);
                         }
 
                     }

+ 3 - 0
EVCB_OCPP.WSServer/Program.cs

@@ -39,6 +39,9 @@ namespace EVCB_OCPP.WSServer
             Console.WriteLine("====================================================================================================");
             Console.WriteLine("====================================================================================================");
 
+            //ThreadPool.GetMaxThreads(out var workerThreads, out var completionThreads);
+            //ThreadPool.SetMinThreads((int)(workerThreads * 0.8), (int)(completionThreads * 0.8));
+
             IHost host = Host.CreateDefaultBuilder(args)
                 //.UseEnvironment("Development")
                 .ConfigureLogging((context, builder) => { 

+ 1 - 1
EVCB_OCPP.WSServer/ProtalServer.cs

@@ -847,7 +847,7 @@ namespace EVCB_OCPP.WSServer
         private async Task ProcessRequestMessage(MessageResult analysisResult, ClientData session, Actions action)
         {
             Stopwatch outter_stopwatch = Stopwatch.StartNew();
-            BasicMessageHandler msgAnalyser = new BasicMessageHandler();
+            //BasicMessageHandler msgAnalyser = new BasicMessageHandler();
             if (!session.IsCheckIn && action != Actions.BootNotification)
             {
                 string response = BasicMessageHandler.GenerateCallError(analysisResult.UUID, OCPPErrorCodes.GenericError, OCPPErrorDescription.NotChecked);

+ 10 - 3
EVCB_OCPP.WSServer/Service/BusinessServiceFactory.cs

@@ -1,4 +1,5 @@
 using EVCB_OCPP.Domain;
+using EVCB_OCPP.Domain.Models.Database;
 using EVCB_OCPP.Packet.Messages.SubTypes;
 using EVCB_OCPP.WSServer.Dto;
 using Microsoft.EntityFrameworkCore;
@@ -47,16 +48,22 @@ public class BusinessServiceFactory : IBusinessServiceFactory
     public async Task<IBusinessService> CreateBusinessService(string customerId)
     {
         bool isCallOut = false;
+        //using (var db = this.mainDBContextFactory.CreateDbContext())
+        //{
+        //    isCallOut = await db.Customer.Where(x => x.Id == new Guid(customerId)).Select(x => x.CallPartnerApiOnSchedule).FirstOrDefaultAsync();
+        //}
+        CustomerSignMaterial _customer;
         using (var db = this.mainDBContextFactory.CreateDbContext())
         {
-            isCallOut = await db.Customer.Where(x => x.Id == new Guid(customerId)).Select(x => x.CallPartnerApiOnSchedule).FirstOrDefaultAsync();
+            _customer = await db.Customer.Where(x => x.Id == new Guid(customerId)).Select(x => new CustomerSignMaterial() { Id = x.Id.ToString(), APIUrl = x.ApiUrl, SaltKey = x.ApiKey, CallsThirdParty = x.CallPartnerApiOnSchedule }).FirstOrDefaultAsync();
         }
-
+        isCallOut = _customer != null && _customer.CallsThirdParty;
         //return isCallOut ? new OuterBusinessService(customerId) : new LocalBusinessService(customerId);
         if (isCallOut)
         {
             OuterBusinessService outerBusinessService = serviceProvider.GetService<OuterBusinessService>();
-            outerBusinessService.CustomerId = customerId;
+            //outerBusinessService.CustomerId = customerId;
+            outerBusinessService.CustomerSignMaterial = _customer;
             return outerBusinessService;
         }
         LocalBusinessService toReturn = serviceProvider.GetService<LocalBusinessService>();

+ 24 - 31
EVCB_OCPP.WSServer/Service/MainDbService.cs

@@ -131,31 +131,31 @@ public class MainDbService : IMainDbService
 
     public async Task UpdateConnectorStatus(string Id, ConnectorStatus Status)
     {
-        using var db = await contextFactory.CreateDbContextAsync();
+        //using var db = await contextFactory.CreateDbContextAsync();
 
-        ConnectorStatus status = new() { Id = Id };
+        //ConnectorStatus status = new() { Id = Id };
 
-        db.ChangeTracker.AutoDetectChangesEnabled = false;
-        db.ConnectorStatus.Attach(status);
+        //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;
+        //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;
+        //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 statusNotificationHandler.HandleAsync(new StatusNotificationParam(Id, Status));
+        //await db.SaveChangesAsync();
+        await statusNotificationHandler.HandleAsync(new StatusNotificationParam(Id, Status));
         return;
     }
 
@@ -210,10 +210,8 @@ public class MainDbService : IMainDbService
 
         updateMachineBasicInfoHandler = new GroupSingleHandler<UpdateMachineBasicInfoParam>(
             handleFunc: BundelUpdateMachineBasicInfo,
-            logger: loggerFactory.CreateLogger("UpdateMachineBasicInfoHandler"))
-            {
-                WorkerCnt = 10
-            };
+            logger: loggerFactory.CreateLogger("UpdateMachineBasicInfoHandler"),
+            workerCnt: 10);
     }
 
     private async Task BundelUpdateMachineBasicInfo(IEnumerable<UpdateMachineBasicInfoParam> pams)
@@ -250,10 +248,8 @@ public class MainDbService : IMainDbService
 
         statusNotificationHandler = new GroupSingleHandler<StatusNotificationParam>(
             handleFunc: BundleUpdateConnectorStatus,
-            logger: loggerFactory.CreateLogger("StatusNotificationHandler"))
-        {
-            WorkerCnt = 10
-        };
+            logger: loggerFactory.CreateLogger("StatusNotificationHandler"),
+            workerCnt: 10);
     }
 
     private async Task BundleUpdateConnectorStatus(IEnumerable<StatusNotificationParam> statusNotifications)
@@ -261,7 +257,7 @@ public class MainDbService : IMainDbService
         using var db = await contextFactory.CreateDbContextAsync();
         using var trans = await db.Database.BeginTransactionAsync();
 
-        statusNotifications = statusNotifications.DistinctBy(x => x.Id);
+        statusNotifications = statusNotifications.OrderBy(x => x.Status.CreatedOn).DistinctBy(x => x.Id);
 
         foreach (var param in statusNotifications)
         {
@@ -303,10 +299,7 @@ public class MainDbService : IMainDbService
 
         addServerMessageHandler = new GroupSingleHandler<ServerMessage>(
             handleFunc: BundleAddServerMessage,
-            logger: loggerFactory.CreateLogger("AddServerMessageHandler"))
-        {
-            WorkerCnt = 1
-        };
+            logger: loggerFactory.CreateLogger("AddServerMessageHandler"));
     }
 
     private async Task BundleAddServerMessage(IEnumerable<ServerMessage> messages)

+ 198 - 47
EVCB_OCPP.WSServer/Service/MeterValueDbService.cs

@@ -3,9 +3,14 @@ using EVCB_OCPP.Packet.Messages.SubTypes;
 using EVCB_OCPP.WSServer.Helper;
 using Microsoft.Data.SqlClient;
 using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.Logging;
 using System;
+using System.Collections.Concurrent;
 using System.Collections.Generic;
+using System.Configuration;
+using System.Data;
+using System.Diagnostics;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
@@ -16,25 +21,197 @@ public class MeterValueDbService
 {
     private readonly IDbContextFactory<MeterValueDBContext> meterValueDbContextFactory;
     private readonly ILoggerFactory loggerFactory;
-    //private GroupSingleHandler<InsertMeterValueParam> insertMeterValueHandler;
-
-    public MeterValueDbService(IDbContextFactory<MeterValueDBContext> meterValueDbContextFactory, ILoggerFactory loggerFactory)
+    private readonly QueueSemaphore insertSemaphore;
+    private readonly string meterValueConnectionString;
+    private readonly ILogger logger;
+    private GroupSingleHandler<InsertMeterValueParam> insertMeterValueHandler;
+    private Queue<string> _existTables = new();
+
+    public MeterValueDbService(
+        IDbContextFactory<MeterValueDBContext> meterValueDbContextFactory, 
+        ILogger<MeterValueDbService> logger,
+        ILoggerFactory loggerFactory,
+        IConfiguration configuration)
     {
         this.meterValueDbContextFactory = meterValueDbContextFactory;
         this.loggerFactory = loggerFactory;
-        //InitInsertMeterValueHandler();
+        this.meterValueConnectionString = configuration.GetConnectionString("MeterValueDBContext");
+        this.logger = logger;
+
+        InitInsertMeterValueHandler();
+
+        var insertLimit = GetInsertLimit(configuration);
+        insertSemaphore = new QueueSemaphore(insertLimit);
+
     }
 
-    public async Task InsertAsync(string chargeBoxId, byte connectorId, decimal value, DateTime createdOn
+    public Task InsertAsync(string chargeBoxId, byte connectorId, decimal value, DateTime createdOn
             , int contextId, int formatId, int measurandId, int phaseId
             , int locationId, int unitId, int transactionId)
     {
-        using var db = await meterValueDbContextFactory.CreateDbContextAsync();
+        //using var token = await insertSemaphore.GetToken();
+        //using var db = await meterValueDbContextFactory.CreateDbContextAsync();
 
-        string sp = "[dbo].[uspInsertMeterValueRecord] @ChargeBoxId, @ConnectorId,@Value,@CreatedOn,@ContextId,@FormatId,@MeasurandId,@PhaseId,@LocationId,@UnitId,@TransactionId";
+        //string sp = "[dbo].[uspInsertMeterValueRecord] @ChargeBoxId, @ConnectorId,@Value,@CreatedOn,@ContextId,@FormatId,@MeasurandId,@PhaseId,@LocationId,@UnitId,@TransactionId";
 
         var param = new InsertMeterValueParam(chargeBoxId, connectorId, value, createdOn, contextId, formatId, measurandId, phaseId, locationId, unitId, transactionId);
 
+        //List<SqlParameter> parameter = new List<SqlParameter>();
+        //parameter.AddInsertMeterValueRecordSqlParameters(
+        //    chargeBoxId: param.chargeBoxId
+        //    , connectorId: (byte)param.connectorId
+        //    , value: param.value
+        //    , createdOn: param.createdOn
+        //    , contextId: param.contextId
+        //    , formatId: param.formatId
+        //    , measurandId: param.measurandId
+        //    , phaseId: param.phaseId
+        //    , locationId: param.locationId
+        //    , unitId: param.unitId
+        //    , transactionId: param.transactionId);
+
+        //await db.Database.ExecuteSqlRawAsync(sp, parameter.ToArray());
+
+        return insertMeterValueHandler.HandleAsync(param);
+    }
+
+    private void InitInsertMeterValueHandler()
+    {
+        if (insertMeterValueHandler is not null)
+        {
+            throw new Exception($"{nameof(InitInsertMeterValueHandler)} should only called once");
+        }
+
+        insertMeterValueHandler = new GroupSingleHandler<InsertMeterValueParam>(
+            BulkInsertWithCache,
+            //loggerFactory.CreateLogger("InsertMeterValueHandler")
+            logger
+            );
+    }
+
+    private async Task BundleInsertWithStoredProcedure(IEnumerable<InsertMeterValueParam> parms)
+    {
+        foreach (var param in parms)
+        {
+            await InsertWithStoredProcedure(param);
+        }
+    }
+
+    private async Task BulkInsertWithCache(IEnumerable<InsertMeterValueParam> parms)
+    {
+        var watcher = Stopwatch.StartNew();
+        long t0 = 0, t1 = 0, t2 = 0, t3 = 0, t4 = 0;
+
+        var parmsList = parms.ToList();
+        foreach (var param in parms)
+        {
+            if (!await GetTableExist(param.createdOn))
+            {
+                await InsertWithStoredProcedure(param);
+                parmsList.Remove(param);
+            }
+        }
+
+        //logger.LogInformation("MeterValue bundle insert cnt {0}", parmsList.Count);
+        var gruopParams = parmsList.GroupBy(x => GetTableName(x.createdOn));
+
+        t0 = watcher.ElapsedMilliseconds;
+        foreach (var group in gruopParams)
+        {
+            var table = new DataTable();
+            table.Columns.Add("ChargeBoxId");
+            table.Columns.Add("ConnectorId");
+            table.Columns.Add("Value");
+            table.Columns.Add("CreatedOn");
+            table.Columns.Add("ContextId");
+            table.Columns.Add("FormatId");
+            table.Columns.Add("MeasurandId");
+            table.Columns.Add("PhaseId");
+            table.Columns.Add("LocationId");
+            table.Columns.Add("UnitId");
+            table.Columns.Add("TransactionId");
+
+            foreach (var param in group)
+            {
+                var row = table.NewRow();
+                row["ChargeBoxId"] = param.chargeBoxId;
+                row["ConnectorId"] = param.connectorId;
+                row["Value"] = param.value;
+                row["CreatedOn"] = param.createdOn;
+                row["ContextId"] = param.contextId;
+                row["FormatId"] = param.formatId;
+                row["MeasurandId"] = param.measurandId;
+                row["PhaseId"] = param.phaseId;
+                row["LocationId"] = param.locationId;
+                row["UnitId"] = param.unitId;
+                row["TransactionId"] = param.transactionId;
+
+                table.Rows.Add(row);
+            }
+            t1 = watcher.ElapsedMilliseconds;
+            using SqlConnection sqlConnection = new SqlConnection(meterValueConnectionString);
+            sqlConnection.Open();
+            using SqlBulkCopy sqlBulkCopy = new SqlBulkCopy(sqlConnection);
+            t2 = watcher.ElapsedMilliseconds;
+            sqlBulkCopy.BatchSize = group.Count();
+            sqlBulkCopy.DestinationTableName = group.Key;
+
+            sqlBulkCopy.ColumnMappings.Add("ChargeBoxId", "ChargeBoxId");
+            sqlBulkCopy.ColumnMappings.Add("ConnectorId", "ConnectorId");
+            sqlBulkCopy.ColumnMappings.Add("Value", "Value");
+            sqlBulkCopy.ColumnMappings.Add("CreatedOn", "CreatedOn");
+            sqlBulkCopy.ColumnMappings.Add("ContextId", "ContextId");
+            sqlBulkCopy.ColumnMappings.Add("FormatId", "FormatId");
+            sqlBulkCopy.ColumnMappings.Add("MeasurandId", "MeasurandId");
+            sqlBulkCopy.ColumnMappings.Add("PhaseId", "PhaseId");
+            sqlBulkCopy.ColumnMappings.Add("LocationId", "LocationId");
+            sqlBulkCopy.ColumnMappings.Add("UnitId", "UnitId");
+            sqlBulkCopy.ColumnMappings.Add("TransactionId", "TransactionId");
+            t3 = watcher.ElapsedMilliseconds;
+            sqlBulkCopy.WriteToServer(table);
+        }
+        watcher.Stop();
+        t4 = watcher.ElapsedMilliseconds;
+
+        if (t4 > 500)
+        {
+            logger.LogWarning("BulkInsertWithCache Slow {0}/{1}/{2}/{3}/{4}",t0,t1,t2,t3,t4);
+        }
+    }
+
+    private async ValueTask<bool> 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 meterValueDbContextFactory.CreateDbContextAsync();
+        var resultList = db.Database.SqlQuery<int>(checkTableSql)?.ToList();
+
+        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 async Task InsertWithStoredProcedure(InsertMeterValueParam param)
+    {
+        using var db = await meterValueDbContextFactory.CreateDbContextAsync();
+
+        string sp = "[dbo].[uspInsertMeterValueRecord] @ChargeBoxId," +
+"@ConnectorId,@Value,@CreatedOn,@ContextId,@FormatId,@MeasurandId,@PhaseId,@LocationId,@UnitId,@TransactionId";
+
         List<SqlParameter> parameter = new List<SqlParameter>();
         parameter.AddInsertMeterValueRecordSqlParameters(
             chargeBoxId: param.chargeBoxId
@@ -49,48 +226,22 @@ public class MeterValueDbService
             , unitId: param.unitId
             , transactionId: param.transactionId);
 
-        await db.Database.ExecuteSqlRawAsync(sp, parameter.ToArray());
-        //return insertMeterValueHandler.HandleAsync(new InsertMeterValueParam(chargeBoxId, connectorId, value, createdOn, contextId, formatId, measurandId, phaseId, locationId, unitId, transactionId));
+        db.Database.ExecuteSqlRaw(sp, parameter.ToArray());
     }
 
-//    private void InitInsertMeterValueHandler()
-//    {
-//        if (insertMeterValueHandler is not null)
-//        {
-//            throw new Exception($"{nameof(InitInsertMeterValueHandler)} should only called once");
-//        }
-
-//        insertMeterValueHandler = new GroupSingleHandler<InsertMeterValueParam>(async (parms) => {
-
-//            using var db = await meterValueDbContextFactory.CreateDbContextAsync();
-//            using var trans = await db.Database.BeginTransactionAsync();
-
-//            string sp = "[dbo].[uspInsertMeterValueRecord] @ChargeBoxId," +
-//"@ConnectorId,@Value,@CreatedOn,@ContextId,@FormatId,@MeasurandId,@PhaseId,@LocationId,@UnitId,@TransactionId";
-
-//            foreach (var param in parms)
-//            {
-//                List<SqlParameter> parameter = new List<SqlParameter>();
-//                parameter.AddInsertMeterValueRecordSqlParameters(
-//                    chargeBoxId: param.chargeBoxId
-//                    , connectorId: (byte)param.connectorId
-//                    , value: param.value
-//                    , createdOn: param.createdOn
-//                    , contextId: param.contextId
-//                    , formatId: param.formatId
-//                    , measurandId: param.measurandId
-//                    , phaseId: param.phaseId
-//                    , locationId: param.locationId
-//                    , unitId: param.unitId
-//                    , transactionId: param.transactionId);
-
-//                await db.Database.ExecuteSqlRawAsync(sp, parameter.ToArray());
-//            }
-
-//            await trans.CommitAsync();
-//        }
-//        , loggerFactory.CreateLogger("InsertMeterValueHandler"));
-//    }
+    private static string GetTableName(DateTime dateTime) 
+        => $"ConnectorMeterValueRecord{dateTime:yyMMdd}";
+
+    private int GetInsertLimit(IConfiguration configuration)
+    {
+        var limitConfig = configuration["MeterValueDbInsertLimit"];
+        int limit = 10;
+        if (limitConfig != default)
+        {
+            int.TryParse(limitConfig, out limit);
+        }
+        return limit;
+    }
 }
 
 public record InsertMeterValueParam(string chargeBoxId, byte connectorId, decimal value, DateTime createdOn

+ 10 - 0
EVCB_OCPP.WSServer/Service/OuterBusinessService.cs

@@ -65,6 +65,16 @@ namespace EVCB_OCPP.WSServer.Service
             }
         }
 
+        internal CustomerSignMaterial CustomerSignMaterial
+        {
+            get => signMaterial;
+            set
+            {
+                signMaterial = value;
+                _CustomerId = signMaterial.Id;
+            }
+        }
+
         public OuterBusinessService(IDbContextFactory<MainDBContext> maindbContextFactory)
         {
             this.maindbContextFactory = maindbContextFactory;