using Npgsql; using DocumentFormat.OpenXml.Drawing; using DocumentFormat.OpenXml.Office.Word; using DocumentFormat.OpenXml.Spreadsheet; using DocumentFormat.OpenXml.Wordprocessing; using KssSmaPlaLib.Commons; using Serilog; using System; using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Data.Entity; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using static KssSmaPlaLib.IO.Database.DbUxer; using Path = System.IO.Path; namespace KssSmaPlaLib.IO.Database { /// /// DBアクセスアシスタント /// public class DbUxer : IDisposable { public enum DbProvider { PostgreSQL, SQLServer, MySQL, Oracle, SQLite, } /// /// プロバイダ名ディクショナリ /// private static readonly Dictionary _providerNameDic = new Dictionary() { { DbProvider.PostgreSQL, "Npgsql" }, { DbProvider.SQLServer, "System.Data.SqlClient" }, { DbProvider.MySQL, "MySql.Data.MySqlClient" }, { DbProvider.Oracle, "Oracle.ManagedDataAccess.Client" }, { DbProvider.SQLite, "System.Data.SQLite" }, }; /// /// プロバイダ名(文字列)からenum値に変更 /// /// プロバイダ名(文字列) /// enum値(該当なし時はnull) public static DbProvider? GetDbProviderEnumFromString(string dbProviderName = "PostgreSQL") { switch(dbProviderName.ToLower().Replace(" ","")) { case "postgresql": case "ポスグレ": return DbProvider.PostgreSQL; case "sqlserver": return DbProvider.SQLServer; case "mysql": return DbProvider.MySQL; case "oracle": case "oracledatabase": return DbProvider.Oracle; case "sqlite": case "sqlite3": return DbProvider.SQLite; default: return null; } } /// /// コミット済みフラグ /// private bool _isCommitted = false; /// /// SQLディクショナリ /// private static readonly Dictionary _sqlDic = new Dictionary(); /// /// 指定フォルダからSQLファイルを読み込む(ディクショナリ保存) /// /// フォルダパス /// 成否 public static bool TryLoadSqlFromText(string folderPath, out string resultMessage) { resultMessage = string.Empty; var fileCount = 0; List files = new List(); try { if (string.IsNullOrEmpty(folderPath)) { resultMessage = "フォルダパスが指定されていません。"; return false; } if (!Directory.Exists(folderPath)) { resultMessage = $"フォルダが存在しません。[フォルダパス:{folderPath}]"; return false; } _sqlDic.Clear(); foreach (var file in Directory.GetFiles(folderPath, "*.sql.txt")) { _sqlDic.Add(Path.GetFileNameWithoutExtension(file).ToLower().Replace(".sql", ""), System.IO.File.ReadAllText(file)); files.Add(Path.GetFileNameWithoutExtension(file).Replace(".sql", "")); fileCount++; } resultMessage = $"SQLファイルをロードしました。[ファイル数:{fileCount},ファイル:{string.Join(",", files)}]"; return true; } catch (Exception ex) { resultMessage = $"SQLファイル読み込み時にエラーが発生しました。[エラー内容:{ex.Message}]"; return false; } } public static bool TestConnectByDic(Dictionary iniDic, out string resultMessage) { const string KEY_DB_PROVIDER_NAME = "DbProviderName"; const string KEY_DB_CONNECTION_STRING = "DbConnectionString"; var dbProviderName = DictionaryUxer.GetValueOrDefault(iniDic, KEY_DB_PROVIDER_NAME, string.Empty); var connectionString = DictionaryUxer.GetValueOrDefault(iniDic, KEY_DB_CONNECTION_STRING, string.Empty); return TestConnect(dbProviderName, connectionString, out resultMessage); } /// /// テスト接続 /// /// 成否 public static bool TestConnect(string dbPrividerName, string dbConnectionString, out string resultMessage) { // パラメータチェック&DBプロバイダ取得 if (!TryConnectParameter(dbPrividerName, dbConnectionString, out DbProvider? _, out resultMessage)) { return false; } if (!TryCreate(dbPrividerName, dbConnectionString, out var dbUxer, out resultMessage, false)) { return false; } dbUxer.Dispose(); return true; } /// /// DB接続 /// private DbConnection _con = null; /// /// トランザクション /// private DbTransaction _tran = null; public static bool TryCreateByDic(Dictionary iniDic, out DbUxer dbUxer, out string resultMessage, bool useTransaction = false) { const string KEY_DB_PROVIDER_NAME = "DbProviderName"; const string KEY_DB_CONNECTION_STRING = "DbConnectionString"; var dbProviderName = DictionaryUxer.GetValueOrDefault(iniDic, KEY_DB_PROVIDER_NAME, string.Empty); var connectionString = DictionaryUxer.GetValueOrDefault(iniDic, KEY_DB_CONNECTION_STRING, string.Empty); return TryCreate(dbProviderName, connectionString, out dbUxer, out resultMessage, useTransaction); } /// /// 生成挑戦 /// /// /// /// /// /// /// public static bool TryCreate(string dbPrividerName, string dbConnectionString, out DbUxer dbUxer, out string resultMessage, bool useTransaction = false) { dbUxer = null; // パラメータチェック&DBプロバイダ取得 if (!TryConnectParameter(dbPrividerName, dbConnectionString, out DbProvider? dbp, out resultMessage)) { return false; } DbProvider dbpp = (DbProvider)dbp; try { // 生成 dbUxer = new DbUxer(dbpp, dbConnectionString, useTransaction); return true; } catch (Exception ex) { // 接続NG回答 resultMessage = $"DB接続に失敗しました。[エラー内容:{ex.Message}]"; return false; } } private static bool TryConnectParameter(string dbPrividerName, string dbConnectionString, out DbProvider? dbp, out string resultMessage) { dbp = DbProvider.PostgreSQL; resultMessage = string.Empty; if (string.IsNullOrEmpty(dbPrividerName)) { // DBプロバイダ指定なしの場合、デフォルトはポスグレとする // Infoメッセージ resultMessage = "DBプロバイダ名が未指定のため、デフォルトのPostgreSQLを使用します。"; } if (string.IsNullOrEmpty(dbConnectionString)) { resultMessage = "接続文字列が設定されていません。"; return false; } if (!string.IsNullOrEmpty(dbPrividerName)) { dbp = GetDbProviderEnumFromString(dbPrividerName); } if (dbp == null) { resultMessage = $"DBプロバイダ名が不正です。[DBプロバイダ名:{dbPrividerName}]"; return false; } return true; } /// /// コンストラクタ(DB接続) /// /// DBプロバイダ(enum値) /// 接続文字列 /// トランザクション有無 private DbUxer(DbProvider dbProvider, string connectionString, bool useTransaction = false) { try { // DBプロバイダファクトリー作成 DbProviderFactory factory; if (dbProvider == DbProvider.PostgreSQL) { // ポスグレの場合、Npgsqlのインスタンをを直接取得 factory = Npgsql.NpgsqlFactory.Instance; } else { factory = DbProviderFactories.GetFactory(_providerNameDic[dbProvider]); } // DB接続作成 _con = factory.CreateConnection(); // 接続文字列設定 _con.ConnectionString = connectionString; // 接続 _con.Open(); // トランザクション if (useTransaction) _tran = _con.BeginTransaction(); } catch (Exception ex) { LoggerUxer.Error($"DB接続に失敗しました。[プロバイダ:{dbProvider},接続文字列:{connectionString},エラー内容:{ex.Message}]"); throw ex; } } public bool ExecuteNonQuery(string sqlOrName, Dictionary parameters, out int resultCount, out string resultMessage) { resultCount = 0; resultMessage = string.Empty; if (_con == null || _con.State != ConnectionState.Open) { resultMessage = "DBと接続されていません。"; return false; } try { using (var cmd = _con.CreateCommand()) { // コパンドプロパティを注入 if (!TryInjectSoulToCmd(cmd, sqlOrName, parameters, out var messageForMe)) { resultMessage = messageForMe; return false; } resultCount = cmd.ExecuteNonQuery(); LoggerUxer.Info($"[Rec Count:{resultCount}]"); return true; } } catch (Exception ex) { resultMessage = $"SQL実行でエラーが発生しました。[SQL名:{sqlOrName},エラー内容:{ex.Message}]"; return false; } } public bool BeginTextImport( string sqlOrName, string csvFilePath, out int resultCount, out string resultMessage) { resultCount = 0; resultMessage = string.Empty; if (_con == null || _con.State != ConnectionState.Open) { resultMessage = "DBと接続されていません。"; return false; } try { // SQLの取得 sqlOrName = sqlOrName.ToLower(); var sql = _sqlDic.ContainsKey(sqlOrName) ? _sqlDic[sqlOrName] : sqlOrName; // CSV有無 if (!System.IO.File.Exists(csvFilePath)) { resultMessage = $"CSVファイルが存在しません。[ファイルパス:{csvFilePath}]"; return false; } // バルクインサート用のメソッドはポスグレの接続しか持たない var pgConn = (NpgsqlConnection)_con; var sqls = sql.Split(new[] { "-- SPLIT --" }, StringSplitOptions.RemoveEmptyEntries); if (sqls.Length != 3) { resultMessage = $"バルク挿入SQLで分割数が正しくありません。[SQLファイル名:{sqlOrName},分割数:{sqls.Length}]"; return false; } // 1個目のSQL(一時テーブル作成) if (!ExecuteNonQuery(sqls[0], new Dictionary(), out var _, out var messageForMe)) { resultMessage = $"バルク挿入SQL(1)が失敗しました。[詳細:{messageForMe}]"; return false; } // 2個目のSQL(一時テーブルにバルク挿入) using (var writer = pgConn.BeginTextImport(sqls[1])) { using (var reader = new StreamReader(csvFilePath)) { string line; while ((line = reader.ReadLine()) != null) { writer.WriteLine(line); //resultCount++; // ここで「1件、2件…」と数える!♨️ } } } // これで完了!1000件なら「シュンッ!」で終わります // 3個目のSQL(一時テーブルから対象テーブルへコピー) if (!ExecuteNonQuery(sqls[2], new Dictionary(), out resultCount, out messageForMe)) { resultMessage = $"バルク挿入SQL(2)が失敗しました。[詳細:{messageForMe}]"; return false; } LoggerUxer.Info($"[Rec Count:{resultCount}]"); return true; } catch (Exception ex) { resultMessage = $"SQL実行でエラーが発生しました。[SQL名:{sqlOrName},エラー内容:{ex.Message}]"; return false; } } public bool ExecuteQuery(string sqlOrName, Dictionary parameters, out List> recordSet, out string resultMessage) { recordSet = new List>(); resultMessage = string.Empty; if (_con == null || _con.State != ConnectionState.Open) { resultMessage = "DBと接続されていません。"; return false; } try { using (var cmd = _con.CreateCommand()) { // コパンドプロパティを注入 if (!TryInjectSoulToCmd(cmd, sqlOrName, parameters, out var messageForMe)) { resultMessage = messageForMe; return false; } using (var reader = cmd.ExecuteReader()) { while (reader.Read()) { var row = new Dictionary(); for (int i = 0; i < reader.FieldCount; i++) { row[reader.GetName(i)] = reader.GetValue(i); } recordSet.Add(row); } LoggerUxer.Info($"[Rec Count:{recordSet.Count}]"); } } return true; } catch (Exception ex) { resultMessage = $"DBからデータ取得時にエラーが発生しました。[SQL名:{sqlOrName},エラー内容:{ex.Message}]"; return false; } } private bool TryInjectSoulToCmd(DbCommand cmd, string sqlOrName, Dictionary parameters, out string resultMessage) { resultMessage = string.Empty; // SQLの取得 sqlOrName = sqlOrName.ToLower(); var sql = _sqlDic.ContainsKey(sqlOrName) ? _sqlDic[sqlOrName] : sqlOrName; //LoggerUxer.Info($"[SQL:{sql}]"); // SQL中のコマンドパラメータ名取得 var paraList = ExtractSqlParams(sql); //oggerUxer.Info($"[SQL Para names:{string.Join(",", paraList)}]"); // コマンドパラメータ値の型変換 if (!TryChangeParamTypeFromSqlParams(ref parameters, ref paraList, out var messageForMe)) { resultMessage = $"コマンドパラメータ値の型変換に失敗しました。[エラー内容:{messageForMe}]"; return false; } // SQL文中のコマンドパラメータ型定義をカット cmd.CommandText = sql = CutParamTypeOfSQL(sql); //LoggerUxer.Info($"[SQL(Param Type Cut):{sql}]"); // 再度、SQL中のコマンドパラメータ名取得 paraList = ExtractSqlParams(sql); //LoggerUxer.Info($"[SQL Para names(Type cut):{string.Join(",", paraList)}]"); // SQLに合わせたコマンドパラメータ登録 foreach (var p in parameters) { if (!paraList.Contains(p.Key)) continue; var param = cmd.CreateParameter(); param.ParameterName = p.Key; param.Value = p.Value; cmd.Parameters.Add(param); } //LoggerUxer.Info($"[SQL Params:{GetCommandParametersCsv(cmd)}]"); if (cmd.Parameters.Count != paraList.Count) { resultMessage = $"必要なパラメータが不足しています。[SQL名:{sqlOrName},SQL項目:{string.Join(",", paraList)}, CSV項目:{string.Join(",", parameters.Keys)}]"; return false; } return true; } public void Commit() { _tran?.Commit(); _isCommitted = true; } /// /// 終了処理 /// /// public void Dispose() { if (_tran != null && !_isCommitted) { Log.Warning("トランザクション未コミット → 自動ロールバックされました"); _tran.Rollback(); // 明示的にロールバック } _con?.Dispose(); _con = null; _tran = null; } public static List ExtractSqlParams(string sql) { //var matches = System.Text.RegularExpressions.Regex.Matches(sql, @":([a-zA-Z_][a-zA-Z0-9_]*)"); //return matches.Cast() // .Select(m => m.Groups[1].Value) // .Distinct() // .ToList(); //var result = new List(); //var lines = sql.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None); //foreach (var line in lines) //{ // // コメント部分を除去 // var codePart = line.Contains("--") ? line.Substring(0, line.IndexOf("--")) : line; // // パラメータ抽出 // var matches = System.Text.RegularExpressions.Regex.Matches( // codePart, // @":(\[[a-zA-Z]\][a-zA-Z_][a-zA-Z0-9_]*)|:([a-zA-Z_][a-zA-Z0-9_]*)" // ); // foreach (System.Text.RegularExpressions.Match match in matches) // { // var paramName = match.Groups[1].Success ? match.Groups[1].Value : match.Groups[2].Value; // if (!result.Contains(paramName)) // result.Add(paramName); // } //} //return result; var result = new List(); var lines = sql.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None); foreach (var line in lines) { // コメント部分を除去 var codePart = line.Contains("--") ? line.Substring(0, line.IndexOf("--")) : line; // パラメータ抽出(::で始まるものは除外) var matches = System.Text.RegularExpressions.Regex.Matches( codePart, @"(?(); foreach (DbParameter param in command.Parameters) { string name = param.ParameterName?.TrimStart('@'); // @を除去(任意) object value = param.Value; string formattedValue; if (value == null || value == DBNull.Value) { formattedValue = "NULL"; } else if (value is string || value is char) { formattedValue = $"'{value}'"; // シングルクォートで括る } else if (value is DateTime dt) { formattedValue = $"'{dt:yyyy-MM-dd HH:mm:ss}'"; // 日付も文字列扱い } else { formattedValue = value.ToString(); } list.Add($"{name}={formattedValue}"); } return string.Join(",", list); } private static bool TryChangeParamTypeFromSqlParams(ref Dictionary parameters, ref List paraNameList, out string resultMessage) { resultMessage = string.Empty; foreach (var key in parameters.Keys.ToList()) { var name = paraNameList .FirstOrDefault(m => m == key || m.Substring(0, 1) == "[" && m.Substring(3) == key); if (name != null && name.Substring(0, 1) == "[") { switch (name.Substring(0, 3).ToUpper()) { case "[I]": if (string.IsNullOrEmpty(parameters[key].ToString())) { parameters[key] = DBNull.Value; } else if (int.TryParse(parameters[key].ToString(), out int intValue)) { parameters[key] = intValue; } else { resultMessage = $"パラメータ値[I]型変換に失敗しました。[キー:{key}値:{parameters[key]}]"; return false; } break; //case "[IN": // if (string.IsNullOrEmpty(parameters[key].ToString())) // { // parameters[key] = DBNull.Value; // } // else if (int.TryParse(parameters[key].ToString(), out intValue)) // { // parameters[key] = intValue; // } // else // { // resultMessage = $"パラメータ値[IN]型変換に失敗しました。[キー:{key}値:{parameters[key]}]"; // return false; // } // break; case "[F]": if (double.TryParse(parameters[key].ToString(), out double doubleValue)) { parameters[key] = doubleValue; } else { resultMessage = $"パラメータ値[F]型変換に失敗しました。[キー:{key}値:{parameters[key]}]"; return false; } break; case "[D]": if (DateTime.TryParse(parameters[key].ToString(), out DateTime dtValue)) { parameters[key] = dtValue; } else { resultMessage = $"パラメータ値[D]型変換に失敗しました。[キー:{key}値:{parameters[key]}]"; return false; } break; case "[S]": parameters[key] = parameters[key].ToString(); break; } } } return true; } // SQL文中のコマンドパラメータ型定義をカット private static string CutParamTypeOfSQL(string sql) { string[] cutItems = new string[] { "[I]", "[F]", "[D]", "[S]" }; foreach (var cutItem in cutItems) { sql = sql.Replace(cutItem.ToUpper(), ""); sql = sql.Replace(cutItem.ToLower(), ""); } return sql; } } }