LoginSignup
5
5

More than 5 years have passed since last update.

c# SqlCommandのデバッグ用テキストファイルを出力する

Last updated at Posted at 2015-05-24

いまだにEFとか使いこなせず生のSqlを書きまくっているので、SqlCommandでエラーを吐いた時にSql Server Management Studioでデバッグしたくなったりします。パラメータクエリを利用している場合は、Sql Server Management Studioにパラメータを定義したりするのが辛いため、デバッグ用のsqlをテキストファイルに出力するようにしてみました。不格好ですいません。

同様の悩みの方、いらっしゃいましたら情報共有などよろしくおねがいします。

NuGetで以下してます。
PM> Install-Package IX-MAIN

Program.cs
using System.Data.SqlClient;
namespace Implem.Sample
{
    class Program
    {
        static void Main(string[] args)
        {
            var cmd = new SqlCommand("select [abc].[a], [abc].[b], [abc].[c], [abc].[d], [abc].[e] from [abc] where @p1");
            cmd.Parameters.AddWithValue("p1", "hoge");
            Debugs.WriteCommandTextForDebugging("dbname", cmd);
        }
    }
}
Debugs.cs
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace Implem.Sample
{
    public static class Debugs
    {
        [Conditional("DEBUG")]
        public static void WriteCommandTextForDebugging(string dbName, SqlCommand sqlCommand)
        {
            // SqlCommandデバッグ用テキスト作成
            var commandTextForDebugging = new StringBuilder();
            commandTextForDebugging.Append("use [", dbName, "];\r\n");
            commandTextForDebugging.Append(GetDeclareParametersText(sqlCommand));
            commandTextForDebugging.Append(GetFormattedCommandText(sqlCommand));

            // テキストファイルに書き込み
            WriteFile(commandTextForDebugging.ToString(), @"c:\temp\CommandTextForDebugging.sql");
        }

        private static string GetFormattedCommandText(SqlCommand sqlCommand)
        {
            var commandTextFormatted = new StringBuilder();
            var commandTextTemp = sqlCommand.CommandText;

            // 特定の命令の前に改行を入れて読みやすくする
            commandTextTemp.RegexMatches(
                GetDelimitersForNewLine().Select(o => @"(?<!\n)\b" + o + @"\b").Join("|"),
                RegexOptions.Multiline)
                    .Cast<Match>()
                    .Select(o => o.Value)
                    .Distinct()
                    .ForEach(match =>
                        commandTextTemp = commandTextTemp.Replace(match, "\r\n" + match));

            // 1行あたりのカンマ区切りが3つを超えたら改行を入れて読みやすくする
            commandTextTemp.SplitReturn().ForEach(line =>
                commandTextFormatted.Append(line.Split(',')
                    .Chunk(3)
                    .Select(o => string.Join(",", o))
                    .Join(",\r\n") + "\r\n"));
            return commandTextFormatted.ToString();
        }

        private static string GetDeclareParametersText(SqlCommand sqlCommand)
        {
            var commandParameters = new StringBuilder();

            // パラメータ宣言を作成する
            foreach (SqlParameter parameter in sqlCommand.Parameters)
            {
                commandParameters.Append(
                    "{0, -50}".Params(GetDeclareParameterText(parameter)),
                    "set @", parameter.ParameterName,
                    " = '",
                    parameter.Value.ToString(),
                    "';\r\n");
            }
            return commandParameters.ToString();
        }

        private static string GetDeclareParameterText(SqlParameter parameter)
        {
            if (parameter.Size == 0)
            {
                return "declare @{0} {1}; ".Params(
                    parameter.ParameterName,
                    parameter.SqlDbType.ToString().ToLower());
            }
            else
            {
                return "declare @{0} {1}({2}); ".Params(
                    parameter.ParameterName,
                    parameter.SqlDbType.ToString().ToLower(),
                    parameter.Size);
            }
        }

        private static string[] GetDelimitersForNewLine()
        {
            // ここに列挙したキーワードの手前で改行(好みで追加/削除)
            return new string[]
            {
                "begin", "commit", "select", "insert into", "values", "update", "delete",
                "from", "inner join", "left outer join", "where","order by"
            };
        }

        private static IEnumerable<IEnumerable<T>> Chunk<T>(
            this IEnumerable<T> ienumerable, int size)
        {
            while (ienumerable.Any())
            {
                yield return ienumerable.Take(size);
                ienumerable = ienumerable.Skip(size);
            }
        }

        public static void WriteFile(string str, string path, string encoding = "utf-8")
        {
            using (var writer = new StreamWriter(path, false, Encoding.GetEncoding(encoding)))
            {
                writer.Write(str);
            }
        }

        private static string Join(this IEnumerable<string> list, string delimiter = ",")
        {
            return string.Join(delimiter, list.ToArray());
        }

        private static MatchCollection RegexMatches(
            this string str, string pattern, RegexOptions regexOptions)
        {
            return Regex.Matches(str, pattern, regexOptions);
        }

        private static void Append(this StringBuilder stringBuilder, params string[] strings)
        {
            strings.ForEach(str => stringBuilder.Append(str));
        }

        private static string Params(this string format, params object[] args)
        {
            return string.Format(format, args);
        }

        private static string[] SplitReturn(this string str)
        {
            return str.Replace("\r\n", "\n").Split('\n');
        }
    }
}
CommandTextForDebugging.sql
use [dbname];
declare @p1 nvarchar(4);                          set @p1 = 'hoge';

select [abc].[a], [abc].[b], [abc].[c],
 [abc].[d], [abc].[e] 
from [abc] 
where @p1
5
5
2

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
5
5