C# – SQL Server on Ubuntu 20.04 トランザクションでクエリを制御する方法 No.106

自己投資としてチャレンジしている内容を Blog で公開しています。

今回は C# で .NET Data Provider for SQL Server (SqlClient) を使い SQL Server に接続し、クエリのコミット、ロールバックを行う、トランザクションの実装方法を紹介します。

▼1. トランザクションでクエリを制御する方法

トランザクションを利用したクエリについて、以下の本 blog で Python および Java を使った実現方法を紹介しました。今回は C# で試してみます。

(2023/01 時点)


▼2. 事前準備

Ubuntu 20.4 で Visual Studio Code 開発環境にて C# を使い SQL Server へ接続する環境の構築方法は以下を参考にしてください。


▼3. クエリのコミット、ロールバックを行う、トランザクションの実装

3-1. アプリケーションの作成し、作成したディレクトリで VSCode を起動

dotnet new console -o My2ndApp
cd My2ndApp
code .

3-2. MytestApp.csproj に System.Data.SqlClient を追加し保存 (Ctrl+ S)

SQL Server への接続に必要な .NET Data Provider for SQL Server の名前空間 System.Data.SqlClient を登録します。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="System.Data.SqlClient" Version="4.8.5" />
  </ItemGroup>

</Project>

3-3. 追加した System.Data.SqlClient を反映

dotnet restore

3-4. C# のコード Program.cs 作成

SQL Server には sa のユーザーで接続しています。sa のパスワードは Password で指定します。Database 名は “sampledb1” を指定しています。Transaction を開始し、1 行レコードを入れて commit します。

using System;
using System.Data.SqlClient;
using System.Threading;

namespace ConsoleApp2
{
    class Program
    {
        static void Main(string[] args)
        {
            // Build connection string
            SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();
            builder.DataSource = "localhost";
            builder.UserID = "sa";
            builder.Password = "password";
            builder.InitialCatalog = "sampledb1";
            Boolean success = false;
            int retryCount = 0;
            int maxRetryCount = 4;
            while (!success && retryCount < maxRetryCount)
            {
                try
                {
                    // Connect to SQL
                    Console.Write("Connecting to SQL Server ... \n");
                    Console.WriteLine("Current Timestamps:" + DateTime.UtcNow.ToLocalTime());
                    Console.WriteLine("-----------------------------");
                    using (SqlConnection connection = new SqlConnection(builder.ConnectionString))
                    {
                        connection.Open();
                        Console.WriteLine("Done.");

                        SqlCommand command = connection.CreateCommand();
                        SqlTransaction transaction;

                        // Start a local transaction.
                        transaction = connection.BeginTransaction();

                        // Must assign both transaction object and connection
                        // to Command object for a pending local transaction
                        command.Connection = connection;
                        command.Transaction = transaction;

                        try
                        {
                            Random rnd = new Random();
                            int rnum = rnd.Next(1,1000);
                            command.CommandText =
                                "Insert into sampletb (id, cdatetime, note) VALUES ('"+ rnum + "','" + DateTime.Now + "','Description');";
                            command.ExecuteNonQuery();

                            // Attempt to commit the transaction.
                            transaction.Commit();
                            Console.WriteLine("one record is written to database.");
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine("Commit Exception Type: {0}", ex.GetType());
                            Console.WriteLine(" Message: {0}", ex.Message);

                            // Attempt to roll back the transaction.
                            try
                            {
                                transaction.Rollback();
                            }
                            catch (Exception ex2)
                            {
                                // This catch block will handle any errors that may have occurred
                                // on the server that would cause the rollback to fail, such as
                                // a closed connection.
                                Console.WriteLine("Rollback Exception Type: {0}", ex2.GetType());
                                Console.WriteLine("  Message: {0}", ex2.Message);
                            }
                        }

                        // execute select query
                        try
                        {
                            String sql = "SELECT id, cdatetime, note FROM sampletb";
                            using (SqlCommand cmd = new SqlCommand(sql, connection))
                            {
                                using (SqlDataReader reader = cmd.ExecuteReader())
                                {
                                    while (reader.Read())
                                    {
                                        Console.WriteLine("{0} {1} {2}", reader.GetInt32(0), reader.GetDateTime(1), reader.GetString(2));
                                    }
                                }
                            }
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine("Error Message is " + ex.ToString());
                        }
                    }
                    success = true;
                }
                catch (SqlException e)
                {

                    Console.WriteLine("Current Timestamps:" + DateTime.UtcNow.ToFileTime() + " :Error MEssage is " + e.ToString());
                    retryCount++;

                    if (retryCount != maxRetryCount)
                    {
                        Console.WriteLine("=============================");
                        Console.WriteLine("Retry count: " + retryCount + "\nCurrent Timestamps:" + DateTime.UtcNow.ToLocalTime());
                        Console.WriteLine("Waiting for 15 seconds");
                        Console.WriteLine("=============================");
                        Thread.Sleep(15000);
                    }
                    else
                    {
                        Console.WriteLine("=============================");
                        Console.WriteLine("Max retry count " + (retryCount - 1) + " reached. Exiting");
                        Console.WriteLine("=============================");
                    }
                }
            }
        }
    }
}

3-5. Commit 完了後の実行結果

Commit 完了して 1 レコード挿入された結果。

$ dotnet run
Connecting to SQL Server ... 
Current Timestamps:1/7/2023 4:07:53 PM
-----------------------------
Done.
one record is written to database.
3366109 12/9/2022 1:59:12 PM int
3366109 12/9/2022 1:59:12 PM mcranentiv
6732218 12/9/2022 1:59:12 PM orlsojlvuu
10098327 12/9/2022 1:59:12 PM uaclnlkehv
13464436 12/9/2022 1:59:12 PM xngcehsmtc
16830545 12/9/2022 1:59:12 PM llvzgooqaa
20196654 12/9/2022 1:59:12 PM atmordnpfz
23562763 12/9/2022 1:59:12 PM qpzlferdra
26928872 12/9/2022 1:59:12 PM lnbjlmclxx
30294981 12/9/2022 1:59:12 PM xfspqvetbf
0 12/9/2022 1:59:12 PM Description

3-6. Commit コメントアウトした場合の実行結果

試しに、Commit をコメントアウトした場合、後続の Select 実行前の ExecuteReader の部分で以下のエラーが出力されます。

// Attempt to commit the transaction.
//transaction.Commit();
//Console.WriteLine("one record is written to database.");
Connecting to SQL Server ... 
Current Timestamps:1/7/2023 4:06:42 PM
-----------------------------
Done.
Error Message is System.InvalidOperationException: ExecuteReader requires the command to have a transaction when the connection assigned to the command is in a pending local transaction.  The Transaction property of the command has not been initialized.
   at System.Data.SqlClient.SqlCommand.ValidateCommand(Boolean async, String method)
   at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, TaskCompletionSource`1 completion, Int32 timeout, Task& task, Boolean asyncWrite, String method)
   at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method)
   at System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior)
   at System.Data.SqlClient.SqlCommand.ExecuteReader()
   at ConsoleApp2.Program.Main(String[] args) in /home/xxx/My2ndApp/Program.cs:line 83

▼4. 参考情報

  1. C# – Visual Studio Marketplace
  2. Install .NET on Ubuntu – .NET | Microsoft Docs
  3. C# Ubuntu (sqlchoice.azurewebsites.net)
  4. System.Data.SqlClient 名前空間 | Microsoft Learn
  5. NuGet Gallery | System.Data.SqlClient
  6. SqlConnection.BeginTransaction メソッド
  7. Python – SQL Server on Linux トランザクションでクエリを制御する方法  No.94
  8. Java – SQL Server on Linux トランザクションでクエリを制御する方法 No9

以上です。参考になりましたら幸いです。




コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です