.NET Standard 2.0規格のADO.NETのコーディング
SQL Serverを使う場合、データベースを読み書きする方法は従来のADO.NET 2.0と同様です。DbConnectionを使ってデータベースに接続したら、DbDataAdapterとDataSetでドカっとやり取りするもよし、DbDataReaderで1レコードずつ処理するもよし、です。それらは、.NET Standard 2.0に含まれています。違いは、それらのインスタンスを得るためのDbProviderFactoryの実装が.NET Standardには含まれていない、つまりプラットフォーム依存だということです。
従来のADO.NETとの違い
SQL Serverを使うためのDbProviderFactoryの実装はSqlClientFactoryクラス(System.Data.SqlClient名前空間)なのですが、それは.NET Standard 2.0規格には含まれていません。そこで、アプリのプラットフォーム依存コード側でSqlClientFactoryのインスタンスを取得して、.NET StandardのPCLへ渡すようにします(次の図)。.NET StandardのPCL側は、それを(SqlClientFactoryの継承元クラスである)DbProviderFactoryとして受け取って、ADO.NETのコードを実行します。ここさえ押さえておけば、あとは従来のADO.NETのスキルでコーディングできるでしょう。
また、SqlClientFactoryがプラットフォーム依存ということは、そこから生成されるDbConnectionなどの実装もプラットフォーム依存だというになります。プラットフォームごとのテストが必須だということですね。
なお、UWPではSQL ServerのLocalDBを利用できません(WPFとWindows Formsから利用できることは確認できました。Android/iOS等は未確認)。UWPでローカルにデータベースを置きたいという場合は、SQLite データベースを利用します。
SQL Serverからデータを取得するコード例
それでは実際のコード例を見ていきましょう。まずは、.NET Standard PCL側で、SQL Serverからデータを取得する方から(⇒Northwindクラス)。
前述したように、SqlClientFactoryのインスタンスをプラットフォーム側のコードから受け取るようにします。また、SQLユーザー認証しか使えないプラットフォームもあるので、認証方式も切り替えられるようにしましょう。ここでは、どちらもメソッドの引数として受け取ることにしました。SqlClientFactoryインスタンスは引数そのものとして、また、認証方法の切り替えは省略可能なbool値の引数として受け取ります。
まず、クラスメンバーに接続文字列の定数を2つ定義しておきます(次のコード)。
// SQL ユーザー認証用の接続文字列
private const string CONN_STR_SQLSERVER
= @"Data Source=Win10VM-RS3.corp.BluewaterSoft.jp\SQLEXPRESS;"
+ "Initial Catalog=NORTHWIND;User ID=nwtestuser;Password=;Persist Security Info=False;";
// Windows 認証用の接続文字列(UWPアプリ接続用)
private const string CONN_STR_ENTERPRISE
= @"Data Source=Win10VM-RS3.corp.BluewaterSoft.jp\SQLEXPRESS;"
+ "Initial Catalog=NORTHWIND;Integrated Security=SSPI";
上はあくまでも接続文字列の例です。試すときは実際のものに適宜書き直してください。なお、AndroidやiOSなどからも利用する場合、接続文字列のホスト名(SQL Serverの名前)はDNSで名前解決できなければいけないようです(WindowsはもちろんNetBIOSでの名前解決でもOK)。また、テスト用ということで、SQLユーザー認証のパスワードを空にしていますが、運用環境では絶対にやってはいけないことなのは言うまでもありません。
SQL ServerのCategoriesテーブルから全レコードを抽出して返すメソッドは、次のコードのようになります。SqlClientFactoryのインスタンスをDbProviderFactoryクラスとして受け取り(第1引数)、そこからDbConnectionなどのDBアクセスに必要なオブジェクトを取得しています。また、第2引数に応じて接続文字列を切り替えています。
public static DataTable GetCategories(DbProviderFactory factory,
bool useEnterpriseAuthentication = false)
{
using (DbConnection conn = factory.CreateConnection())
{
// 第2引数に応じて接続文字列を切り替える
conn.ConnectionString = useEnterpriseAuthentication ?
CONN_STR_ENTERPRISE : CONN_STR_SQLSERVER;
using (DbCommand command = factory.CreateCommand())
{
command.Connection = conn;
command.CommandText = "select * from Categories order by CategoryID asc";
using (DbDataAdapter da = factory.CreateDataAdapter())
{
da.SelectCommand = command;
var dt = new DataTable("Categories");
da.Fill(dt);
return dt;
}
}
}
}
データ取得ライブラリを呼び出すコード例
次に、上のGetCategoriesメソッドを呼び出す側のコードです。プラットフォーム依存コードから呼び出さないといけないので、PCLを使ったXamarin.Formsだけは複雑になります(これはXamarin.Formsの「定石」です)。
Windows Forms/WPF
従来のWindowsデスクトップアプリから呼び出すには、たった1行ですみます(⇒Form1.cs/MainWindow.xaml.cs)。SqlClientFactoryクラスのInstance静的フィールドがSqlClientFactoryのインスタンスを返してくれるので、それを引数にして.NET Standard側のメソッドを呼び出すだけです。ただし、.NET Standard 2.0に対応している.NET Framework「本家」はバージョン4.6.1以降(+.NET Core 2 SDK)だということは、忘れないでください。
DataTable dt = UF03StdLib.Northwind.GetCategories(SqlClientFactory.Instance);
Xamarin.Forms
Xamarin.Formsで「PCLプロジェクト」を作った場合(前述の「.NET Standard 2.0規格クラスライブラリを使うには?」で説明した[Code Sharing Strategy]セクションで[.NET Standard]を選んだ場合)、UIのコードビハインドは(.NET Standard 2.0の)PCLに置かれます。つまり、コードビハインドやそこから呼び出すコードでは、プラットフォーム依存であるSqlClientFactoryのインスタンスを作れないのです。
ではどうするかというと、これがXamarin.Formsの「定石」になるのですが、DependencyServiceという一種のDIコンテナを使います。そのコーディング手順は次のようになります。
- PCLプロジェクト(UIをコーディングしているプロジェクト)に、インターフェースを定義します
- プラットフォームごとのプロジェクトで、インターフェースを実装するクラスを記述します。そのクラスには、Dependency属性(Xamarin.Forms名前空間)を付けておきます
- PCLプロジェクト内で、DependencyServiceクラスのGetメソッドを使って、プラットフォームに依存するオブジェクトを取得して使います
まず、PCLプロジェクトに定義するインターフェースです(⇒ISqlClientFactoryDS.cs)。PCLプロジェクトではSqlClientFactoryクラスを書けませんから、その親クラスであるDbProviderFactory抽象クラスを返すように定義します。
public interface ISqlClientFactoryDS
{
DbProviderFactory Instance { get; }
}
次に、プラットフォームごとのDependencyServiceの実装です。名前空間の記述が異なる以外は同じなので、ここではAndroid版のみを載せておきます(⇒SqlClientFactoryDS.cs)。実装は、SqlClientFactory.InstanceをDbProviderFactory抽象クラスとして返すだけです。
[assembly: Xamarin.Forms.Dependency(typeof(XamarinFormsSample.Droid.SqlClientFactoryDS))]
namespace XamarinFormsSample.Droid
{
public class SqlClientFactoryDS : ISqlClientFactoryDS
{
public DbProviderFactory Instance
=> System.Data.SqlClient.SqlClientFactory.Instance;
}
}
最後に、PCLプロジェクトに戻って、データ取得メソッドを呼び出すコードを書きます(⇒MainPage.xaml.cs)。DependencyServiceを使ってプラットフォーム依存のSqlClientFactoryインスタンスを取得し、それを引数にして.NET Standard 2.0のコードを呼び出しています。
// DependencyServiceを使ってSqlClientFactoryインスタンスを取得する var sqlClient = DependencyService.Get<ISqlClientFactoryDS>().Instance; // .NET Standard 2.0 PCLにあるデータ取得メソッドを呼び出す DataTable dt = UF03StdLib.Northwind.GetCategories(sqlClient);
UWP
UWPも、従来のデスクトップアプリと同様に、呼び出すコードは1行だけです(⇒MainPage.xaml.cs)。ここでは第2引数にtrueを渡して、Windows認証を使っています。
DataTable dt = UF03StdLib.Northwind.GetCategories(SqlClientFactory.Instance, true);
ただしUWPでは、Package.appxmanifestの「機能」(capability)に次に示す宣言を追加する必要があります(Xamarin.FormsのUWPアプリも同じ)。
- SQLユーザー認証の場合:プライベートネットワーク(privateNetworkClientServer)
- Windows認証の場合:エンタープライズ認証(enterpriseAuthentication)
enterpriseAuthenticationは、公式ドキュメント「アプリ機能の宣言」の「特殊な用途および制限された用途に関する機能」セクションに「特殊な用途の機能」として分類されており、通常はストアに提出するアプリでは許可されません。組織内で展開するアプリ専用のcapabilityだと考えてください。
また、.NET Core 2.0をサポートしているWindows 10はbuild 16299以降だということも忘れないでください。16299未満で止まってしまったWindows 10 Mobile(本稿執筆時点で15254.248)では動作しません。
まとめ
.NET Standard 2.0規格に含まれるAPIは大幅に増えました。業務ロジックを.NET Standard 2.0のPCLにまとめておけば、それはWindows Forms/WPF/ASP.NETといった従来からの.NET Frameworkのコードから利用できるだけでなく、UWP/Xamarin/ASP.NET Coreといった新しいアプリのコードからも利用できます(Xamarin.AndroidやXamarin.iOSなどからも!)。新しく業務ロジックを開発するときは、.NET Standardで書けないか検討してみてください。
また、UWPアプリでも、ついにSQL Serverへ直接アクセスできるようになりました。
UWPアプリに興味が湧いてきた人へ
Windowsデスクトップ用アプリの開発はWindows Formsなら経験があるけど、ちょっとUWPアプリ開発も気になってきたという方へ。ぴったりの電子書籍を書きました!
『UWP アプリ開発 101 第2版: Windows Forms 開発者のための C# / XAML による UWP アプリ開発入門』 (BluewaterSoft 2017/7/1)
・Kindle版とUWPアプリ版があります。
・どちらも無料で最初の方を読めるので、とりあえず試し読みをしてみてください。
