DbHelper

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Linq;
using System.Threading.Tasks;

namespace App.Helpers
{
    /// <summary>
    /// DbHelper - สุดติ่งกระดิ่งแมวเวอร์ชันปรับปรุง
    /// ✅ รองรับ async, ✅ memory ดีขึ้น, ✅ type-safe, ✅ reuse สูง
    /// </summary>
    public class DbHelper : IDisposable
    {
        private readonly ConfigHelper _config;
        private bool _disposed = false;

        public DbHelper()
        {
            _config = new ConfigHelper();
        }

        // --- Connection String ---
        protected string GetConnectionString(string name)
        {
            if (string.IsNullOrWhiteSpace(name)) 
                return string.Empty;

            try
            {
                return name.Equals("DEFAULT", StringComparison.OrdinalIgnoreCase)
                    ? _config.GetConnectionString()
                    : _config.GetConfig(name);
            }
            catch
            {
                return string.Empty;
            }
        }

        // --- ExecuteNonQuery (Insert/Update/Delete) ---
        public async Task<int> ExecuteAsync(
            string connectionStringName,
            string sql,
            params SqlParameter[] parameters)
        {
            var cs = EncryptionHelper.Decrypt(GetConnectionString(connectionStringName));
            if (string.IsNullOrEmpty(cs))
                throw new InvalidOperationException("Connection string is empty or invalid.");

            using var conn = new SqlConnection(cs);
            using var cmd = CreateCommand(conn, sql, parameters);

            await conn.OpenAsync();
            return await cmd.ExecuteNonQueryAsync();
        }

        // --- ExecuteScalar ---
        public async Task<object> ExecuteScalarAsync(
            string connectionStringName,
            string sql,
            params SqlParameter[] parameters)
        {
            var cs = EncryptionHelper.Decrypt(GetConnectionString(connectionStringName));
            if (string.IsNullOrEmpty(cs))
                throw new InvalidOperationException("Connection string is empty or invalid.");

            using var conn = new SqlConnection(cs);
            using var cmd = CreateCommand(conn, sql, parameters);

            await conn.OpenAsync();
            return await cmd.ExecuteScalarAsync() ?? DBNull.Value;
        }

        // --- Query to List<Dictionary<string, object>> + Schema ---
        public async Task<(List<Dictionary<string, object>> Rows, List<ColumnInfo> Schema)> 
            QueryAsync(
                string connectionStringName,
                string sql,
                params SqlParameter[] parameters)
        {
            var cs = EncryptionHelper.Decrypt(GetConnectionString(connectionStringName));
            if (string.IsNullOrEmpty(cs))
                throw new InvalidOperationException("Connection string is empty or invalid.");

            var rows = new List<Dictionary<string, object>>();
            var schema = new List<ColumnInfo>();
            var schemaMap = new Dictionary<string, ColumnInfo>(StringComparer.OrdinalIgnoreCase);

            using var conn = new SqlConnection(cs);
            using var cmd = CreateCommand(conn, sql, parameters);

            await conn.OpenAsync();
            using var reader = await cmd.ExecuteReaderAsync();

            // อ่าน schema ก่อน
            var schemaTable = reader.GetSchemaTable();
            foreach (DataRow row in schemaTable.Rows)
            {
                var col = new ColumnInfo(
                    name: row["ColumnName"].ToString(),
                    dataType: (Type)row["DataType"],
                    ordinal: (int)row["ColumnOrdinal"],
                    isNullable: (bool)row["AllowDBNull"]
                );
                schema.Add(col);
                schemaMap[col.Name] = col;
            }

            // อ่านข้อมูล
            while (await reader.ReadAsync())
            {
                var rowDict = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
                for (int i = 0; i < reader.FieldCount; i++)
                {
                    var value = reader.GetValue(i);
                    rowDict[reader.GetName(i)] = value ?? DBNull.Value;
                }
                rows.Add(rowDict);
            }

            return (rows, schema);
        }

        // --- Helper: สร้าง Command ---
        private static DbCommand CreateCommand(DbConnection conn, string sql, SqlParameter[] parameters)
        {
            var cmd = conn.CreateCommand();
            cmd.CommandText = sql;
            cmd.CommandTimeout = 600;
            cmd.CommandType = CommandType.Text;

            if (parameters?.Length > 0)
            {
                foreach (var p in parameters)
                {
                    cmd.Parameters.Add(p);
                }
            }

            return cmd;
        }

        // --- IDisposable ---
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (_disposed) return;
            if (disposing)
            {
                // ถ้ามี resource เช่น connection pool ฯลฯ จัดการตรงนี้
            }
            _disposed = true;
        }
    }

    // --- Column Info Record ---
    public record ColumnInfo(
        string Name,
        Type DataType,
        int Ordinal,
        bool IsNullable
    );
}
var dbHelper = new DbHelper();

// 1. Execute (INSERT/UPDATE)
await dbHelper.ExecuteAsync("DEFAULT", 
    "INSERT INTO Users (Name) VALUES (@name)", 
    new SqlParameter("@name", "John"));

// 2. Scalar
var count = await dbHelper.ExecuteScalarAsync("DEFAULT", 
    "SELECT COUNT(*) FROM Users");

// 3. Query with schema & data
var (rows, schema) = await dbHelper.QueryAsync("DEFAULT", 
    "SELECT Id, Name, BirthDate FROM Users WHERE Age > @age",
    new SqlParameter("@age", 18));

for (int i = 0; i < rows.Count; i++)
{
    var name = rows[i]["Name"].ToString();
    var birthDateType = schema.First(c => c.Name == "BirthDate").DataType; // → typeof(DateTime)
}

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *