はじめに
この記事では、Microsoft 社が公開している Build an app using SQL Serverの内容に従い、SQL Server を使用した C# アプリを作成します。
環境
- OS: macOS Catalina バージョン 10.15.4
- SQL Server: SQL Server 2019
- .NET Core: 3.1 LTS
環境のセットアップ
ここでは、SQL Server を Docker上で取得します。その後、SQL Server で .NET Core アプリを作成するために必要な依存関係をインストールします。
SQL Server のインストール
- macOS で SQL Server を実行するには、SQL Server on Linux の Docker イメージを使用します。そのためには、Docker for Macをインストールする必要があります。
- Docker 環境に最低 4GB のメモリを設定し、パフォーマンスを評価したい場合は複数のコアを追加することも検討します。これは、メニューバーの [環境設定] -> [詳細設定]オプションで行うことができます。
- 新しいターミナルプロンプトを起動し、以下のコマンドを使用して SQL Server on Linux Docker イメージをダウンロードして起動します。
SA_PASSWORD の部分は特殊文字を使用した強力なパスワードを使用するように書き換えてください。
sudo docker pull microsoft/mssql-server-linux:2017-latest
docker run -e'HOMEBREW_NO_ENV_FILTERING=1'-e'ACCEPT_EULA=Y'-e'SA_PASSWORD=yourStrong(!)Password'-p 1433:1433 -d microsoft/mssql-server-linux
筆者は、Docker Desktop をインストールすると一緒に利用可能になる、Docker Compose を利用して、SQL Server 2019 on Linuxをインストールしています。docker-compose.yaml については、以下の GitHub リポジトリを参照してください。
なお、ウェブ上で公開されている Docker イメージの URL は、Ubuntu ベースの SQL Server 2017 on Linuxの Docker イメージ です。SQL Server 2019では、従来の Ubuntu ベースに加え、RHEL ベース の SQL Server on Linuxの Docker イメージ もサポートされるようになりました。
Ubuntu ベースを利用するか、RHEL ベースを利用するかは、好きな方を選択してください。(SQL Server における機能に差はありません)
Homebrew と .NET Core のインストール
すでに .NET Core 3.1 LTS がインストールされている場合は、このステップをスキップしてください。
公式インストーラーをダウンロードして、.NET Coreをインストールします。インストーラーは、Build apps - SDKのものを選択してください。.NET Core 3.1.x SDK および、.NET Core ランタイムを一緒にインストールできます。
- ASP.NET Core Runtime
- Desktop Runtime
- .NET Core Runtime (上の2つを一緒にしたもの)
.NET Core では、作成したアプリを実行するためのランタイムが、種類によって分けられています。
ダウンロードサイト上では分かれて表示されているため、悩んでしまう可能性がありますが、ここでは気にしないでください。
なお、本家のサイトにあるリンクは .NET Core 2.0 のダウンロードリンクになっています。これは既にサポート切れ、かつ LTS ではないため、最新の .NET Core 3.1.x をダウンロードし、インストールするようにしてください。.NET Core 3.1 は LTS バージョンになります。
なお、筆者は、SQL Server 2019 と同様、Docker Compose を使用して、.NET Core 3.1 のコンテナーを作成し、開発を進めています。docker-compose.yaml については、以下の GitHub リポジトリを参照してください。
SQL Server および .NET Core 3.1 を Docker 上で利用する場合は、同じ docker-compose.yaml ファイル内に記述します。container_nameを記述することで、コンテナ名を使ってコンテナ同士の相互通信が可能になります。
version:'3'services:app:image:mcr.microsoft.com/dotnet/core/sdk:latestcontainer_name:dotnetcoreapptty:trueports:-10080:80volumes:-./src:/srcworking_dir:"/src"mssql:image:mcr.microsoft.com/mssql/rhel/server:2019-latestcontainer_name:'mssql2019'environment:-MSSQL_SA_PASSWORD=databaseadmin@1-ACCEPT_EULA=Yports:-1433:1433# volumes: # Mounting a volume does not work on Docker for Mac# - ./mssql/log:/var/opt/mssql/log# - ./mssql/data:/var/opt/mssql/dataSQL Server を使った C# アプリケーションを作成
ここでは、以下、2 つのシンプルな C# アプリを作成します。
- 基本的な Insert、Update、Delete、Select を実行するアプリ
- .NET Core の ORM フレームワークの中でも特に人気のある Entity Framework Coreを利用してInsert、Update、Delete、Select を実行するアプリ
SQL Server に接続してクエリを実行する C# アプリを作成
開発を行うワークディレクトリに移動し、新しい .NET Core プロジェクトを作成します。
基本的な .NET Core の Program.cs と csproj ファイルを含むプロジェクトディレクトリが作成されます。
cd ~/
dotnet new console -o SqlServerSample
Docker Compose で行う場合は、以下の通りです。
docker-compose run --rm app dotnet new console -o SqlServerSample
SqlServerSample.csprojというファイルが SqlServerSample ディレクトリ以下に作成されます。
任意のテキストエディタで SqlServerSample.csproj ファイルを開き、コードを以下の通りに書き換え、System.Data.SqlClient をプロジェクトに追加します。保存してファイルを閉じます。
<ProjectSdk="Microsoft.NET.Sdk"><PropertyGroup><OutputType>Exe</OutputType><TargetFramework>netcoreapp3.1</TargetFramework></PropertyGroup><ItemGroup><PackageReferenceInclude="System.Data.SqlClient"Version="4.8.1"/></ItemGroup></Project>SqlServerSample ディレクトリ以下にある Program.cs ファイルを開き、コードを以下の通りに書き換え、保存してファイルを閉じます。ユーザー名とパスワードを自分のものに置き換えることを忘れないでください。
usingSystem;usingSystem.Data.SqlClient;namespaceSqlServerSample{classProgram{staticvoidMain(string[]args){try{// 接続文字列の構築SqlConnectionStringBuilderbuilder=newSqlConnectionStringBuilder();builder.DataSource="localhost";// 接続先の SQL Server インスタンスbuilder.UserID="sa";// 接続ユーザー名builder.Password="your_password";// 接続パスワードbuilder.InitialCatalog="master";// 接続するデータベース(ここは変えないでください)// builder.ConnectTimeout = 60000; // 接続タイムアウトの秒数(ms) デフォルトは 15 秒// SQL Server に接続Console.Write("SQL Server に接続しています... ");using(SqlConnectionconnection=newSqlConnection(builder.ConnectionString)){connection.Open();Console.WriteLine("接続成功。");}}catch(SqlExceptione){Console.WriteLine(e.ToString());}Console.WriteLine("すべてが完了しました。任意のキーを押してアプリを終了します...");Console.ReadKey(true);}}}SqlServerSample ディレクトリに戻り、以下のコマンドを実行して csproj 内の依存関係を復元します。
cd ~/SqlServerSample
dotnet restore
完了したら、ビルド実行を行います。
dotnet run
なお、Docker Compose で実行している場合は、以下のようなコマンドを実行することで上記を実現できます。
docker-compose run -w /src/SqlServerSample --rm app dotnet restore
docker-compose run -w /src/SqlServerSample --rm app dotnet run
これで、SQL Server に接続を行うコンソールアプリができました。ただし、このアプリでは単にデータベースへの接続だけを行っているだけで、クエリは実行していません。
次に、Program.cs 内にコードを追加して、データベースやテーブルの作成、INSERT/UPDATE/DELETE/SELECT などのクエリを実行するように変更します。ユーザー名とパスワードは自分のものに置き換えることを忘れないでください。
書き換えた後、ファイルを保存し、プロジェクトをビルドして実行します。
usingSystem;usingSystem.Text;usingSystem.Data.SqlClient;namespaceSqlServerSample{classProgram{staticvoidMain(string[]args){try{Console.WriteLine("SQL Server に接続し、Create、Read、Update、Delete 操作のデモを行います。");// 接続文字列の構築SqlConnectionStringBuilderbuilder=newSqlConnectionStringBuilder();builder.DataSource="localhost";// 接続先の SQL Server インスタンスbuilder.UserID="sa";// 接続ユーザー名builder.Password="your_password";// 接続パスワードbuilder.InitialCatalog="master";// 接続するデータベース(ここは変えないでください)// builder.ConnectTimeout = 60000; // 接続タイムアウトの秒数(ms) デフォルトは 15 秒// SQL Server に接続Console.Write("SQL Server に接続しています... ");using(SqlConnectionconnection=newSqlConnection(builder.ConnectionString)){connection.Open();Console.WriteLine("接続成功。");// サンプルデータベースの作成Console.Write("既に作成されている SampleDB データベースを削除し、再作成します... ");Stringsql="DROP DATABASE IF EXISTS [SampleDB]; CREATE DATABASE [SampleDB]";using(SqlCommandcommand=newSqlCommand(sql,connection)){// command.CommandTimeout = 60000; // コマンドがタイムアウトする場合は秒数を変更(ms) デフォルトは 30秒command.ExecuteNonQuery();Console.WriteLine("SampleDB データベースを作成しました。");}// テーブルを作成しサンプルデータを登録Console.Write("サンプルテーブルを作成しデータを登録します。任意のキーを押して続行します...");Console.ReadKey(true);StringBuildersb=newStringBuilder();sb.Append("USE SampleDB; ");sb.Append("CREATE TABLE Employees ( ");sb.Append(" Id INT IDENTITY(1,1) NOT NULL PRIMARY KEY, ");sb.Append(" Name NVARCHAR(50), ");sb.Append(" Location NVARCHAR(50) ");sb.Append("); ");sb.Append("INSERT INTO Employees (Name, Location) VALUES ");sb.Append("(N'Jared', N'Australia'), ");sb.Append("(N'Nikita', N'India'), ");sb.Append("(N'Tom', N'Germany'); ");sql=sb.ToString();using(SqlCommandcommand=newSqlCommand(sql,connection)){// command.CommandTimeout = 60000; // コマンドがタイムアウトする場合は秒数を変更(ms) デフォルトは 30秒command.ExecuteNonQuery();Console.WriteLine("作成完了");}// INSERT デモConsole.Write("テーブルに新しい行を挿入するには、任意のキーを押して続行します...");Console.ReadKey(true);sb.Clear();sb.Append("INSERT Employees (Name, Location) ");sb.Append("VALUES (@name, @location);");sql=sb.ToString();using(SqlCommandcommand=newSqlCommand(sql,connection)){// command.CommandTimeout = 60000; // コマンドがタイムアウトする場合は秒数を変更(ms) デフォルトは 30秒command.Parameters.AddWithValue("@name","Jake");command.Parameters.AddWithValue("@location","United States");introwsAffected=command.ExecuteNonQuery();Console.WriteLine(rowsAffected+" 行 挿入されました");}// UPDATE デモStringuserToUpdate="Nikita";Console.Write("ユーザー '"+userToUpdate+"' の 'Location' を更新するには、任意のキーを押して続行します...");Console.ReadKey(true);sb.Clear();sb.Append("UPDATE Employees SET Location = N'United States' WHERE Name = @name");sql=sb.ToString();using(SqlCommandcommand=newSqlCommand(sql,connection)){// command.CommandTimeout = 60000; // コマンドがタイムアウトする場合は秒数を変更(ms) デフォルトは 30秒command.Parameters.AddWithValue("@name",userToUpdate);introwsAffected=command.ExecuteNonQuery();Console.WriteLine(rowsAffected+" 行 更新されました");}// DELETE デモStringuserToDelete="Jared";Console.Write("ユーザー '"+userToDelete+"' を削除するには、任意のキーを押して続行します...");Console.ReadKey(true);sb.Clear();sb.Append("DELETE FROM Employees WHERE Name = @name;");sql=sb.ToString();using(SqlCommandcommand=newSqlCommand(sql,connection)){// command.CommandTimeout = 60000; // コマンドがタイムアウトする場合は秒数を変更(ms) デフォルトは 30秒command.Parameters.AddWithValue("@name",userToDelete);introwsAffected=command.ExecuteNonQuery();Console.WriteLine(rowsAffected+" 行 削除されました");}// READ デモConsole.WriteLine("テーブルからデータを読み取るには、任意のキーを押して続行します...");Console.ReadKey(true);sql="SELECT Id, Name, Location FROM Employees;";using(SqlCommandcommand=newSqlCommand(sql,connection)){using(SqlDataReaderreader=command.ExecuteReader()){while(reader.Read()){Console.WriteLine("{0} {1} {2}",reader.GetInt32(0),reader.GetString(1),reader.GetString(2));}}}}}catch(SqlExceptione){Console.WriteLine(e.ToString());}Console.WriteLine("すべて完了しました。任意のキーを押して終了します...");Console.ReadKey(true);}}}これで、macOS 上の .NET Core を使って、初めて C# + SQL Server アプリを作成できました。次は、ORM を使って C# アプリを作成します。
.NET Core で Entity Framework Core ORM を使用して SQL Server に接続する C# アプリを作成
ワークディレクトリに戻り、新しい.NET Coreプロジェクトを作成します。
cd ~/
dotnet new console -o SqlServerEFSample
Docker Compose で行う場合は、以下の通りです。
docker-compose run --rm app dotnet new console -o SqlServerSampleEF
SqlServerEFSample.csproj というファイルが SqlServerEFSample ディレクトリ以下に作成されます。
任意のテキストエディタで SqlServerEFSample.csproj ファイルを開き、コードを以下の通りに書き換え、Entity Framework Core をプロジェクトに追加します。保存してファイルを閉じます。
<ProjectSdk="Microsoft.NET.Sdk"><PropertyGroup><OutputType>Exe</OutputType><TargetFramework>netcoreapp3.1</TargetFramework></PropertyGroup><ItemGroup><PackageReferenceInclude="System.Data.SqlClient"Version="4.8.1"/><PackageReferenceInclude="Microsoft.EntityFrameworkCore.SqlServer"Version="3.1.4"/></ItemGroup></Project>このサンプルでは、2つのテーブルを作成します。1つ目は「ユーザー」に関するデータを保持し、もう1つは「タスク」に関するデータを保持するものです。
User.cs を作成します。
User クラスを定義します。SqlServerEFSample ディレクトリ以下に User.cs ファイルを作成します。
このクラスは、User テーブルに紐づくモデルのクラスです。書き換えた後、ファイルを保存して閉じます。この時点では、Task クラスがないためコンパイルエラーとなりますが、問題ありません。
usingSystem;usingSystem.Collections.Generic;namespaceSqlServerEFSample{publicclassUser{publicintUserId{get;set;}publicStringFirstName{get;set;}publicStringLastName{get;set;}publicvirtualIList<Task>Tasks{get;set;}publicStringGetFullName(){returnthis.FirstName+" "+this.LastName;}publicoverridestringToString(){return"User [id="+this.UserId+", name="+this.GetFullName()+"]";}}}Task.cs を作成します。
Task クラスを定義します。SqlServerEFSample ディレクトリ以下に Task.cs ファイルを作成します。
このクラスは、Task テーブルに紐づくモデルのクラスです。書き換えた後、ファイルを保存して閉じます。
usingSystem;namespaceSqlServerEFSample{publicclassTask{publicintTaskId{get;set;}publicstringTitle{get;set;}publicDateTimeDueDate{get;set;}publicboolIsComplete{get;set;}publicvirtualUserAssignedTo{get;set;}publicoverridestringToString(){return"Task [id="+this.TaskId+", title="+this.Title+", dueDate="+this.DueDate.ToString()+", IsComplete="+this.IsComplete+"]";}}}EFSampleContext.cs を作成します。
EFSampleContext クラスを定義します。SqlServerEFSample ディレクトリ以下に EFSampleContext.cs ファイルを作成します。
このクラスは、Entity Framework Core を使用し、.NET オブジェクトを利用してデータのクエリ、挿入、更新、および削除を行うためのクラスです。User クラスと Task クラスを使用しています。
書き換えた後、ファイルを保存して閉じます。
usingMicrosoft.EntityFrameworkCore;namespaceSqlServerEFSample{publicclassEFSampleContext:DbContext{string_connectionString;publicEFSampleContext(stringconnectionString){this._connectionString=connectionString;}protectedoverridevoidOnConfiguring(DbContextOptionsBuilderoptionsBuilder){optionsBuilder.UseSqlServer(this._connectionString);}publicDbSet<User>Users{get;set;}publicDbSet<Task>Tasks{get;set;}}}Entity Framework (.NET Framework) と違う点としては、OnConfiguring メソッドが新たにオーバーライドされ、逆に Database.SetInitializer(IDatabaseInitializer) を EFSampleContext のコンストラクタ内で指定しなくなっている点です。
最後に Program.csを更新します。これまで作成したクラスを使用するための設定を行います。ユーザー名とパスワードを自分のものに更新することを忘れないでください。
保存してファイルを閉じます。
usingSystem;usingSystem.Linq;usingSystem.Data.SqlClient;usingSystem.Collections.Generic;namespaceSqlServerEFSample{classProgram{staticvoidMain(string[]args){Console.WriteLine("** Entity Framework Core と SQL Server を使用した C# CRUD のサンプル **\n");try{// 接続文字列を構築SqlConnectionStringBuilderbuilder=newSqlConnectionStringBuilder();builder.DataSource="localhost";// 接続先の SQL Server インスタンスbuilder.UserID="sa";// 接続ユーザー名builder.Password="your_password";// 接続パスワードbuilder.InitialCatalog="EFSampleDB";// 接続するデータベース(ここは変えないでください)// builder.ConnectTimeout = 60000; // 接続タイムアウトの秒数(ms) デフォルトは 15 秒using(EFSampleContextcontext=newEFSampleContext(builder.ConnectionString)){context.Database.EnsureDeleted();context.Database.EnsureCreated();Console.WriteLine("C#のクラスからデータベーススキーマを作成しました。");// Create デモ: ユーザーインスタンスを作成し、データベースに保存UsernewUser=newUser{FirstName="Anna",LastName="Shrestinian"};context.Users.Add(newUser);context.SaveChanges();Console.WriteLine("\n作成されたユーザー: "+newUser.ToString());// Create デモ: タスクインスタンスを作成し、データベースに保存TasknewTask=newTask(){Title="Ship Helsinki",IsComplete=false,DueDate=DateTime.Parse("04-01-2017")};context.Tasks.Add(newTask);context.SaveChanges();Console.WriteLine("\nCreated Task: "+newTask.ToString());// Association demo: Assign task to usernewTask.AssignedTo=newUser;context.SaveChanges();Console.WriteLine("\n作成されたタスク: '"+newTask.Title+"' 割り当てられたユーザー: '"+newUser.GetFullName()+"'");// Read デモ: ユーザー 'Anna' に割り当てられた未完了のタスクを見つけるConsole.WriteLine("\n'Anna' に割り当てられた未完了のタスク:");varquery=fromtincontext.Taskswheret.IsComplete==false&&t.AssignedTo.FirstName.Equals("Anna")selectt;foreach(vartinquery){Console.WriteLine(t.ToString());}// Update デモ: タスクの '期限' を変更TasktaskToUpdate=context.Tasks.First();// 最初のタスクを取得Console.WriteLine("\nタスクをアップデート中: "+taskToUpdate.ToString());taskToUpdate.DueDate=DateTime.Parse("06-30-2016");context.SaveChanges();Console.WriteLine("変更された期限: : "+taskToUpdate.ToString());// Delete デモ: 2016年が期限になっているすべてのタスクを削除Console.WriteLine("\n期限が2016年になっているすべてのタスクを削除します。");DateTimedueDate2016=DateTime.Parse("12-31-2016");query=fromtincontext.Taskswheret.DueDate<dueDate2016selectt;foreach(Tasktinquery){Console.WriteLine("Deleting task: "+t.ToString());context.Tasks.Remove(t);}context.SaveChanges();// 'Delete' 操作の後にタスクを表示 - 0個のタスクがあるはずConsole.WriteLine("\n削除後のタスク:");List<Task>tasksAfterDelete=(fromtincontext.Tasksselectt).ToList<Task>();if(tasksAfterDelete.Count==0){Console.WriteLine("[なし]");}else{foreach(Tasktinquery){Console.WriteLine(t.ToString());}}}}catch(Exceptione){Console.WriteLine(e.ToString());}Console.WriteLine("すべて完了しました。任意のキーを押して終了します...");Console.ReadKey(true);}}}EFSampleContext クラスのコンストラクタで Database.SetInitializer(IDatabaseInitializer) を行っていないため、Program.cs 内で context.Database.EnsureDeleted() と context.Database.EnsureCreated() が行われていますね。
SqlServerSampleEF ディレクトリに戻り、以下のコマンドを実行して csproj 内の依存関係を復元します。
cd ~/SqlServerEFSample
dotnet restore
完了したら、ビルド実行を行います。
dotnet run
Docker Compose で実行する場合は、以下のコマンドを実行してください。
docker-compose run -w /src/SqlServerEFSample --rm app dotnet restore
docker-compose run -w /src/SqlServerEFSample --rm app dotnet run
これで、2つ目の C# アプリの作成が終わりました。最後に、SQL Server の カラムストア機能を使って C# アプリを高速化する方法について学びます。
C# アプリを 100 倍速にする
これまでで基本的なことは理解できたと思います。最後は、SQL Server を使用してアプリをより良くする方法を見てみます。このモジュールでは、カラムストアインデックスの簡単な例と、カラムストアインデックスがどのようにデータ処理速度を向上させるかを確認します。カラムストアインデックスは、従来の列ストアインデックスに比べて、分析ワークロードでは最大 100 倍のパフォーマンス向上、データ圧縮では最大 10 倍のパフォーマンス向上を実現できます。
カラムストアインデックスの機能を確認するために、500 万行のサンプルデータベースとサンプルテーブルを作成し、カラムストアインデックスを追加する前と後の簡単なクエリを実行する C# アプリケーションを作成します。
ワークディレクトリに戻り、新しい.NET Coreプロジェクトを作成します。
cd ~/
dotnet new console -o SqlServerColumnstoreSample
Docker Compose で行う場合は、以下の通りです。
docker-compose run --rm app dotnet new console -o SqlServerColumnstoreSample
SqlServerColumnstoreSample.csproj というファイルが SqlServerColumnstoreSample ディレクトリ以下に作成されます。
任意のテキストエディタで SqlServerColumnstoreSample.csproj ファイルを開き、コードを以下の通りに書き換え、System.Data.SqlClient をプロジェクトに追加します。保存してファイルを閉じます。
<ProjectSdk="Microsoft.NET.Sdk"><PropertyGroup><OutputType>Exe</OutputType><TargetFramework>netcoreapp3.1</TargetFramework></PropertyGroup><ItemGroup><PackageReferenceInclude="System.Data.SqlClient"Version="4.8.1"/></ItemGroup></Project>Program.cs の内容を書き換えます。ユーザー名とパスワードは自分のものに置き換えることを忘れないでください。
保存してファイルを閉じます。
usingSystem;usingSystem.Collections.Generic;usingSystem.Data.SqlClient;usingSystem.Linq;usingSystem.Text;usingSystem.Threading.Tasks;namespaceSqlServerColumnstoreSample{classProgram{staticvoidMain(string[]args){try{Console.WriteLine("*** SQL Server カラムストアのデモ ***");// 接続文字列の構築SqlConnectionStringBuilderbuilder=newSqlConnectionStringBuilder();builder.DataSource="localhost";// 接続先の SQL Server インスタンスbuilder.UserID="sa";// 接続ユーザー名builder.Password="your_password";// 接続パスワードbuilder.InitialCatalog="master";// 接続するデータベース(ここは変えないでください)// builder.ConnectTimeout = 60000; // 接続タイムアウトの秒数(ms) デフォルトは 15 秒// SQL Server に接続Console.Write("SQL Serverへ接続しています... ");using(SqlConnectionconnection=newSqlConnection(builder.ConnectionString)){connection.Open();Console.WriteLine("接続完了。");// サンプルデータベースの作成Console.Write("'SampleDB' を再作成しています... ");Stringsql="DROP DATABASE IF EXISTS [SampleDB]; CREATE DATABASE [SampleDB]";using(SqlCommandcommand=newSqlCommand(sql,connection)){command.CommandTimeout=60000;// コマンドがタイムアウトする場合は秒数を変更(ms) デフォルトは 30秒command.ExecuteNonQuery();Console.WriteLine("完了。");}// 'Table_with_5M_rows' テーブルに500万行を挿入Console.Write("テーブル 'Table_with_5M_rows' に500万行を挿入します。1分ほどかかりますが、お待ちください... ");StringBuildersb=newStringBuilder();sb.Append("USE SampleDB; ");sb.Append("WITH a AS (SELECT * FROM (VALUES(1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) AS a(a))");sb.Append("SELECT TOP(5000000)");sb.Append("ROW_NUMBER() OVER (ORDER BY a.a) AS OrderItemId ");sb.Append(",a.a + b.a + c.a + d.a + e.a + f.a + g.a + h.a AS OrderId ");sb.Append(",a.a * 10 AS Price ");sb.Append(",CONCAT(a.a, N' ', b.a, N' ', c.a, N' ', d.a, N' ', e.a, N' ', f.a, N' ', g.a, N' ', h.a) AS ProductName ");sb.Append("INTO Table_with_5M_rows ");sb.Append("FROM a, a AS b, a AS c, a AS d, a AS e, a AS f, a AS g, a AS h;");sql=sb.ToString();using(SqlCommandcommand=newSqlCommand(sql,connection)){command.CommandTimeout=60000;// コマンドがタイムアウトする場合は秒数を変更(ms) デフォルトは 30秒command.ExecuteNonQuery();Console.WriteLine("完了。");}// カラムストアインデックスなしで SQL クエリを実行doubleelapsedTimeWithoutIndex=SumPrice(connection);Console.WriteLine("カラムストアインデックスなしのクエリ時間: "+elapsedTimeWithoutIndex+"ms");// カラムストアインデックスを追加Console.Write("'Table_with_5M_rows' テーブルにカラムストアインデックスを追加中... ");sql="CREATE CLUSTERED COLUMNSTORE INDEX columnstoreindex ON Table_with_5M_rows;";using(SqlCommandcommand=newSqlCommand(sql,connection)){command.CommandTimeout=60000;// コマンドがタイムアウトする場合は秒数を変更(ms) デフォルトは 30秒command.ExecuteNonQuery();Console.WriteLine("完了。");}// カラムストアインデックスが追加された後、再度同じ SQL クエリを実行doubleelapsedTimeWithIndex=SumPrice(connection);Console.WriteLine("カラムストアありのクエリ時間: "+elapsedTimeWithIndex+"ms");// カラムストアインデックスの追加によるパフォーマンス向上を計算Console.WriteLine("カラムストアインデックスによる性能向上: "+Math.Round(elapsedTimeWithoutIndex/elapsedTimeWithIndex)+"x!");}Console.WriteLine("すべて完了しました。任意のキーを押して終了します...");Console.ReadKey(true);}catch(Exceptione){Console.WriteLine(e.ToString());}}publicstaticdoubleSumPrice(SqlConnectionconnection){Stringsql="SELECT SUM(Price) FROM Table_with_5M_rows";longstartTicks=DateTime.Now.Ticks;using(SqlCommandcommand=newSqlCommand(sql,connection)){try{command.CommandTimeout=60000;// コマンドがタイムアウトする場合は秒数を変更(ms) デフォルトは 30秒varsum=command.ExecuteScalar();TimeSpanelapsed=TimeSpan.FromTicks(DateTime.Now.Ticks)-TimeSpan.FromTicks(startTicks);returnelapsed.TotalMilliseconds;}catch(Exceptione){Console.WriteLine(e.ToString());}}return0;}}}SqlServerColumnstoreSample ディレクトリに戻り、以下のコマンドを実行して csproj 内の依存関係を復元します。
cd ~/SqlServerColumnstoreSample
dotnet restore
完了したら、ビルド実行を行います。
dotnet run
Docker Compose で実行する場合は、以下のコマンドを実行してください。
docker-compose run -w /src/SqlServerColumnstoreSample --rm app dotnet restore
docker-compose run -w /src/SqlServerColumnstoreSample --rm app dotnet run
おめでとうございます。カラムストアインデックスを使って C# アプリを高速化しました!
おわりに
以上で、「macOS上でSQL Serverを使用してC#アプリを作成する」は終了です。Build an app using SQL Serverには、他言語での SQL Server アプリを作成するチュートリアルがあります。ぜひ、他の言語でも試してみてください。



