// ============================================================================= // GB5 Framework — IReportConnectionResolver // Namespace : FrameworkBLL.ReportOrchestration // Purpose : Resolves the correct IDbConnection for a given DataSourceType, // derived entirely from the LoginDTO session (which carries // ServerConfigId → MSERVERCONFIG). No runtime DB lookup required. // // Resolution logic: // DataSourceType.Oltp → LoginDTO.ServerConfigId // DataSourceType.ReportDb → MSERVERCONFIG.REPORTSERVERCONFIGID (-1 = OLTP fallback) // DataSourceType.ArchiveDb → MSERVERCONFIG.ARCHIVESERVERCONFIGID (-1 = OLTP fallback) // DataSourceType.Auto → caller must resolve via IDataSourceRuleEvaluator first // // Provider: // LoginDTO.DatabaseType → DatabaseProviderType enum // 0 = SqlServer (Microsoft.Data.SqlClient) // 1 = Oracle (Oracle.ManagedDataAccess) // 2 = PostgreSQL (Npgsql) // 3 = MySQL (MySql.Data) // // Lifetime: Scoped (one instance per HTTP request; caches resolved configs // within the request to avoid repeated MSERVERCONFIG lookups). // ============================================================================= using System.Data; using GB5Shared.DTO.Framework.Login; using GB5Shared.DTO.ReportOrchestration; using GB5Shared.Enums.ReportOrchestration; namespace FrameworkBLL.ReportOrchestration { /// /// Resolves a ready-to-open for the requested /// , using the session context in /// and the pre-loaded . /// /// /// Implementations must: /// /// Be registered as Scoped in DI. /// Cache resolved instances /// within the request (avoid repeated lookups per pipeline stage). /// Return connections in a closed state; the caller opens /// them so that Dapper / ADO.NET lifetime is controlled explicitly. /// Never log or expose connection strings. /// /// public interface IReportConnectionResolver { /// /// Resolves a closed for the given /// using the session in /// . /// /// /// The authenticated session. Must carry a valid /// . /// /// /// The logical data source. Must not be /// — resolve Auto to a concrete /// source via before calling. /// /// Propagated from the HTTP request. /// /// A closed of the correct provider type. /// Caller is responsible for opening and disposing. /// /// /// Thrown when is /// . /// /// /// Thrown when the MSERVERCONFIG row cannot be located or the /// connection string cannot be formed. /// Task ResolveConnectionAsync( LoginDTO loginDTO, DataSourceType dataSource, CancellationToken cancellationToken = default); /// /// Returns the resolved for the /// given without opening a connection. /// Use this when you need metadata (ProviderType, IsDedicatedAnalyticsDb) /// to make decisions (e.g. whether to apply NOLOCK hints) before /// opening a connection. /// /// The authenticated session. /// /// The logical data source. Must not be . /// /// Propagated from the HTTP request. Task ResolveConfigAsync( LoginDTO loginDTO, DataSourceType dataSource, CancellationToken cancellationToken = default); /// /// Checks whether a dedicated analytics database is configured for /// . Returns false when the /// config falls back to OLTP (REPORTSERVERCONFIGID / /// ARCHIVESERVERCONFIGID = -1), meaning the report will execute /// against OLTP — callers should log a warning in this case. /// /// The authenticated session. /// /// ReportDb or ArchiveDb. Returns false for Oltp (trivially true). /// bool IsDedicatedDbConfigured(LoginDTO loginDTO, DataSourceType dataSource); } // ========================================================================= // IDataSourceRuleEvaluator // Resolves DataSourceType.Auto → concrete source using MREPORTDATASOURCERULE // ========================================================================= /// /// Evaluates reports by applying the /// MREPORTDATASOURCERULE rows (ordered by RULEPRIORITY ascending) for the /// given report against the current request context. /// Returns the first matching rule's TARGETDATASOURCE, or the configured /// default from if no rule matches. /// /// /// Rules are loaded once per request and cached within the evaluator. /// Rule types supported: /// /// 0 — DateRangeDays: matches when /// > /// MREPORTDATASOURCERULE.THRESHOLDVALUE. /// 2 — TimeOfDay: matches when the current server hour /// (UTC) is ≥ THRESHOLDVALUE (e.g. route off-hours batches to Archive). /// 3 — Manual: never matched automatically; only via /// . /// /// Registered as Scoped. /// public interface IDataSourceRuleEvaluator { /// /// Resolves the concrete for the given /// execution context. Safe to call even when the config's /// DataSourceType is already concrete (returns it unchanged). /// /// /// The partially-initialised execution context. Must have /// set. /// /// Propagated from the HTTP request. /// /// A concrete — never /// . /// Task EvaluateAsync( ReportExecutionContext context, CancellationToken cancellationToken = default); } // ========================================================================= // ServerConfigCache — typed cache populated at login / first access // ========================================================================= /// /// In-memory cache of MSERVERCONFIG rows for the current session. /// Keyed by SERVERCONFIGID. Populated lazily on first resolver call. /// Avoids repeated DB roundtrips when the same datasource is resolved /// across multiple pipeline stages within one HTTP request. /// /// /// Registered as Scoped. Populated by /// on first access. /// Exposes only the fields required for connection resolution and /// NOLOCK/timeout decisions — not the full MSERVERCONFIG entity. /// public interface IServerConfigCache { /// /// Returns the cached for the given /// , loading from DB if not yet cached. /// Task GetAsync( int serverConfigId, CancellationToken cancellationToken = default); } /// /// Projection of MSERVERCONFIG fields required by the resolver. /// public sealed class CachedServerConfig { public int ServerConfigId { get; init; } /// MSERVERCONFIG.DATABASETYPE — maps to DatabaseProviderType. public DatabaseProviderType ProviderType { get; init; } public string DatabaseName { get; init; } = string.Empty; public string DatabaseUserName { get; init; } = string.Empty; /// /// Decrypted at load time; held in memory only. /// Never logged, never serialised. /// public string DatabasePassword { get; init; } = string.Empty; public string DatabasePort { get; init; } = string.Empty; public string ServerIp { get; init; } = string.Empty; public string ServerMachineName { get; init; } = string.Empty; /// MSERVERCONFIG.REPORTSERVERCONFIGID (-1 = no dedicated ReportDB). public int ReportServerConfigId { get; init; } = -1; /// MSERVERCONFIG.ARCHIVESERVERCONFIGID (-1 = no dedicated ArchiveDB). public int ArchiveServerConfigId { get; init; } = -1; /// MSERVERCONFIG.NATURE (0 Online, 1 Offline, 2 ReferAll). public byte Nature { get; init; } } }