Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

C# Dapper で DB 接続クラスを作ってみた (.NET Framework 版)

0
Posted at

はじめに

Dapper で DB 接続クラスを作ってみました。

環境

  • Visual Studio Community 2022
  • .NET Framework 4.8.1
  • Dapper 2.1.72

ソース

小~中規模の想定で機能をひとつにひっくるめています。

DbSession.cs
using System;
using System.Collections.Generic;
using System.Data;
using System.Threading.Tasks;
using Dapper;
using Microsoft.Extensions.Logging;

namespace DapperExampleNet481
{
    /// <summary>
    /// データベースセッションを表すインターフェイスです。
    /// </summary>
    public interface IDbSession : IDisposable, IAsyncDisposable
    {
        /// <summary>
        /// トランザクションを開始します。
        /// </summary>
        void BeginTransaction();

        /// <summary>
        /// トランザクションをコミットします。
        /// </summary>
        void CommitTransaction();

        /// <summary>
        /// トランザクションをロールバックします。
        /// </summary>
        void RollbackTransaction();

        /// <summary>
        /// SQL クエリを実行し、結果を動的型で取得します。
        /// </summary>
        /// <param name="sql">実行する SQL クエリ。</param>
        /// <param name="param">SQL クエリのパラメータ。</param>
        /// <returns>SQL クエリの実行結果を動的型で返します。</returns>
        IEnumerable<dynamic> Get(string sql, object param = null);

        /// <summary>
        /// SQL クエリを非同期で実行し、結果を動的型で取得します。
        /// </summary>
        /// <param name="sql">実行する SQL クエリ。</param>
        /// <param name="param">SQL クエリのパラメータ。</param>
        /// <returns>SQL クエリの実行結果を動的型で返します。</returns>
        Task<IEnumerable<dynamic>> GetAsync(string sql, object param = null);

        /// <summary>
        /// SQL クエリを実行し、結果を指定された型で取得します。
        /// </summary>
        /// <typeparam name="T">取得する型。</typeparam>
        /// <param name="sql">実行する SQL クエリ。</param>
        /// <param name="param">SQL クエリのパラメータ。</param>
        /// <returns>SQL クエリの実行結果を指定された型で返します。</returns>
        IEnumerable<T> Get<T>(string sql, object param = null);

        /// <summary>
        /// SQL クエリを非同期で実行し、結果を指定された型で取得します。
        /// </summary>
        /// <typeparam name="T">取得する型。</typeparam>
        /// <param name="sql">実行する SQL クエリ。</param>
        /// <param name="param">SQL クエリのパラメータ。</param>
        /// <returns>SQL クエリの実行結果を指定された型で返します。</returns>
        Task<IEnumerable<T>> GetAsync<T>(string sql, object param = null);

        /// <summary>
        /// SQL クエリを実行し、最初の行を動的型で取得します。
        /// </summary>
        /// <param name="sql">実行する SQL クエリ。</param>
        /// <param name="param">SQL クエリのパラメータ。</param>
        /// <returns>SQL クエリの実行結果の最初の行を動的型で返します。</returns>
        dynamic GetFirstOrDefault(string sql, object param = null);

        /// <summary>
        /// SQL クエリを非同期で実行し、最初の行を動的型で取得します。
        /// </summary>
        /// <param name="sql">実行する SQL クエリ。</param>
        /// <param name="param">SQL クエリのパラメータ。</param>
        /// <returns>SQL クエリの実行結果の最初の行を動的型で返します。</returns>
        Task<dynamic> GetFirstOrDefaultAsync(string sql, object param = null);

        /// <summary>
        /// SQL クエリを実行し、最初の行を指定された型で取得します。
        /// </summary>
        /// <typeparam name="T">取得する型。</typeparam>
        /// <param name="sql">実行する SQL クエリ。</param>
        /// <param name="param">SQL クエリのパラメータ。</param>
        /// <returns>SQL クエリの実行結果の最初の行を指定された型で返します。</returns>
        T GetFirstOrDefault<T>(string sql, object param = null);

        /// <summary>
        /// SQL クエリを非同期で実行し、最初の行を指定された型で取得します。
        /// </summary>
        /// <typeparam name="T">取得する型。</typeparam>
        /// <param name="sql">実行する SQL クエリ。</param>
        /// <param name="param">SQL クエリのパラメータ。</param>
        /// <returns>SQL クエリの実行結果の最初の行を指定された型で返します。</returns>
        Task<T> GetFirstOrDefaultAsync<T>(string sql, object param = null);

        /// <summary>
        /// SQL コマンドを実行し、処理した行数を返します。
        /// </summary>
        /// <param name="sql">実行する SQL コマンド。</param>
        /// <param name="param">SQL コマンドのパラメータ。</param>
        /// <returns>処理した行数を返します。</returns>
        int Execute(string sql, object param = null);

        /// <summary>
        /// SQL コマンドを非同期で実行し、処理した行数を返します。
        /// </summary>
        /// <param name="sql">実行する SQL コマンド。</param>
        /// <param name="param">SQL コマンドのパラメータ。</param>
        /// <returns>処理した行数を返します。</returns>
        Task<int> ExecuteAsync(string sql, object param = null);
    }

    /// <summary>
    ///データベースセッションを表すクラスです。
    /// </summary>
    public class DbSession : IDbSession
    {
        private static readonly string s_exceptionMessage = "Exception occurred. SQL: {Sql} Params: {@Params}";
        private static readonly string s_sqlExecutingMessage = "Executing SQL. SQL: {Sql} Params: {@Params}";
        private static readonly string s_sqlCompletedMessage = "SQL execution completed. Result: {Result} SQL: {Sql} Params: {@Params}";

        private readonly IDbConnection _connection;
        private readonly int _commandTimeout;
        private readonly ILogger<DbSession> _logger;
        private IDbTransaction _transaction;

        /// <summary>
        /// DbSession クラスの新しいインスタンスを初期化します。
        /// </summary>
        /// <param name="connection">データベース接続オブジェクト。</param>
        /// <param name="commandTimeout">コマンドのタイムアウト時間(秒)。</param>
        /// <param name="providerName">データベースプロバイダーの名前。</param>
        /// <param name="logger">ロガーオブジェクト。</param>
        public DbSession(
            IDbConnection connection,
            int commandTimeout,
            ILogger<DbSession> logger = null)
        {
            _connection = connection;
            _commandTimeout = commandTimeout;
            _logger = logger;
        }

        /// <inheritdoc />
        public void BeginTransaction()
        {
            try
            {
                // もし接続されていない状態でトランザクションを開始した場合、
                // 以下の例外が発生する。
                // トランザクションを開始する前にデータベース接続を開いておく。
                // ------------------------------------------------------------
                // System.InvalidOperationException:
                //     '操作が無効です。接続は閉じています。'
                // ------------------------------------------------------------
                if (_connection.State != ConnectionState.Open)
                {
                    _connection.Open();
                }

                // サブトランザクション(ネストされたトランザクション)を防ぐため、
                // トランザクションが既に存在する場合は、新しいトランザクションを開始しない。
                // サブトランザクションはデータベース毎に仕様が異なること、
                // またトランザクションの管理自体が煩雑になるため、禁止とする。
                if (_transaction == null)
                {
                    _transaction = _connection.BeginTransaction();
                }
            }
            catch (Exception ex)
            {
                LogException(ex);
                throw;
            }
        }

        /// <inheritdoc />
        public void CommitTransaction()
        {
            try
            {
                if (_transaction != null)
                {
                    _transaction.Commit();
                    _transaction.Dispose();
                    _transaction = null;
                }
            }
            catch (Exception ex)
            {
                LogException(ex);
                throw;
            }
        }

        /// <inheritdoc />
        public void RollbackTransaction()
        {
            try
            {
                if (_transaction != null)
                {
                    _transaction.Rollback();
                    _transaction.Dispose();
                    _transaction = null;
                }
            }
            catch (Exception ex)
            {
                LogException(ex);
                throw;
            }
        }

        /// <inheritdoc />
        public IEnumerable<dynamic> Get(string sql, object param = null)
        {
            try
            {
                // 接続が Closed の場合、内部で自動的に Open される。
                return _connection.Query(
                    sql: sql,
                    param: param,
                    transaction: _transaction,
                    commandTimeout: _commandTimeout);
            }
            catch (Exception ex)
            {
                LogException(ex, sql, param);
                throw;
            }
        }

        /// <inheritdoc />
        public async Task<IEnumerable<dynamic>> GetAsync(string sql, object param = null)
        {
            try
            {
                // 接続が Closed の場合、内部で自動的に Open される。
                return await _connection
                    .QueryAsync(
                        sql: sql,
                        param: param,
                        transaction: _transaction,
                        commandTimeout: _commandTimeout)
                    .ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                LogException(ex, sql, param);
                throw;
            }
        }

        /// <inheritdoc />
        public IEnumerable<T> Get<T>(string sql, object param = null)
        {
            try
            {
                // 接続が Closed の場合、内部で自動的に Open される。
                return _connection.Query<T>(
                    sql: sql,
                    param: param,
                    transaction: _transaction,
                    commandTimeout: _commandTimeout);
            }
            catch (Exception ex)
            {
                LogException(ex, sql, param);
                throw;
            }
        }

        /// <inheritdoc />
        public async Task<IEnumerable<T>> GetAsync<T>(string sql, object param = null)
        {
            try
            {
                // 接続が Closed の場合、内部で自動的に Open される。
                return await _connection
                    .QueryAsync<T>(
                        sql: sql,
                        param: param,
                        transaction: _transaction,
                        commandTimeout: _commandTimeout)
                    .ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                LogException(ex, sql, param);
                throw;
            }
        }

        /// <inheritdoc />
        public dynamic GetFirstOrDefault(string sql, object param = null)
        {
            try
            {
                // 接続が Closed の場合、内部で自動的に Open される。
                return _connection.QueryFirstOrDefault(
                    sql: sql,
                    param: param,
                    transaction: _transaction,
                    commandTimeout: _commandTimeout);
            }
            catch (Exception ex)
            {
                LogException(ex, sql, param);
                throw;
            }
        }

        /// <inheritdoc />
        public async Task<dynamic> GetFirstOrDefaultAsync(string sql, object param = null)
        {
            try
            {
                // 接続が Closed の場合、内部で自動的に Open される。
                return await _connection
                    .QueryFirstOrDefaultAsync(
                        sql: sql,
                        param: param,
                        transaction: _transaction,
                        commandTimeout: _commandTimeout)
                    .ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                LogException(ex, sql, param);
                throw;
            }
        }

        /// <inheritdoc />
        public T GetFirstOrDefault<T>(string sql, object param = null)
        {
            try
            {
                // 接続が Closed の場合、内部で自動的に Open される。
                return _connection.QueryFirstOrDefault<T>(
                    sql: sql,
                    param: param,
                    transaction: _transaction,
                    commandTimeout: _commandTimeout);
            }
            catch (Exception ex)
            {
                LogException(ex, sql, param);
                throw;
            }
        }

        /// <inheritdoc />
        public async Task<T> GetFirstOrDefaultAsync<T>(string sql, object param = null)
        {
            try
            {
                // 接続が Closed の場合、内部で自動的に Open される。
                return await _connection
                    .QueryFirstOrDefaultAsync<T>(
                        sql: sql,
                        param: param,
                        transaction: _transaction,
                        commandTimeout: _commandTimeout)
                    .ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                LogException(ex, sql, param);
                throw;
            }
        }

        /// <inheritdoc />
        public int Execute(string sql, object param = null)
        {
            try
            {
                LogSqlExecuting(sql, param);

                // 接続が Closed の場合、内部で自動的に Open される。
                var result = _connection.Execute(
                    sql: sql,
                    param: param,
                    transaction: _transaction,
                    commandTimeout: _commandTimeout);

                LogSqlCompleted(result, sql, param);

                return result;
            }
            catch (Exception ex)
            {
                LogException(ex, sql, param);
                throw;
            }
        }

        /// <inheritdoc />
        public async Task<int> ExecuteAsync(string sql, object param = null)
        {
            try
            {
                LogSqlExecuting(sql, param);

                // 接続が Closed の場合、内部で自動的に Open される。
                var result = await _connection
                    .ExecuteAsync(
                        sql: sql,
                        param: param,
                        transaction: _transaction,
                        commandTimeout: _commandTimeout)
                    .ConfigureAwait(false);

                LogSqlCompleted(result, sql, param);

                return result;
            }
            catch (Exception ex)
            {
                LogException(ex, sql, param);
                throw;
            }
        }

        /// <inheritdoc />
        public void Dispose()
        {
            try
            {
                // トランザクションが残っている場合、ロールバックして破棄する。
                if (_transaction != null)
                {
                    _transaction.Rollback();
                    _transaction.Dispose();
                    _transaction = null;
                }

                // 接続を破棄する。
                _connection.Dispose();
            }
            catch (Exception ex)
            {
                // 例外ログを出力する。
                LogException(ex);

                // Dispose メソッド内で例外が発生しても throw しない。
                // throw;
            }
            finally
            {
                GC.SuppressFinalize(this);
            }
        }

        /// <inheritdoc />
        public async ValueTask DisposeAsync()
        {
            try
            {
                // トランザクションが残っている場合、ロールバックして破棄する。
                if (_transaction != null)
                {
                    _transaction.Rollback();
                    _transaction.Dispose();
                    _transaction = null;
                }

                // 接続を破棄する。
                if (_connection is IAsyncDisposable asyncConnection)
                {
                    await asyncConnection.DisposeAsync().ConfigureAwait(false);
                }
                else
                {
                    _connection.Dispose();
                }
            }
            catch (Exception ex)
            {
                // 例外ログを出力する。
                LogException(ex);

                // Dispose メソッド内で例外が発生しても throw しない。
                // throw;
            }
            finally
            {
                GC.SuppressFinalize(this);
            }
        }

        /// <summary>
        /// 例外ログを出力します。
        /// </summary>
        private void LogException(Exception ex, string sql = null, object param = null)
        {
            try
            {
                if (_logger == null)
                {
                    return;
                }

                _logger.LogError(ex, s_exceptionMessage, sql, param);
            }
            catch
            {
                // ログ出力に失敗しても処理を継続する。
                // throw;
            }
        }

        /// <summary>
        /// SQL 実行ログを出力します。
        /// </summary>
        private void LogSqlExecuting(string sql, object param = null)
        {
            try
            {
                if (_logger == null)
                {
                    return;
                }

                _logger.LogTrace(s_sqlExecutingMessage, sql, param);
            }
            catch
            {
                // ログ出力に失敗しても処理を継続する。
                // throw;
            }
        }

        /// <summary>
        /// SQL 完了ログを出力します。
        /// </summary>
        private void LogSqlCompleted(int result, string sql, object param = null)
        {
            try
            {
                if (_logger == null)
                {
                    return;
                }

                _logger.LogTrace(s_sqlCompletedMessage, result, sql, param);
            }
            catch
            {
                // ログ出力に失敗しても処理を継続する。
                // throw;
            }
        }
    }
}

おわりに

細かく動作確認はしていません。

0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?