using HotelPms.Share.IO; using Npgsql; using System.Data; using System.Data.Common; namespace HotelPms.Share.Data; public class PostgreSqlNet : DataAccess { #region ★★★★★ Declartions ★★★★★ public string ConnectionString { get; set; } = string.Empty; /// ユーザー public override string UseID { get; set; } = string.Empty; /// パスワード public override string PassWord { get; set; } = string.Empty; /// データベース所在のパソコン名 public override string DataSource { get; set; } = string.Empty; /// データベース名 public override string Catalog { get; set; } = string.Empty; public override string Schema { get; set; } = "public"; public override int Port { get; set; } = 5432; /// /// デッドロック関連: 1205,3635,5231,5252,17888,22840,23424 /// public int Message_id { get; set; } = 0; public string SqlState { get; set; } = string.Empty; /// エラー情報 public override string ErrInfo { get; set; } = string.Empty; /// エラーコード public override int ErrNo { get; set; } = 0; public bool PoolEnabled { get; set; } = false; public int MaxPoolSize { get; set; } = 1000; public int RetryCount { get; set; } = 12; public int RetryInterval { get; set; } = 5; /// /// タイマー単位のSQL /// public bool IsTimerAccess { get; set; } = false; public bool FireInfoMessageEventOnUserErrors { get; set; } = false; public bool MessageEventEnabled { get; set; } = false; #endregion public PostgreSqlNet(string connectionString) { ConnectionString = connectionString; NpgsqlConnectionStringBuilder ssb = new NpgsqlConnectionStringBuilder(connectionString); DataSource = ssb.Host; Port = ssb.Port; UseID = ssb.Username; PassWord = ssb.Password; Catalog = ssb.Database; MaxPoolSize = ssb.MaxPoolSize; } public PostgreSqlNet(DBConnectItem connecitem) : this(connecitem.HostName, connecitem.UserID, connecitem.Password, connecitem.Port, connecitem.DBName) { } public PostgreSqlNet(string dataSource, string userID, string passWord) : this(dataSource, userID, passWord, 5432, "postgres") { } public PostgreSqlNet(string dataSource, string userID, string passWord, int port) : this(dataSource, userID, passWord, port, "postgres") { } public PostgreSqlNet(string dataSource, string userID, string passWord, int port, string catalog) { DataSource = dataSource; Port = port; UseID = userID; PassWord = passWord; Catalog = catalog; ResetConnectionString(); } public override void Dispose() { } public void ResetConnectionString() { NpgsqlConnectionStringBuilder ssb = new NpgsqlConnectionStringBuilder(); ssb.Host = DataSource; ssb.Port = Port; ssb.Username = UseID; ssb.Password = PassWord; ssb.Database = Catalog; ssb.Enlist = true; //ssb.MaxPoolSize = MaxPoolSize; ssb.ApplicationName = System.IO.Path.GetFileNameWithoutExtension(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName); ssb.Encoding = "UTF8"; //ssb.ApplicationName = System.IO.Path.GetFileNameWithoutExtension(Application.ExecutablePath); ConnectionString = ssb.ToString(); } public static bool CreateDataBase(DBConnectItem item) { using (PostgreSqlNet dbAccess = new PostgreSqlNet(item.HostName, item.UserID, item.Password, item.Port)) { if (dbAccess.ErrNo != 0) { return false; } return (dbAccess.ExecuteNonQuery($"CREATE DATABASE \"{item.DBName}\" ENCODING 'UTF8'") != -1); } } /// 可変戻り値 /// /// /// /// /// public override T GetDataReader(string sql, Action Fetch) { OperationLog.Instance.WriteLog(string.Format("MsSqlNet.GetDataReader({0})", sql), GetLogLevel()); for (int i = 0; i < RetryCount; i++) { if (i > 0) { OperationLog.Instance.WriteLog(string.Format("{1}⇒MsSqlNet.GetDataReader({0})", sql, i), OperationLog.LogLevelType.Normal); } try { ErrClear(); T t = new T(); using (NpgsqlConnection dbConnect = Open()) { using (NpgsqlCommand command = new NpgsqlCommand(sql, dbConnect)) { command.CommandTimeout = 6000; using (NpgsqlDataReader reader = command.ExecuteReader(CommandBehavior.Default)) { Fetch(reader, t); } //自動クロス } } return t; } catch (Exception ex) { if (!IsTransient(ex, sql)) { return default; } } Thread.Sleep(RetryInterval * 1000); } return default; } public override DataSet GetDataSet(string sql) { throw new NotImplementedException(); } public override DataSet GetDataSetWithTran(string sql) { throw new NotImplementedException(); } public override DataTable GetDataTable(string sql) { OperationLog.Instance.WriteLog(string.Format("GetDataTable({0})", sql), GetLogLevel()); for (int i = 0; i < RetryCount; i++) { if (i > 0) { OperationLog.Instance.WriteLog(string.Format("{1}⇒GetDataTable({0})", sql, i), OperationLog.LogLevelType.Normal); } try { ErrClear(); using (NpgsqlConnection dbConnect = Open()) { using (NpgsqlCommand command = new NpgsqlCommand(sql, dbConnect)) { command.CommandTimeout = 60000; DataTable dataTable = new DataTable(); using (NpgsqlDataAdapter adapter = new NpgsqlDataAdapter()) { adapter.SelectCommand = command; adapter.Fill(dataTable); } return dataTable; } } } catch (Exception ex) { if (!IsTransient(ex, sql)) { return null; } } Thread.Sleep(RetryInterval * 1000); } return null; } public override object ExecuteScalar(string sql) { OperationLog.Instance.WriteLog($"ExecuteScalar({sql})", GetLogLevel()); for (int i = 0; i < RetryCount; i++) { if (i > 0) { OperationLog.Instance.WriteLog($"{i}⇒ExecuteScalar({sql})", OperationLog.LogLevelType.Normal); } try { ErrClear(); using (NpgsqlConnection dbConnect = Open()) { using (NpgsqlCommand command = new NpgsqlCommand(sql, dbConnect)) { return command.ExecuteReader(); } } } catch (Exception ex) { if (!IsTransient(ex, sql)) { return null; } } Thread.Sleep(RetryInterval * 1000); } return null; } public override int ExecuteGetID(string sql) { throw new NotImplementedException(); } public override int ExecuteNonQuery(string sql) { OperationLog.Instance.WriteLog($"ExecuteNonQuery({sql})", GetLogLevel()); for (int i = 0; i < RetryCount; i++) { if (i > 0) { OperationLog.Instance.WriteLog($"{i}⇒ExecuteNonQuery({sql})", OperationLog.LogLevelType.Normal); } try { ErrClear(); using (NpgsqlConnection dbConnect = Open()) { using (NpgsqlCommand command = new NpgsqlCommand(sql, dbConnect)) { int row = command.ExecuteNonQuery(); return (row == -1 ? 0 : row); } } } catch (Exception ex) { if (!IsTransient(ex, sql)) { return -1; } } Thread.Sleep(RetryInterval * 1000); } return -1; } public override bool ExecuteNonQueryWithTran(string sql) { OperationLog.Instance.WriteLog($"ExecuteNonQuery({sql})", GetLogLevel()); for (int i = 0; i < RetryCount; i++) { if (i > 0) { OperationLog.Instance.WriteLog($"{i}⇒ExecuteNonQuery({sql})", OperationLog.LogLevelType.Normal); } NpgsqlTransaction tran = null; try { ErrClear(); using (NpgsqlConnection dbConnect = Open()) { tran = dbConnect.BeginTransaction(); using (NpgsqlCommand command = new NpgsqlCommand(sql, dbConnect)) { int row = command.ExecuteNonQuery(); if (row == -1) { tran.Rollback(); return false; } else { tran.Commit(); return true; } } } } catch (Exception ex) { tran?.Rollback(); if (!IsTransient(ex, sql)) { return false; } } Thread.Sleep(RetryInterval * 1000); } return false; } public NpgsqlConnection Open() { NpgsqlConnection dbConnect = new NpgsqlConnection(); dbConnect.ConnectionString = ConnectionString; dbConnect.Open(); return dbConnect; } /// /// 一時的なエラー /// /// /// private bool IsTransient(Exception ex, string sql) { NpgsqlException exSql = null; if (ex is NpgsqlException) { exSql = ex as NpgsqlException; Message_id = exSql.HResult; ErrNo = exSql.ErrorCode; SqlState = exSql.SqlState; } else { ErrNo = -1; } ErrInfo = ex.Message; OperationLog.Instance.WriteLog(string.Format("IsTransient({0},{1},{2},{3})", Message_id, ErrNo, ErrInfo, sql), OperationLog.LogLevelType.Normal); if (exSql != null) { //Specifies whether the exception is considered transient, that is, whether retrying the operation could succeed (e.g. a network error or a timeout). if (exSql.IsTransient) { return true; } if (exSql.SqlState == "40P01") { return true; } //デッドロックの検出 } else if (ex is TimeoutException) { return true; } return false; } private void ErrClear() { ErrNo = 0; ErrInfo = string.Empty; SqlState = string.Empty; Message_id = 0; } private OperationLog.LogLevelType GetLogLevel() { return IsTimerAccess ? OperationLog.LogLevelType.Level8 : OperationLog.LogLevelType.Level7; } /// /// テーブル一覧を取得 /// ※一般的にschema = publicですが /// /// public override DataTable GetTableList() { return GetDataTable($"SELECT \"table_name\" AS \"Name\" FROM \"information_schema\".\"tables\" WHERE \"table_schema\"='{Schema}';"); } /// /// テーブル一覧を取得 /// /// public override DataTable GetTableList(string whereAdding) { return GetDataTable($"SELECT \"table_name\" AS \"Name\" FROM \"information_schema\".\"tables\" WHERE \"table_schema\"='{Schema}' AND {whereAdding};"); } }