فهرست منبع

1. optimize CheckEVSEOnlineJob as parallel
2. limit DeleteServerMessage by ReceivedOn
3. optimize StartTransacionReportJob/StopTransacionReportJob by get once

Robert 1 سال پیش
والد
کامیت
290ded9058

+ 120 - 0
EVCB_OCPP.TaskScheduler/Helper/GroupSingleHandler.cs

@@ -0,0 +1,120 @@
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace EVCB_OCPP.TaskScheduler.Helper;
+
+public class GroupSingleHandler<T>
+{
+    public GroupSingleHandler(Func<IEnumerable<T>, Task> handleFunc, ILogger logger, int workerCnt = 1)
+    {
+        this.handleFunc = handleFunc;
+        this.logger = logger;
+
+        WorkerCnt = workerCnt;
+        //singleWorkLock = new(_WorkerCnt);
+    }
+
+    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 bool IsStarted = false;
+    private Task singleHandleTask;
+
+    public Task HandleAsync(T param)
+    {
+        IsStarted = true;
+
+        SemaphoreSlim reqLock = new(0);
+        waitList.Enqueue((param, reqLock));
+        TryStartHandler();
+        return reqLock.WaitAsync();
+    }
+
+    private void TryStartHandler()
+    {
+        if (!singleWorkLock.Wait(0))
+        {
+            return;
+        }
+
+        if (waitList.Count == 0)
+        {
+            singleWorkLock.Release();
+            return;
+        }
+
+        singleHandleTask = StartHandleTask();
+    }
+
+    private async Task StartHandleTask()
+    {
+        var timer = Stopwatch.StartNew();
+        long t0 = 0, t1 = 0, t2 = 0;
+
+        var handleList = new List<(T param, SemaphoreSlim waitLock)>();
+
+        while (waitList.TryDequeue(out var handle))
+        {
+            handleList.Add(handle);
+        }
+        t0 = timer.ElapsedMilliseconds;
+
+        int cnt = 0;
+        do
+        {
+            cnt++;
+            try
+            {
+                var task = handleFunc(handleList.Select(x => x.param));
+                await task;
+                t1 = timer.ElapsedMilliseconds;
+                break;
+            }
+            catch (Exception e)
+            {
+                logger.LogError(e, "Trying Cnt {0}", cnt);
+                logger.LogError(e.Message);
+            }
+        }
+        while (true);
+
+        foreach (var handled in handleList)
+        {
+            handled.waitLock.Release();
+        }
+        singleWorkLock.Release();
+
+        timer.Stop();
+        t2 = timer.ElapsedMilliseconds;
+        if (t2 > 1000)
+        {
+            logger.LogWarning("StartHandleTask {0}/{1}/{2}", t0, t1, t2);
+        }
+
+        TryStartHandler();
+    }
+}

+ 81 - 34
EVCB_OCPP.TaskScheduler/Jobs/CheckEVSEOnlineJob.cs

@@ -10,6 +10,7 @@ using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Configuration;
 using EVCB_OCPP.TaskScheduler.Helper;
 using EVCB_OCPP.Domain;
+using EVCB_OCPP.TaskScheduler.Services;
 
 namespace EVCB_OCPP.TaskScheduler.Jobs
 {
@@ -19,29 +20,32 @@ namespace EVCB_OCPP.TaskScheduler.Jobs
     [DisallowConcurrentExecution]
     public class CheckEVSEOnlineJob : IJob
     {
-        private readonly ILogger logger;
-        private readonly SqlConnectionFactory<MainDBContext> mainDbConnectionFactory;
-        private readonly SqlConnectionFactory<OnlineLogDBContext> onlineLogDbConnectionFactory;
-
         //private readonly string mainDBConnectString;
         //private readonly string onlineDBConnectString;
 
-        private DateTime latestHeartbeatTime = DateTime.Now;
-        private List<EVSEOnlineRecord> updateData = new List<EVSEOnlineRecord>();
-        private List<EVSEOnlineRecord> insertData = new List<EVSEOnlineRecord>();
-
         public CheckEVSEOnlineJob(
             ILogger<CheckEVSEOnlineJob> logger,
+            OnlineLogDbService onlineLogDbService,
             SqlConnectionFactory<MainDBContext> mainDbConnectionFactory,
             SqlConnectionFactory<OnlineLogDBContext> onlineLogDbConnectionFactory)
         {
             this.logger = logger;
+            this.onlineLogDbService = onlineLogDbService;
             this.mainDbConnectionFactory = mainDbConnectionFactory;
             this.onlineLogDbConnectionFactory = onlineLogDbConnectionFactory;
             //this.mainDBConnectString = configuration.GetConnectionString("MainDBContext");
             //this.onlineDBConnectString = configuration.GetConnectionString("OnlineLogDBContext");
         }
 
+        private readonly ILogger logger;
+        private readonly OnlineLogDbService onlineLogDbService;
+        private readonly SqlConnectionFactory<MainDBContext> mainDbConnectionFactory;
+        private readonly SqlConnectionFactory<OnlineLogDBContext> onlineLogDbConnectionFactory;
+
+        private DateTime latestHeartbeatTime = DateTime.Now;
+        private List<EVSEOnlineRecord> updateData = new List<EVSEOnlineRecord>();
+        private List<EVSEOnlineRecord> insertData = new List<EVSEOnlineRecord>();
+
         public async Task Execute(IJobExecutionContext context)
         {
             logger.LogDebug(this.ToString() + " :Starting........");
@@ -50,38 +54,43 @@ namespace EVCB_OCPP.TaskScheduler.Jobs
                 List<EVSECurrentStatus> _EVSEs = await GetEVSEs();
                 var checktime = DateTime.UtcNow.AddDays(-3);
                 _EVSEs = _EVSEs.Where(x => x.HeartbeatUpdatedOn > checktime).ToList();
+                List<Task> saveTasks = new List<Task>();
 
-                foreach (var evse in _EVSEs)
-                {
+                var temp = _EVSEs.GroupBy(x => IsOnlineNow(x)).ToList();
 
-                    if (IsOnlineNow(evse))
+                foreach (var g in temp)
+                {
+                    if (g.Key)
                     {
+                        _EVSEs = g.Where(x => !x.Online).ToList();
 
-                        if (!evse.Online)
-                        { //off - on                           
-
-                            await UpdateEVSECurrentStatus(evse.CustomerId.ToString(), evse.ChargeBoxId, true, DefaultSetting.DefaultNullTime);
-                            await UpdateOnlineRecords(evse.ChargeBoxId, true, evse.HeartbeatUpdatedOn, null);
+                        for (int evseIndex = 0; evseIndex < _EVSEs.Count; evseIndex++)
+                        {
+                            var evse = _EVSEs[evseIndex];
+                            saveTasks.Add(UpdateEVSECurrentStatus(evse.CustomerId.ToString(), evse.ChargeBoxId, true, DefaultSetting.DefaultNullTime));
+                            //saveTasks.Add(UpdateOnlineRecords(evse.ChargeBoxId, true, evse.HeartbeatUpdatedOn, null));
+                            saveTasks.Add(onlineLogDbService.InsertOnlineLog(evse.ChargeBoxId, evse.HeartbeatUpdatedOn));
                         }
                     }
                     else
                     {
+                        _EVSEs = g.Where(x => x.Online).ToList();
+                        var ChargeBoxIdOnlineRecordIdPairs = await GetOnlineRecordId(_EVSEs.Select(x=>x.ChargeBoxId).ToList());
 
-                        if (evse.Online)
+                        for (int evseIndex = 0; evseIndex < _EVSEs.Count; evseIndex++)
                         {
-                            //on -off                        
-
-                            await UpdateEVSECurrentStatus(evse.CustomerId.ToString(), evse.ChargeBoxId, false, evse.HeartbeatUpdatedOn);
-                            var online_row = await GetOnlineRecord(evse.ChargeBoxId);
-                            if (online_row != null)
+                            var evse = _EVSEs[evseIndex];
+                            saveTasks.Add(UpdateEVSECurrentStatus(evse.CustomerId.ToString(), evse.ChargeBoxId, false, evse.HeartbeatUpdatedOn));
+                            var online_rowId = ChargeBoxIdOnlineRecordIdPairs[evse.ChargeBoxId];
+                            if (online_rowId is not null)
                             {
-                                await UpdateOnlineRecords(evse.ChargeBoxId, false, evse.HeartbeatUpdatedOn, online_row.Id);
+                                saveTasks.Add(onlineLogDbService.UpdateOnlineLog(online_rowId.Value, evse.HeartbeatUpdatedOn));
                             }
-
                         }
-
                     }
                 }
+
+                await Task.WhenAll(saveTasks);
             }
             catch (Exception ex)
             {
@@ -105,7 +114,7 @@ namespace EVCB_OCPP.TaskScheduler.Jobs
                 string sqlstring =
                     """
                     SELECT m.CustomerId,m.Id,m.ChargeBoxId,m.Online,m.HeartbeatUpdatedOn,MachineConfigurations.ConfigureSetting HeartbeatInterval
-                    FROM [dbo].[Machine] m left join [dbo].[MachineConfigurations] MachineConfigurations  on m.ChargeBoxId = MachineConfigurations.ChargeBoxId
+                    FROM [dbo].[Machine] m left join [dbo].[MachineConfigurations] MachineConfigurations on m.ChargeBoxId = MachineConfigurations.ChargeBoxId
                     WHERE MachineConfigurations.ConfigureName = 'HeartbeatInterval'  and MachineConfigurations.ConfigureSetting!=''
                     """;
                 result = (await dbConn.QueryAsync<EVSECurrentStatus>(sqlstring)).ToList();
@@ -127,7 +136,7 @@ namespace EVCB_OCPP.TaskScheduler.Jobs
                 using var dbConn = await mainDbConnectionFactory.CreateAsync();
                 var parameters = new DynamicParameters();
                 parameters.Add("@Online", turnOn, System.Data.DbType.Boolean);
-                parameters.Add("@chargeBoxId", ChargeBoxId, System.Data.DbType.String);
+                parameters.Add("@chargeBoxId", ChargeBoxId, System.Data.DbType.String, System.Data.ParameterDirection.Input, 50);
 
                 if (!turnOn)
                 {
@@ -154,7 +163,7 @@ namespace EVCB_OCPP.TaskScheduler.Jobs
                     using var dbConn = await onlineLogDbConnectionFactory.CreateAsync();
                     {
                         var parameters = new DynamicParameters();
-                        parameters.Add("@ChargeBoxId", chargeBoxId, System.Data.DbType.String);
+                        parameters.Add("@ChargeBoxId", chargeBoxId, System.Data.DbType.String, System.Data.ParameterDirection.Input, 36);
                         parameters.Add("@OnlineTime", hearbeatDt, System.Data.DbType.DateTime);
                         parameters.Add("@OfflineTime", DefaultSetting.DefaultNullTime, System.Data.DbType.DateTime);
 
@@ -165,7 +174,7 @@ namespace EVCB_OCPP.TaskScheduler.Jobs
                 {
                     if (rowId.HasValue)
                     {
-                        string sqlString = "UPDATE dbo.EVSEOnlineRecord SET OfflineTime=@OfflineTime  WHERE Id=@Id";
+                        string sqlString = "UPDATE dbo.EVSEOnlineRecord SET OfflineTime=@OfflineTime WHERE Id=@Id";
                         using (var dbConn = await onlineLogDbConnectionFactory.CreateAsync())
                         {
                             var parameters = new DynamicParameters();
@@ -194,20 +203,20 @@ namespace EVCB_OCPP.TaskScheduler.Jobs
 
 
 
-        async private Task<EVSEOnlineRecord> GetOnlineRecord(string chargeBoxId)
+        private async Task<EVSEOnlineRecord> GetOnlineRecord(string chargeBoxId)
         {
             EVSEOnlineRecord lastrow = new EVSEOnlineRecord();
             try
             {
                 string sqlString = string.Format("SELECT Id FROM dbo.EVSEOnlineRecord WHERE chargeBoxId=@chargeBoxId Order by OnlineTime desc");
-                using (var dbConn = onlineLogDbConnectionFactory.Create())
+                using (var dbConn = await onlineLogDbConnectionFactory.CreateAsync())
                 {
                     var parameters = new DynamicParameters();
-                    parameters.Add("@chargeBoxId", chargeBoxId, System.Data.DbType.String);
+                    parameters.Add("@chargeBoxId", chargeBoxId, System.Data.DbType.String, System.Data.ParameterDirection.Input, 36);
 
 
-                    var result = await dbConn.QueryAsync<EVSEOnlineRecord>(sqlString, parameters);
-                    lastrow = result.FirstOrDefault();
+                    lastrow = await dbConn.QueryFirstOrDefaultAsync<EVSEOnlineRecord>(sqlString, parameters);
+                    //lastrow = result.FirstOrDefault();
                 }
 
             }
@@ -218,6 +227,44 @@ namespace EVCB_OCPP.TaskScheduler.Jobs
             return lastrow;
         }
 
+        private async Task<Dictionary<string, long?>> GetOnlineRecordId(List<string> chargeBoxIds)
+        {
+            Dictionary<string, long?> toReturn = new();
+            Dictionary<string, long> queryResult;
+            string sqlString = """
+                WITH CTE AS (
+                    SELECT
+                        Id, ChargeBoxId,
+                        ROW_NUMBER() OVER (PARTITION BY s.ChargeBoxId ORDER BY s.OnlineTime DESC) AS RowNum
+                    FROM [dbo].[EVSEOnlineRecord] s
+                    WHERE ChargeBoxId in @ChargeBoxIds
+                )
+                SELECT Id,ChargeBoxId
+                FROM CTE
+                WHERE RowNum = 1;
+                """;
+            var parameters = new DynamicParameters();
+            parameters.Add("@ChargeBoxIds", chargeBoxIds.Select(x => new DbString() { Value = x, Length = 36 }));
+
+            try
+            {
+                using (var dbConn = await onlineLogDbConnectionFactory.CreateAsync())
+                {
+                    var temp = await dbConn.QueryAsync<EVSEOnlineRecord>(sqlString, parameters);
+                    queryResult = temp.ToDictionary(x => x.ChargeBoxId, x => x.Id);
+                    //lastrow = result.FirstOrDefault();
+                }
+
+                toReturn = chargeBoxIds.ToDictionary<string,string,long?>(keySelector: x => x, elementSelector: x => queryResult.ContainsKey(x) ? queryResult[x] : null);
+            }
+            catch (Exception ex)
+            {
+                logger.LogError("Query Data Error " + ex.ToString());
+                toReturn = chargeBoxIds.ToDictionary<string, string, long?>(keySelector: x => x, elementSelector: null);
+            }
+            return toReturn;
+        }
+
 
         private bool IsOnlineNow(EVSECurrentStatus currentEVSE)
         {

+ 1 - 2
EVCB_OCPP.TaskScheduler/Jobs/DeleteServerMessageJob.cs

@@ -35,8 +35,7 @@ namespace EVCB_OCPP.TaskScheduler.Jobs
                 counter++;
                 using (var dbConn = await mainDBConnectionFactory.CreateAsync())
                 {
-                    dbConn.Open();
-                    string sqlstring = "Delete Top(10000) from ServerMessage";
+                    string sqlstring = "DELETE Top(10000) From ServerMessage WHERE ReceivedOn != '1991-01-01'";
                     await dbConn.ExecuteAsync(sqlstring);
                 }
             }

+ 15 - 6
EVCB_OCPP.TaskScheduler/Jobs/StartTransacionReportJob.cs

@@ -15,11 +15,16 @@ namespace EVCB_OCPP.TaskScheduler.Jobs
     public class StartTransacionReportJob : IJob
     {
         private readonly ICustomersService customersService;
+        private readonly DatabaseService databaseService;
         private readonly ILogger logger;
 
-        public StartTransacionReportJob(ICustomersService customersService, ILogger<StartTransacionReportJob> logger )
+        public StartTransacionReportJob(
+            ICustomersService customersService,
+            DatabaseService databaseService,
+            ILogger<StartTransacionReportJob> logger )
         {
             this.customersService = customersService;
+            this.databaseService = databaseService;
             this.logger = logger;
         }
       
@@ -32,13 +37,17 @@ namespace EVCB_OCPP.TaskScheduler.Jobs
 
             var cList = await customersService.GetCallPartnerCustomers();
 
+            var items = await databaseService.GetNeedReportStartTransactionSession();
+
             foreach (var customerId in cList)
             {
                 if (customerId == new Guid("009E603C-79CD-4620-A2B8-D9349C0E8AD8")) continue;
 
                 ICustomerService s = await customersService.GetCustomerService(customerId);
 
-               tList.Add(DoMainTask(s));
+                //tList.Add(s.ReportStartTransaction());
+                //tList.Add(DoMainTask(s));
+                tList.Add(s.ReportStartTransaction(items.Where(x=>x.CustomerId == customerId).ToList()));
             }
 
             await Task.WhenAll(tList.ToArray());
@@ -46,9 +55,9 @@ namespace EVCB_OCPP.TaskScheduler.Jobs
             logger.LogInformation("{0} complete", this.ToString());
         }
 
-        private Task DoMainTask(ICustomerService _service)
-        {
-            return _service.ReportStartTransaction();
-        }
+        //private Task DoMainTask(ICustomerService _service)
+        //{
+        //    return _service.ReportStartTransaction();
+        //}
     }
 }

+ 11 - 2
EVCB_OCPP.TaskScheduler/Jobs/StopTransacionReportJob.cs

@@ -15,12 +15,17 @@ namespace EVCB_OCPP.TaskScheduler.Jobs
     public class StopTransacionReportJob : IJob
     {
         private readonly ICustomersService customersService;
+        private readonly DatabaseService databaseService;
         private readonly ILogger logger;
 
 
-        public StopTransacionReportJob(ICustomersService customersService, ILogger<StopTransacionReportJob> logger)
+        public StopTransacionReportJob(
+            ICustomersService customersService,
+            DatabaseService databaseService,
+            ILogger<StopTransacionReportJob> logger)
         {
             this.customersService = customersService;
+            this.databaseService = databaseService;
             this.logger = logger;
         }
 
@@ -33,11 +38,15 @@ namespace EVCB_OCPP.TaskScheduler.Jobs
 
             var cList = await customersService.GetNotifyStopTransactionCustomers();
 
+            var items = await databaseService.GetNeedReportStopTransactionSession();
+
             foreach (var customerId in cList)
             {
                 ICustomerService s = await customersService.GetCustomerService(customerId);
 
-                tList.Add(DoMainTask(s));
+                //tList.Add(s.ReportStopTransaction());
+                tList.Add(s.ReportStopTransaction(items.Where(x=>x.CustomerId == customerId).ToList()));
+                //tList.Add(DoMainTask(s));
             }
 
             await Task.WhenAll(tList.ToArray());

+ 2 - 0
EVCB_OCPP.TaskScheduler/Models/CustomerConnectionDto.cs

@@ -8,6 +8,8 @@ namespace EVCB_OCPP.TaskScheduler.Models
 {
    public class CustomerConnectionDto
     {
+        public string Name { get; set; }
+
         public string ApiKey { set; get; }
 
         public string ApiUrl { set; get; }

+ 1 - 0
EVCB_OCPP.TaskScheduler/Program.cs

@@ -41,6 +41,7 @@ namespace EVCB_OCPP.TaskScheduler
                     services.AddTransient<ICustomerService, CommonCustomerService>();
                     services.AddSingleton<ICustomersService, CustomersService>();
 
+                    services.AddSingleton<OnlineLogDbService>();
                     services.AddSingleton<DatabaseService>();
                     //services.AddTransient<OuterHttpClient>();
 

+ 24 - 17
EVCB_OCPP.TaskScheduler/Services/CommonCustomerService.cs

@@ -37,30 +37,38 @@ namespace EVCB_OCPP.TaskScheduler.Services
         {
             this.customerId = customerId;
 
-            customerName = await _dbService.GetCustomerName(this.customerId);
+            //customerName = await _dbService.GetCustomerName(this.customerId);
             //_dbService.GetCustomerName(this.customerId);
             var connectionInfo = await _dbService.GetAPIConnectionInfo(customerId);
+            customerName = connectionInfo.Name;
             _saltkey = connectionInfo.ApiKey;
             _partnerAPIRoot = connectionInfo.ApiUrl;
         }
 
-        async public Task ReportStartTransaction()
+        async public Task ReportStartTransaction(List<Models.Transaction> items = null)
         {
-            var items = await _dbService.GetNeedReportSession(customerId, true, 1000);
+            //List<Models.Transaction> items = await _dbService.GetNeedReportSession(customerId, true, 1000);
+            if (items == null)
+            {
+                items = await _dbService.GetNeedReportSession(customerId, true, 1000);
+            }
+            items = items.Where(x => x.CustomerId == customerId).ToList();
 
             Stopwatch watch = Stopwatch.StartNew();
 
             List<Task> groupTasks = new List<Task>();
-            int skipCount = 0;
+            int completedCount = 0;
             int count = items.Count / 5 <= 100 ? items.Count : items.Count / 5;
-            while (skipCount < items.Count)
+            //var groupedItems = items.Chunk(50);
+
+            while (completedCount < items.Count)
             {
-                if (items.Count - skipCount < count)
+                if (items.Count - completedCount < count)
                 {
-                    count = items.Count - skipCount;
+                    count = items.Count - completedCount;
                 }
 
-                var templst = items.Skip(skipCount).Take(count).ToList();
+                var templst = items.Skip(completedCount).Take(count).ToList();
                 if (templst.Count > 0)
                 {
                     Task t = Task.Factory.StartNew(async () =>
@@ -70,7 +78,7 @@ namespace EVCB_OCPP.TaskScheduler.Services
                     groupTasks.Add(t);
 
                 }
-                skipCount += count;
+                completedCount += count;
             }
             while (ChargeRecordCallCounter != groupTasks.Count)
             {
@@ -152,10 +160,13 @@ namespace EVCB_OCPP.TaskScheduler.Services
 
         }
 
-        async public Task ReportStopTransaction()
+        public async Task ReportStopTransaction(List<Models.Transaction> items = null)
         {
-
-            var items = await _dbService.GetNeedReportSession(customerId, false, 1000);
+            if (items == null)
+            {
+                items = await _dbService.GetNeedReportSession(customerId, true, 1000);
+            }
+            items = items.Where(x => x.CustomerId == customerId).ToList();
 
             Stopwatch watch = new Stopwatch();
             watch.Start();
@@ -304,15 +315,11 @@ namespace EVCB_OCPP.TaskScheduler.Services
 
         }
 
-
-
-
         async public Task ReportExecutionofRemoteCommand()
         {
             var items = await _dbService.GetNeedReportExecution(customerId, 1000);
 
-            Stopwatch watch = new Stopwatch();
-            watch.Start();
+            Stopwatch watch = Stopwatch.StartNew();
 
             List<Task> groupTasks = new List<Task>();
             int skipCount = 0;

+ 55 - 6
EVCB_OCPP.TaskScheduler/Services/DatabaseService.cs

@@ -1,5 +1,5 @@
 using Dapper;
-using EVCB_OCPP.Domain;
+using EVCB_OCPP.Domain.Models.Database;
 using EVCB_OCPP.TaskScheduler.Helper;
 using EVCB_OCPP.TaskScheduler.Models;
 using Microsoft.Data.SqlClient;
@@ -8,8 +8,10 @@ using Polly;
 using System;
 using System.Collections.Generic;
 using System.Data;
+using System.Data.SqlTypes;
 using System.Linq;
 using System.Threading.Tasks;
+using MachineOperateRecord = EVCB_OCPP.TaskScheduler.Models.MachineOperateRecord;
 using Transaction = EVCB_OCPP.TaskScheduler.Models.Transaction;
 
 namespace EVCB_OCPP.TaskScheduler.Services
@@ -17,8 +19,8 @@ namespace EVCB_OCPP.TaskScheduler.Services
     public class DatabaseService
     {
         private readonly ILogger logger;
-        private readonly SqlConnectionFactory<MainDBContext> mainDbConnectionFactory;
-        private readonly SqlConnectionFactory<OnlineLogDBContext> onlineLogDbConnectionFactory;
+        private readonly SqlConnectionFactory<Domain.MainDBContext> mainDbConnectionFactory;
+        //private readonly SqlConnectionFactory<OnlineLogDBContext> onlineLogDbConnectionFactory;
         private readonly SqlConnectionFactory<WebDBConetext> webDbConnectionFactory;
 
         //private readonly string mainDBConnectString;
@@ -26,13 +28,13 @@ namespace EVCB_OCPP.TaskScheduler.Services
 
         public DatabaseService(
             ILogger<DatabaseService> logger,
-            SqlConnectionFactory<MainDBContext> mainDbConnectionFactory,
+            SqlConnectionFactory<Domain.MainDBContext> mainDbConnectionFactory,
             SqlConnectionFactory<OnlineLogDBContext> onlineLogDbConnectionFactory,
             SqlConnectionFactory<WebDBConetext> webDbConnectionFactory)
         {
             this.logger = logger;
             this.mainDbConnectionFactory = mainDbConnectionFactory;
-            this.onlineLogDbConnectionFactory = onlineLogDbConnectionFactory;
+            //this.onlineLogDbConnectionFactory = onlineLogDbConnectionFactory;
             this.webDbConnectionFactory = webDbConnectionFactory;
 
             //mainDBConnectString = configuration.GetConnectionString("MainDBContext");
@@ -93,7 +95,7 @@ namespace EVCB_OCPP.TaskScheduler.Services
             parameters.Add("@Id", partnerId, DbType.Guid, ParameterDirection.Input);
             using (SqlConnection conn = await mainDbConnectionFactory.CreateAsync())
             {
-                string strSql = "Select ApiKey, ApiUrl from [dbo].[Customer] where Id=@Id; ";
+                string strSql = "Select Name, ApiKey, ApiUrl from [dbo].[Customer] where Id=@Id; ";
                 result = await conn.QueryFirstOrDefaultAsync<CustomerConnectionDto>(strSql, parameters);
             }
             return result;
@@ -195,6 +197,53 @@ namespace EVCB_OCPP.TaskScheduler.Services
             return result;
         }
 
+        internal async Task<List<Transaction>> GetNeedReportStartTransactionSession(int size = 1000)
+        {
+            //string sqlString = $"""
+            //    SELECT Top({size}) Id, CustomerId,ReservationId, ChargeBoxId,ConnectorId,StartTime,MeterStart,StartIdTag 
+            //    FROM [dbo].[TransactionRecord]
+            //    WHERE StopTime='1991/1/1' and StartTransactionReportedOn='1991/1/1'
+            //    """;
+            string sqlString = $"""
+                 WITH CTE AS (
+                	 SELECT Id, CustomerId,ReservationId, ChargeBoxId,ConnectorId,StartTime,MeterStart,StartIdTag 
+                		, ROW_NUMBER() OVER (PARTITION BY CustomerId ORDER BY Id desc) AS RowNum
+                	 FROM [dbo].[TransactionRecord]
+                	 WHERE StopTime='1991/1/1' and StartTransactionReportedOn='1991/1/1'
+                )
+                SELECT Id, CustomerId,ReservationId, ChargeBoxId,ConnectorId,StartTime,MeterStart,StartIdTag 
+                FROM CTE
+                WHERE RowNum < {size};
+                """;
+
+            using var dbConn = await mainDbConnectionFactory.CreateAsync();
+            return (await dbConn.QueryAsync<Transaction>(sqlString)).ToList();
+        }
+        
+        internal async Task<List<Transaction>> GetNeedReportStopTransactionSession(int size = 1000)
+        {
+            //string sqlString = $"""
+            //    SELECT Top({size}) Id,ReservationId,ChargeBoxId,ConnectorId,StartTime,StopTime,MeterStart,MeterStop,StartIdTag ,StopReasonId,Receipt,Cost,Fee 
+            //    FROM [dbo].[TransactionRecord] 
+            //    WHERE StopTime!='1991/1/1' and StopTransactionReportedOn='1991/1/1' 
+            //    """;
+            string sqlString = $"""
+                WITH CTE AS (
+                    SELECT Id,CustomerId,ReservationId,ChargeBoxId,ConnectorId,StartTime,StopTime,MeterStart,MeterStop,StartIdTag ,StopReasonId,Receipt,Cost,Fee 
+                	    , ROW_NUMBER() OVER (PARTITION BY CustomerId ORDER BY Id desc) AS RowNum
+                    FROM [dbo].[TransactionRecord] 
+                    WHERE StopTime!='1991/1/1' and StopTransactionReportedOn='1991/1/1'
+                )
+                SELECT Id,CustomerId,ReservationId,ChargeBoxId,ConnectorId,StartTime,StopTime,MeterStart,MeterStop,StartIdTag ,StopReasonId,Receipt,Cost,Fee
+                FROM CTE
+                WHERE RowNum < {size};
+                """;
+
+            using var dbConn = await mainDbConnectionFactory.CreateAsync();
+            return (await dbConn.QueryAsync<Transaction>(sqlString)).ToList();
+        }
+
+
         internal async Task<List<MachineOperateRecord>> GetNeedReportExecution(Guid customerId, int size)
         {
             List<MachineOperateRecord> result = new List<MachineOperateRecord>();

+ 2 - 2
EVCB_OCPP.TaskScheduler/Services/ICustomerService.cs

@@ -10,9 +10,9 @@ namespace EVCB_OCPP.TaskScheduler.Services
     {
         Task SetCustomerId(Guid customerId);
 
-        Task ReportStartTransaction();
+        Task ReportStartTransaction(List<Models.Transaction> items = null);
 
-        Task ReportStopTransaction();
+        Task ReportStopTransaction(List<Models.Transaction> items = null);
 
         Task MonitorRemoteCommand();
 

+ 134 - 0
EVCB_OCPP.TaskScheduler/Services/OnlineLogDbService.cs

@@ -0,0 +1,134 @@
+using Dapper;
+using EVCB_OCPP.TaskScheduler.Helper;
+using EVCB_OCPP.TaskScheduler.Models;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EVCB_OCPP.TaskScheduler.Services;
+
+public class OnlineLogDbService
+{
+    public OnlineLogDbService(
+        SqlConnectionFactory<OnlineLogDBContext> connectionFactory,
+        ILogger<OnlineLogDbService> logger
+        )
+    {
+        this.connectionFactory = connectionFactory;
+        this.logger = logger;
+
+        InitInsertOnlineLogHandler();
+        InitUpdateOnlineLogHandler();
+    }
+
+    private void InitUpdateOnlineLogHandler()
+    {
+        if (updateOnlineLogHandler is not null)
+        {
+            throw new Exception($"{nameof(InitUpdateOnlineLogHandler)} should only called once");
+        }
+
+        updateOnlineLogHandler = new GroupSingleHandler<EVSEOnlineRecord>(
+            //BulkInsertWithBulkCopy,
+            BundleUpdateWithDapper,
+            //loggerFactory.CreateLogger("InsertMeterValueHandler")
+            logger,
+            workerCnt: 10
+            );
+    }
+
+    private void InitInsertOnlineLogHandler()
+    {
+        if (insertOnlineLogHandler is not null)
+        {
+            throw new Exception($"{nameof(InitInsertOnlineLogHandler)} should only called once");
+        }
+
+        insertOnlineLogHandler = new GroupSingleHandler<EVSEOnlineRecord>(
+            //BulkInsertWithBulkCopy,
+            BundleInsertWithDapper,
+            //loggerFactory.CreateLogger("InsertMeterValueHandler")
+            logger,
+            workerCnt: 10
+            );
+    }
+
+    private readonly SqlConnectionFactory<OnlineLogDBContext> connectionFactory;
+    private readonly ILogger<OnlineLogDbService> logger;
+
+    private GroupSingleHandler<EVSEOnlineRecord> insertOnlineLogHandler;
+    private GroupSingleHandler<EVSEOnlineRecord> updateOnlineLogHandler;
+
+    internal Task UpdateOnlineLog(long rowId, DateTime offlineTime)
+    {
+        return updateOnlineLogHandler.HandleAsync(new EVSEOnlineRecord() { Id = rowId, OfflineTime = offlineTime });
+        //return UpdateOnlineLogDapper(rowId, offlineTime);
+    }
+
+    internal Task InsertOnlineLog(string chargeBoxId, DateTime heartbeatUpdatedOn)
+    {
+        return insertOnlineLogHandler.HandleAsync(new EVSEOnlineRecord() { ChargeBoxId = chargeBoxId, OnlineTime = heartbeatUpdatedOn });
+        //return InsertOnlineLogDapper(chargeBoxId, heartbeatUpdatedOn);
+    }
+
+    private async Task UpdateOnlineLogDapper(long rowId, DateTime offlineTime)
+    {
+        string sqlString = "UPDATE dbo.EVSEOnlineRecord SET OfflineTime=@OfflineTime WHERE Id=@Id";
+        var parameters = new DynamicParameters();
+        parameters.Add("@OfflineTime", offlineTime, System.Data.DbType.DateTime);
+        parameters.Add("@Id", rowId, System.Data.DbType.Int64);
+
+        using var dbConn = await connectionFactory.CreateAsync();
+        await dbConn.ExecuteAsync(sqlString, parameters);
+    }
+
+    private async Task InsertOnlineLogDapper(string chargeBoxId, DateTime heartbeatUpdatedOn)
+    {
+        string sqlString = """
+            INSERT INTO dbo.EVSEOnlineRecord ("ChargeBoxId","OnlineTime","OfflineTime")
+            VALUES( @ChargeBoxId, @OnlineTime, @OfflineTime);
+            """;
+        var parameters = new DynamicParameters();
+        parameters.Add("@ChargeBoxId", chargeBoxId, System.Data.DbType.String, System.Data.ParameterDirection.Input, 36);
+        parameters.Add("@OnlineTime", heartbeatUpdatedOn, System.Data.DbType.DateTime);
+        parameters.Add("@OfflineTime", DefaultSetting.DefaultNullTime, System.Data.DbType.DateTime);
+
+        using var dbConn = await connectionFactory.CreateAsync();
+        await dbConn.ExecuteAsync(sqlString, parameters);
+    }
+
+    private async Task BundleUpdateWithDapper(IEnumerable<EVSEOnlineRecord> records)
+    {
+        string sqlString = "UPDATE dbo.EVSEOnlineRecord SET OfflineTime=@OfflineTime WHERE Id=@Id";
+
+        using var dbConn = await connectionFactory.CreateAsync();
+        foreach (var record in records)
+        {
+            var parameters = new DynamicParameters();
+            parameters.Add("@OfflineTime", record.OfflineTime, System.Data.DbType.DateTime);
+            parameters.Add("@Id", record.Id, System.Data.DbType.Int64);
+            await dbConn.ExecuteAsync(sqlString, parameters);
+        }
+    }
+
+    private async Task BundleInsertWithDapper(IEnumerable<EVSEOnlineRecord> records)
+    {
+        string sqlString = """
+            INSERT INTO dbo.EVSEOnlineRecord ("ChargeBoxId","OnlineTime","OfflineTime")
+            VALUES( @ChargeBoxId, @OnlineTime, @OfflineTime);
+            """;
+
+        using var dbConn = await connectionFactory.CreateAsync();
+        foreach (var record in records)
+        {
+            var parameters = new DynamicParameters();
+            parameters.Add("@ChargeBoxId", record.ChargeBoxId, System.Data.DbType.String, System.Data.ParameterDirection.Input, 36);
+            parameters.Add("@OnlineTime", record.OnlineTime, System.Data.DbType.DateTime);
+            parameters.Add("@OfflineTime", DefaultSetting.DefaultNullTime, System.Data.DbType.DateTime);
+            await dbConn.ExecuteAsync(sqlString, parameters);
+        }
+    }
+}