using System.Data.Common;
using Dapper;
using FrameworkDAL.DTO.Server;
using Microsoft.Data.SqlClient;
using Npgsql;
namespace FrameworkBLL.Connection
{
///
/// Resolves a DbConnection driver by DBINSTANCE.DBTypeId:
/// 1 = SQL Server (Microsoft.Data.SqlClient)
/// 2 = PostgreSQL (Npgsql)
/// MySQL / Oracle = Phase 4
///
public class ExternalDbConnectionFactory : IExternalDbConnectionFactory
{
// DBTypeId constants (from MDBTYPE seed data)
private const int DbTypeSqlServer = 1;
private const int DbTypePostgreSQL = 2;
public async Task OpenAsync(DBInstanceDTO instance, CancellationToken ct)
{
var conn = CreateConnection(instance);
await conn.OpenAsync(ct).ConfigureAwait(false);
return conn;
}
public async Task TestConnectionAsync(DBInstanceDTO instance, CancellationToken ct)
{
try
{
await using var conn = CreateConnection(instance);
await conn.OpenAsync(ct).ConfigureAwait(false);
return true;
}
catch
{
return false;
}
}
public async Task> GetColumnMetaAsync(
DbConnection conn, string schemaName, string tableName, CancellationToken ct)
{
// INFORMATION_SCHEMA is supported by SQL Server, PostgreSQL, and MySQL.
const string sql = @"
SELECT
c.COLUMN_NAME AS ColumnName,
c.DATA_TYPE AS DataType,
c.CHARACTER_MAXIMUM_LENGTH AS MaxLength,
c.NUMERIC_PRECISION AS NumericPrecision,
c.NUMERIC_SCALE AS NumericScale,
CASE WHEN c.IS_NULLABLE = 'YES' THEN 1 ELSE 0 END AS IsNullable,
CASE WHEN kcu.COLUMN_NAME IS NOT NULL THEN 1 ELSE 0 END AS IsPrimaryKey,
c.ORDINAL_POSITION AS OrdinalPosition
FROM INFORMATION_SCHEMA.COLUMNS c
LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
ON kcu.TABLE_SCHEMA = c.TABLE_SCHEMA
AND kcu.TABLE_NAME = c.TABLE_NAME
AND kcu.COLUMN_NAME = c.COLUMN_NAME
AND EXISTS (
SELECT 1
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
WHERE tc.TABLE_SCHEMA = kcu.TABLE_SCHEMA
AND tc.TABLE_NAME = kcu.TABLE_NAME
AND tc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME
AND tc.CONSTRAINT_TYPE = 'PRIMARY KEY'
)
WHERE c.TABLE_SCHEMA = @SchemaName
AND c.TABLE_NAME = @TableName
ORDER BY c.ORDINAL_POSITION";
var rows = await conn.QueryAsync(
new CommandDefinition(sql, new { SchemaName = schemaName, TableName = tableName },
cancellationToken: ct))
.ConfigureAwait(false);
return rows.Select(r => new ExternalColumnMetaResult(
ColumnName: (string)r.ColumnName,
DataType: (string)r.DataType,
MaxLength: (int?)r.MaxLength,
NumericPrecision: (int?)r.NumericPrecision,
NumericScale: (int?)r.NumericScale,
IsNullable: (bool)(r.IsNullable == 1),
IsPrimaryKey: (bool)(r.IsPrimaryKey == 1),
OrdinalPosition: (int)r.OrdinalPosition));
}
public async Task> GetExternalTablesAsync(
DbConnection conn, string schemaName, CancellationToken ct)
{
const string sql = @"
SELECT TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = @SchemaName
AND TABLE_TYPE = 'BASE TABLE'
ORDER BY TABLE_NAME";
var rows = await conn.QueryAsync(
new CommandDefinition(sql, new { SchemaName = schemaName }, cancellationToken: ct))
.ConfigureAwait(false);
return rows;
}
// ── Private ──────────────────────────────────────────────────────────
private static DbConnection CreateConnection(DBInstanceDTO instance)
{
return instance.DBTypeId switch
{
DbTypeSqlServer => BuildSqlServerConnection(instance),
DbTypePostgreSQL => BuildPostgresConnection(instance),
3 => throw new NotSupportedException(
$"MySQL driver (DBTypeId=3) support is scheduled for Phase 4. " +
"Add MySqlConnector package and implement BuildMySqlConnection()."),
4 => throw new NotSupportedException(
$"Oracle driver (DBTypeId=4) support is scheduled for Phase 4. " +
"Add Oracle.ManagedDataAccess.Core package and implement BuildOracleConnection()."),
_ => throw new InvalidOperationException(
$"Unsupported DBTypeId {instance.DBTypeId} ('{instance.DBTypeName}').")
};
}
private static SqlConnection BuildSqlServerConnection(DBInstanceDTO instance)
{
var csb = new SqlConnectionStringBuilder
{
DataSource = instance.ServerIp,
InitialCatalog = instance.DBInstanceDataBase,
UserID = instance.DBInstanceUserName,
Password = instance.DBInstancePassword,
TrustServerCertificate = true,
ConnectTimeout = 30
};
return new SqlConnection(csb.ConnectionString);
}
private static NpgsqlConnection BuildPostgresConnection(DBInstanceDTO instance)
{
var csb = new NpgsqlConnectionStringBuilder
{
Host = instance.ServerIp,
Database = instance.DBInstanceDataBase,
Username = instance.DBInstanceUserName,
Password = instance.DBInstancePassword,
Timeout = 30
};
return new NpgsqlConnection(csb.ConnectionString);
}
}
}