diff --git a/PortfolioViewer/PortfolioViewer.ApiService/Services/SyncGrpcService.cs b/PortfolioViewer/PortfolioViewer.ApiService/Services/SyncGrpcService.cs index f07c38b5d..f9c2cefb4 100644 --- a/PortfolioViewer/PortfolioViewer.ApiService/Services/SyncGrpcService.cs +++ b/PortfolioViewer/PortfolioViewer.ApiService/Services/SyncGrpcService.cs @@ -9,70 +9,10 @@ namespace GhostfolioSidekick.PortfolioViewer.ApiService.Services public class SyncGrpcService(DatabaseContext dbContext, ILogger logger) : SyncService.SyncServiceBase { private readonly string[] _tablesToIgnore = ["sqlite_sequence", "__EFMigrationsHistory", "__EFMigrationsLock"]; - private readonly Dictionary _tablesWithDates = []; - private bool _tablesWithDatesInitialized; + private Lazy>>? _tablesWithDatesCache; - private async Task> GetTablesWithDatesAsync() - { - if (_tablesWithDatesInitialized) return _tablesWithDates; - - using var connection = dbContext.Database.GetDbConnection(); - await connection.OpenAsync(); - - var tableNames = new List(); - using (var cmd = connection.CreateCommand()) - { - cmd.CommandText = "SELECT name FROM sqlite_master WHERE type='table'"; - using var reader = await cmd.ExecuteReaderAsync(); - while (await reader.ReadAsync()) - { - var name = reader.GetString(0); - if (!_tablesToIgnore.Contains(name)) tableNames.Add(name); - } - } - - foreach (var tableName in tableNames) - { - try - { - using var cmd = connection.CreateCommand(); - cmd.CommandText = $"PRAGMA table_info({tableName})"; - using var reader = await cmd.ExecuteReaderAsync(); - while (await reader.ReadAsync()) - { - var columnName = reader.GetString(1); - var columnType = reader.GetString(2); - if (IsDateColumn(columnName, columnType)) - { - _tablesWithDates[tableName] = columnName; - logger.LogDebug("Found date column {ColumnName} in table {TableName}", columnName, tableName); - break; - } - } - } - catch (Exception ex) - { - logger.LogWarning(ex, "Failed to analyze columns for table {TableName}", tableName); - } - } - - _tablesWithDatesInitialized = true; - logger.LogInformation("Discovered {Count} tables with date columns: {Tables}", - _tablesWithDates.Count, string.Join(", ", _tablesWithDates.Select(kvp => $"{kvp.Key}({kvp.Value})"))); - - return _tablesWithDates; - } - - private static bool IsDateColumn(string columnName, string columnType) => - columnName.ToLower() switch - { - var name when name == "date" || name.EndsWith("date") || name.StartsWith("date") || name.Contains("timestamp") => true, - _ => columnType.ToLower() switch - { - var type when type.Contains("date") || type.Contains("datetime") || type.Contains("timestamp") => true, - _ => false - } - }; + private Lazy>> TablesWithDatesCache => + _tablesWithDatesCache ??= new(() => LoadTablesWithDatesAsync(dbContext, logger, _tablesToIgnore)); public override async Task GetTableNames(GetTableNamesRequest request, ServerCallContext context) { @@ -171,6 +111,70 @@ public override async Task GetLatestDates(GetLatestDates } } + private async Task> GetTablesWithDatesAsync() => + await TablesWithDatesCache.Value; + + private static async Task> LoadTablesWithDatesAsync(DatabaseContext dbContext, ILogger logger, string[] tablesToIgnore) + { + var tablesWithDates = new Dictionary(); + + using var connection = dbContext.Database.GetDbConnection(); + await connection.OpenAsync(); + + var tableNames = new List(); + using (var cmd = connection.CreateCommand()) + { + cmd.CommandText = "SELECT name FROM sqlite_master WHERE type='table'"; + using var reader = await cmd.ExecuteReaderAsync(); + while (await reader.ReadAsync()) + { + var name = reader.GetString(0); + if (!tablesToIgnore.Contains(name)) tableNames.Add(name); + } + } + + foreach (var tableName in tableNames) + { + try + { + using var cmd = connection.CreateCommand(); + cmd.CommandText = $"PRAGMA table_info({tableName})"; + using var reader = await cmd.ExecuteReaderAsync(); + while (await reader.ReadAsync()) + { + var columnName = reader.GetString(1); + var columnType = reader.GetString(2); + if (IsDateColumn(columnName, columnType)) + { + tablesWithDates[tableName] = columnName; + logger.LogDebug("Found date column {ColumnName} in table {TableName}", columnName, tableName); + break; + } + } + } + catch (Exception ex) + { + logger.LogWarning(ex, "Failed to analyze columns for table {TableName}", tableName); + } + } + + logger.LogInformation("Discovered {Count} tables with date columns: {Tables}", + tablesWithDates.Count, string.Join(", ", tablesWithDates.Select(kvp => $"{kvp.Key}({kvp.Value})"))); + + return tablesWithDates; + } + + private static bool IsDateColumn(string columnName, string columnType) => + columnName.ToLower() switch + { + var name when name == "date" || name.EndsWith("date") || name.StartsWith("date") || name.Contains("timestamp") => true, + _ => columnType.ToLower() switch + { + var type when type.Contains("date") || type.Contains("datetime") || type.Contains("timestamp") => true, + _ => false + } + }; + private async Task GetEntityDataInternal(string entity, int page, int pageSize, string? sinceDate, IServerStreamWriter responseStream) { if (page <= 0 || pageSize <= 0) diff --git a/PortfolioViewer/PortfolioViewer.WASM/Pages/UpcomingDividends.razor b/PortfolioViewer/PortfolioViewer.WASM/Pages/UpcomingDividends.razor index 50a40f17d..9fe1794d4 100644 --- a/PortfolioViewer/PortfolioViewer.WASM/Pages/UpcomingDividends.razor +++ b/PortfolioViewer/PortfolioViewer.WASM/Pages/UpcomingDividends.razor @@ -48,7 +48,7 @@ - @foreach (var div in dividends.OrderBy(d => d.ExDate)) + @foreach (var div in dividends.OrderBy(d => d.PaymentDate)) { @div.Symbol @@ -65,7 +65,7 @@ } else { - + — } @div.Quantity.ToString("N2")