はじめに
この記事では、Microsoft 社が公開している Build an app using SQL Server の内容に従い、SQL Server を使用した C# アプリを作成します。
第30回 SQL Server 2019 勉強会@JSSUG (2020/5/16) のセッション資料です。
環境
- OS: Windows 10 Pro 10.0.19041 N/A ビルド 19041
- SQL Server: SQL Server 2019
- Visual Studio: Visual Studio 2019 Community
環境のセットアップ
SQL Server のインストール
ウェブ上では、ホスト OS に SQL Server 2017 Developer 以上をインストール とあります。
こちら のサイトより、SQL Server 2019 Developer インストーラーをダウンロードし、インストールを行ってください。
筆者は、WSL2 (Ubuntu 18.04 LTS) 上にインストールした Docker 上に、SQL Server 2019 on Linux
をインストールして利用しました。docker-compose.yaml については、以下の GitHub リポジトリを参照してください。構築後、ifconfig コマンドを実行し、eth0 の IP アドレスを確認してください。
Visual Studio および .NET Framework をインストール
こちら のサイトより、Visual Studio 2019 Community インストーラーをダウンロードし、インストールを行ってください。
SQL Server を使った C# アプリケーションを作成
ここでは、以下、2 つのシンプルな C# アプリを作成します。
- 基本的な Insert、Update、Delete、Select を実行するアプリ
- C# の ORM フレームワークの中でも特に人気のある Entity Framework を利用してInsert、Update、Delete、Select を実行するアプリ
SQL Server に接続してクエリを実行する C# アプリを作成
C# コンソールアプリを新規作成します。
- Visual Studio を起動します。
- [ファイル] -> [新規作成] -> [プロジェクト] を選択します。
- テンプレート一覧から .NET Framework のコンソールアプリを選択します。
- プロジェクト名に SqlServerSample と設定し、プロジェクトを作成します。
Visual Studio で新しい C# Console Application プロジェクトを作成しました。
作成したプロジェクト内にある Program.cs というファイルを開き、Program.cs の内容を書き換えます。
ユーザー名とパスワードは自分のものに置き換えることを忘れないでください。
書き換えた後、ファイルを保存します。その後、F5 キーを押してプロジェクトをビルドして実行します。
using System;
using System.Data.SqlClient;
namespace SqlServerSample
{
class Program
{
static void Main(string[] args)
{
try
{
// 接続文字列の構築
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();
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 (SqlConnection connection = new SqlConnection(builder.ConnectionString))
{
connection.Open();
Console.WriteLine("接続成功。");
}
}
catch (SqlException e)
{
Console.WriteLine(e.ToString());
}
Console.WriteLine("すべてが完了しました。任意のキーを押してアプリを終了します...");
Console.ReadKey(true);
}
}
}
これで、SQL Server に接続を行うコンソールアプリができました。ただし、このアプリでは単にデータベースへの接続だけを行っているだけで、クエリは実行していません。
次に、Program.cs 内にコードを追加して、データベースやテーブルの作成、INSERT/UPDATE/DELETE/SELECT などのクエリを実行するように変更します。
ユーザー名とパスワードは自分のものに置き換えることを忘れないでください。
書き換えた後、ファイルを保存します。その後、F5 キーを押してプロジェクトをビルドして実行します。
using System;
using System.Text;
using System.Data.SqlClient;
namespace SqlServerSample
{
class Program
{
static void Main(string[] args)
{
try
{
Console.WriteLine("SQL Server に接続し、Create、Read、Update、Delete 操作のデモを行います。");
// 接続文字列を構築
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();
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 (SqlConnection connection = new SqlConnection(builder.ConnectionString))
{
connection.Open();
Console.WriteLine("接続成功。");
// サンプルデータベースの作成
Console.Write("既に作成されている SampleDB データベースを削除し、再作成します... ");
String sql = "DROP DATABASE IF EXISTS [SampleDB]; CREATE DATABASE [SampleDB]";
using (SqlCommand command = new SqlCommand(sql, connection))
{
command.CommandTimeout = 60000; // コマンドがタイムアウトする場合は秒数を変更(ms) デフォルトは 30秒
command.ExecuteNonQuery();
Console.WriteLine("SampleDB データベースを作成しました。");
}
// テーブルを作成しサンプルデータを登録
Console.Write("サンプルテーブルを作成しデータを登録します。任意のキーを押して続行します...");
Console.ReadKey(true);
StringBuilder sb = new StringBuilder();
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 (SqlCommand command = new SqlCommand(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 (SqlCommand command = new SqlCommand(sql, connection))
{
command.CommandTimeout = 60000; // コマンドがタイムアウトする場合は秒数を変更(ms) デフォルトは 30秒
command.Parameters.AddWithValue("@name", "Jake");
command.Parameters.AddWithValue("@location", "United States");
int rowsAffected = command.ExecuteNonQuery();
Console.WriteLine(rowsAffected + " 行 挿入されました。");
}
// UPDATE デモ
String userToUpdate = "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 (SqlCommand command = new SqlCommand(sql, connection))
{
command.CommandTimeout = 60000; // コマンドがタイムアウトする場合は秒数を変更(ms) デフォルトは 30秒
command.Parameters.AddWithValue("@name", userToUpdate);
int rowsAffected = command.ExecuteNonQuery();
Console.WriteLine(rowsAffected + " 行 更新されました。");
}
// DELETE デモ
String userToDelete = "Jared";
Console.Write("ユーザー名 '" + userToDelete + "' を削除中です。任意のキーを押して処理を続行します...");
Console.ReadKey(true);
sb.Clear();
sb.Append("DELETE FROM Employees WHERE Name = @name;");
sql = sb.ToString();
using (SqlCommand command = new SqlCommand(sql, connection))
{
command.CommandTimeout = 60000; // コマンドがタイムアウトする場合は秒数を変更(ms) デフォルトは 30秒
command.Parameters.AddWithValue("@name", userToDelete);
int rowsAffected = command.ExecuteNonQuery();
Console.WriteLine(rowsAffected + " 行 削除されました。");
}
// READ デモ
Console.WriteLine("テーブルからデータを読み取り中です。任意のキーを押して処理を続行します。...");
Console.ReadKey(true);
sql = "SELECT Id, Name, Location FROM Employees;";
using (SqlCommand command = new SqlCommand(sql, connection))
{
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
Console.WriteLine("{0} {1} {2}", reader.GetInt32(0), reader.GetString(1), reader.GetString(2));
}
}
}
}
}
catch (SqlException e)
{
Console.WriteLine(e.ToString());
}
Console.WriteLine("全て完了しました。任意のキーを押して終了します...");
Console.ReadKey(true);
}
}
}
これで、Windows上の.NET Frameworkを使って、初めて C# + SQL Server アプリを作成できました。次は、ORM を使って C# アプリを作成します。
.NET Framework で Entity Framework ORM を使用して SQL Server に接続する C# アプリを作成
C# コンソールアプリを新規作成します。
- Visual Studio を起動します。
- [ファイル] -> [新規作成] -> [プロジェクト] を選択します。
- テンプレート一覧から .NET Framework のコンソールアプリを選択します。
- プロジェクト名に SqlServerEFSample と設定し、プロジェクトを作成します。
プロジェクトを作成したら、Entity Framework の依存関係をプロジェクトに追加します。
- [ツール] -> [Nuget パッケージ マネージャー] -> [パッケージ マネージャー コンソール] でパッケージマネージャーコンソールを開きます。
- パッケージマネージャーコンソール上にて、Install-Package EntityFramework と入力し、Enter を押します。
Install-Package EntityFramework
パッケージマネージャコンソールを閉じます。これで、必要なEntity Frameworkの依存関係がプロジェクトに追加されました。このサンプルでは、2つのテーブルを作成します。1つ目は「ユーザー」に関するデータを保持し、もう1つは「タスク」に関するデータを保持するものです。
User.cs を作成します。
- [プロジェクト] -> [クラスの追加] を選択します。
- [名前] の欄に "User.cs" を入力し、[追加] を選択します。
User クラスを定義します。このクラスは、User テーブルに紐づくモデルのクラスです。
書き換えた後、ファイルを保存して閉じます。
この時点では、Task クラスがないためコンパイルエラーとなりますが、問題ありません。
using System;
using System.Collections.Generic;
namespace SqlServerEFSample
{
public class User
{
public int UserId { get; set; }
public String FirstName { get; set; }
public String LastName { get; set; }
public virtual IList<Task> Tasks { get; set; }
public String GetFullName()
{
return this.FirstName + " " + this.LastName;
}
public override string ToString()
{
return "User [id=" + this.UserId + ", name=" + this.GetFullName() + "]";
}
}
}
Task.cs を作成します。
- [プロジェクト] -> [クラスの追加] を選択します。
- [名前] の欄に "Task.cs" を入力し、[追加] を選択します。
Task クラスを定義します。このクラスは、Task テーブルに紐づくモデルのクラスです。
書き換えた後、ファイルを保存して閉じます。
using System;
namespace SqlServerEFSample
{
public class Task
{
public int TaskId { get; set; }
public string Title { get; set; }
public DateTime DueDate { get; set; }
public bool IsComplete { get; set; }
public virtual User AssignedTo { get; set; }
public override string ToString()
{
return "Task [id=" + this.TaskId + ", title=" + this.Title + ", dueDate=" + this.DueDate.ToString() + ", IsComplete=" + this.IsComplete + "]";
}
}
}
EFSampleContext.cs を作成します。
- [プロジェクト] -> [クラスの追加] を選択します。
- [名前] の欄に "EFSampleContext.cs" を入力し、[追加] を選択します。
EFSampleContext クラスを定義します。このクラスは、Entity Framework を使用して .NET オブジェクトを使用してデータのクエリ、挿入、更新、および削除を行うためのクラスです。User クラスと Task クラスを使用しています。
書き換えた後、ファイルを保存して閉じます。
using System;
using System.Data.Entity;
namespace SqlServerEFSample
{
public class EFSampleContext : DbContext
{
public EFSampleContext(string connectionString)
{
Database.SetInitializer<EFSampleContext>(new DropCreateDatabaseAlways<EFSampleContext>());
this.Database.Connection.ConnectionString = connectionString;
}
public DbSet<User> Users { get; set; }
public DbSet<Task> Tasks { get; set; }
}
}
最後に Program.cs を更新します。これまで作成したクラスを使用するための設定を行います。
ユーザー名とパスワードを自分のものに更新することを忘れないでください。
保存してファイルを閉じます。その後、F5 キーを押してプロジェクトをビルドして実行します。
using System;
using System.Linq;
using System.Data.SqlClient;
using System.Collections.Generic;
namespace SqlServerEFSample
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("** Entity Framework と SQL Server を使用した C# CRUD のサンプル **\n");
try
{
// 接続文字列を構築
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();
builder.DataSource = "localhost"; // 接続先の SQL Server インスタンス
builder.UserID = "sa"; // 接続ユーザー名
builder.Password = "your_password"; // 接続パスワード
builder.InitialCatalog = "EFSampleDB";// 接続するデータベース(ここは変えないでください)
// builder.ConnectTimeout = 60000; // 接続タイムアウトの秒数(ms) デフォルトは 15 秒
using (EFSampleContext context = new EFSampleContext(builder.ConnectionString))
{
Console.WriteLine("C# のクラスからデータベーススキーマを作成しました。");
// Create デモ: ユーザーインスタンスを作成し、データベースに保存
User newUser = new User { FirstName = "Anna", LastName = "Shrestinian" };
context.Users.Add(newUser);
context.SaveChanges();
Console.WriteLine("\n作成されたユーザー: " + newUser.ToString());
// Create デモ: タスクインスタンスを作成し、データベースに保存
Task newTask = new Task() { Title = "Ship Helsinki", IsComplete = false, DueDate = DateTime.Parse("04-01-2017") };
context.Tasks.Add(newTask);
context.SaveChanges();
Console.WriteLine("\n作成されたタスク: " + newTask.ToString());
// Association デモ: ユーザーにタスクを割り当てる
newTask.AssignedTo = newUser;
context.SaveChanges();
Console.WriteLine("\n割り当てられたタスク: '" + newTask.Title + "' 割り当てられたユーザー: '" + newUser.GetFullName() + "'");
// Read デモ: ユーザー 'Anna' に割り当てられた未完了のタスクを見つける
Console.WriteLine("\n'Anna' に割り当てられた未完了のタスク:");
var query = from t in context.Tasks
where t.IsComplete == false &&
t.AssignedTo.FirstName.Equals("Anna")
select t;
foreach (var t in query)
{
Console.WriteLine(t.ToString());
}
// Update デモ: タスクの '期限' を変更
Task taskToUpdate = 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年になっているすべてのタスクを削除します。");
DateTime dueDate2016 = DateTime.Parse("12-31-2016");
query = from t in context.Tasks
where t.DueDate < dueDate2016
select t;
foreach (Task t in query)
{
Console.WriteLine("タスクを削除中: " + t.ToString());
context.Tasks.Remove(t);
}
context.SaveChanges();
// 'Delete' 操作の後にタスクを表示 - 0個のタスクがあるはず
Console.WriteLine("\n削除後のタスク:");
List <Task> tasksAfterDelete = (from t in context.Tasks select t).ToList <Task> ();
if (tasksAfterDelete.Count == 0)
{
Console.WriteLine("[なし]");
}
else
{
foreach (Task t in query)
{
Console.WriteLine(t.ToString());
}
}
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
Console.WriteLine("全て完了しました。任意のキーを押して終了します...");
Console.ReadKey(true);
}
}
}
これで、2つ目の C# アプリの作成が終わりました。最後に、SQL Server の Columnstore 機能を使って C# アプリを高速化する方法について学びます。
C# アプリを 100 倍速にする
これまでで基本的なことは理解できたと思います。最後は、SQL Server を使用してアプリをより良くする方法を見てみます。このモジュールでは、カラムストアインデックスの簡単な例と、カラムストアインデックスがどのようにデータ処理速度を向上させるかを確認します。カラムストアインデックスは、従来の列ストアインデックスに比べて、分析ワークロードでは最大 100 倍のパフォーマンス向上、データ圧縮では最大 10 倍のパフォーマンス向上を実現できます。
注意! Service Pack 1 for SQL Server 2016 の導入により、アプリケーション開発に関連するデータベース エンジンの機能が、SQL Server のすべてのエディション(Express から Enterprise まで)で利用できるようになりました。これには、アプリケーションのスループット、レイテンシ、セキュリティーを大幅に向上させる技術革新が含まれています。例としては、このチュートリアルで使用されているインメモリカラムストア、インメモリ OLTP、データ圧縮、テーブルパーティショニング、PolyBase との Hadoop 統合、常時暗号化、行レベルのセキュリティ、データのマスキングなどがあります。お楽しみください。
カラムストアインデックスの機能を確認するために、500 万行のサンプルデータベースとサンプルテーブルを作成し、カラムストアインデックスを追加する前と後の簡単なクエリを実行する C# アプリケーションを作成します。
C# コンソールアプリを新規作成します。
- Visual Studio を起動します。
- [ファイル] -> [新規作成] -> [プロジェクト] を選択します。
- テンプレート一覧から .NET Framework のコンソールアプリを選択します。
- プロジェクト名に SqlServerColumnstoreSample と設定し、プロジェクトを作成します。
Visual Studio で新しい C# Console Application プロジェクトを作成しました。Program.cs の内容を書き換えます。
ユーザー名とパスワードは自分のものに置き換えることを忘れないでください。
保存してファイルを閉じます。その後、F5 キーを押してプロジェクトをビルドして実行します。
using System;
using System.Data.SqlClient;
using System.Text;
namespace SqlServerColumnstoreSample
{
class Program
{
static void Main(string[] args)
{
try
{
Console.WriteLine("*** SQL Server カラムストアのデモ ***");
// 接続文字列の構築
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();
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 (SqlConnection connection = new SqlConnection(builder.ConnectionString))
{
connection.Open();
Console.WriteLine("接続完了。");
// サンプルデータベースの作成
Console.Write("'SampleDB' を再作成しています... ");
String sql = "DROP DATABASE IF EXISTS [SampleDB]; CREATE DATABASE [SampleDB]";
using (SqlCommand command = new SqlCommand(sql, connection))
{
command.CommandTimeout = 60000; // コマンドがタイムアウトする場合は秒数を変更(ms) デフォルトは 30秒
command.ExecuteNonQuery();
Console.WriteLine("完了。");
}
// 'Table_with_5M_rows' テーブルに500万行を挿入
Console.Write("テーブル 'Table_with_5M_rows' に500万行を挿入します。1分ほどかかりますが、お待ちください... ");
StringBuilder sb = new StringBuilder();
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 (SqlCommand command = new SqlCommand(sql, connection))
{
command.CommandTimeout = 60000; // コマンドがタイムアウトする場合は秒数を変更(ms) デフォルトは 30秒
command.ExecuteNonQuery();
Console.WriteLine("データ挿入完了。");
}
// カラムストアインデックスなしで SQL クエリを実行
double elapsedTimeWithoutIndex = SumPrice(connection);
Console.WriteLine("カラムストアインデックスなしのクエリ時間: " + elapsedTimeWithoutIndex + "ms");
// カラムストアインデックスを追加
Console.Write("'Table_with_5M_rows' テーブルにカラムストアインデックスを追加中... ");
sql = "CREATE CLUSTERED COLUMNSTORE INDEX columnstoreindex ON Table_with_5M_rows;";
using (SqlCommand command = new SqlCommand(sql, connection))
{
command.CommandTimeout = 60000; // コマンドがタイムアウトする場合は秒数を変更(ms) デフォルトは 30秒
command.ExecuteNonQuery();
Console.WriteLine("追加完了。");
}
// カラムストアインデックスが追加された後、再度同じ SQL クエリを実行
double elapsedTimeWithIndex = SumPrice(connection);
Console.WriteLine("カラムストアありのクエリ時間: " + elapsedTimeWithIndex + "ms");
// カラムストアインデックスの追加によるパフォーマンス向上を計算
Console.WriteLine("カラムストアインデックスによる性能向上: "
+ Math.Round(elapsedTimeWithoutIndex / elapsedTimeWithIndex) + "x!");
}
Console.WriteLine("全て完了しました。任意のキーを押して終了します...");
Console.ReadKey(true);
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
public static double SumPrice(SqlConnection connection)
{
String sql = "SELECT SUM(Price) FROM Table_with_5M_rows";
long startTicks = DateTime.Now.Ticks;
using (SqlCommand command = new SqlCommand(sql, connection))
{
try
{
command.CommandTimeout = 60000; // コマンドがタイムアウトする場合は秒数を変更(ms) デフォルトは 30秒
var sum = command.ExecuteScalar();
TimeSpan elapsed = TimeSpan.FromTicks(DateTime.Now.Ticks) - TimeSpan.FromTicks(startTicks);
return elapsed.TotalMilliseconds;
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
return 0;
}
}
}
Visual Studio Code の SQL Server 拡張機能を試してみる
Linux、Windows または Docker、Azure SQL Database および Azure SQL Data Warehouse で実行されている SQL Server に対して、VS Code 用の SQL Server 拡張機能を使用して接続を行うことができます。豊富な T-SQL 言語サービス (IntelliSense やキーワード補完など) を利用でき、クエリを入力しながらクエリ実行結果を見ることができます。
おわりに
以上で、「Windows上でSQL Serverを使用してC#アプリを作成する」は終了です。Build an app using SQL Server には、他言語での SQL Server アプリを作成するチュートリアルがあります。ぜひ、他の言語でも試してみてください。