メソッドチェーンでアスペクト指向プログラミング

透過プロキシでアスペクト指向プログラミング (1) では、ログ出力やデータベース トランザクションなどの横断的関心事を、

public class NorthwindBusiness : MarshalByRefObject
{
    [TraceLog]
    [TransactionScope]
    public short SelectUnitsInStock()
    {
    }
}

のように、属性として記述することができました。
今回は属性を使わずに、LINQ と同様にメソッドチェーンを使って横断的関心事を記述してみます。

まず、戻り値を持つ (Func<TResult> に相当する) 処理を IProxyable<TResult> インターフェイスとして定義して、
後ろにメソッドチェーンを追加することで処理を上書きできるようにします。

これを実行すると出力は次のようになり、元の処理の前後に処理が追加されていることがわかります。

Before
Body
After

次に、戻り値を持たない (Action に相当する) 処理を表す IProxyable インターフェイスを、
IProxyable<TResult> インターフェイスの特別な場合とみなして継承させます。

あとは、ログ出力やデータベース トランザクションなどの横断的関心事を拡張メソッドとして作成すれば完成です。

実行結果です:

ProxyableConsole

 

使い道としては、

  • .NET で透過プロキシを使いたくないとき (処理速度を上げたい、など)
  • .NET の属性のような仕組みを持たない別のプラットフォーム

などの場合が考えられるでしょう。

前回:透過プロキシでアスペクト指向プログラミング (2)

作成したサンプル
ProxyableConsole (GitHub)

バージョン情報
C# 7.0
.NET Framework 4.5

参照
アスペクト指向プログラミング

広告
カテゴリー: .NET Framework, データベース. タグ: . 1 Comment »

透過プロキシでアスペクト指向プログラミング (2)

前回の記事で、NorthwindBusiness クラスの透過プロキシを生成するときに、

var nw = CrossCuttingProxy.CreateProxy<NorthwindBusiness>();

というコードを記述していましたが、
MarshalByRefObject の代わりに ContextBoundObject クラスを継承させると、通常のコンストラクターを利用することができます。
ただし、クラスに属性を付ける必要があります。
次のように実装します。

以上で、

var nw = new NorthwindBusiness();

と記述できるようになりました。
なお、上記のコードには現れていませんが、
コンストラクターが呼び出されたときに、CrossCuttingProxy クラスの Invoke メソッドが呼び出されます。

 

前回:透過プロキシでアスペクト指向プログラミング (1)
次回:メソッドチェーンでアスペクト指向プログラミング

作成したサンプル
CrossCuttingConsole (GitHub)

バージョン情報
C# 7.0
.NET Framework 4.5

参照
RealProxy クラス
アスペクト指向プログラミング (Wikipedia)

透過プロキシでアスペクト指向プログラミング (1)

前回のインターフェイスに対する透過プロキシでは、RealProxy クラスを利用してインターフェイスに対する透過プロキシを生成し、
その実装クラスが存在していなくてもメソッドに処理を割り当てることができました。

改めて、透過プロキシ (transparent proxy) の主な特徴を整理すると次のようになります。

  • 対象となる型は、MarshalByRefObject を継承したクラス、またはインターフェイス
  • 各メンバーが呼び出されたときの挙動を上書きできる

今回は MarshalByRefObject を継承したクラスの既存の処理を透過プロキシで拡張して、
アスペクト指向プログラミング (AOP) を実践してみます。

一般的にアプリケーション設計においては、
ログ出力やデータベース トランザクションなど、いろいろなビジネス ロジックに共通する横断的関心事があります。
例えばデータベース トランザクションであれば、以前にファントム リードとその解決方法などで書いている通り、
ビジネス ロジックごとに TransactionScope に関する同じようなコードを繰り返し記述することになります。
この部分をアスペクト (側面) として分離し、属性として記述できるようにすることで再利用性を高めることを目指します。

まず次のコードで示す通り、RealProxy を継承した CrossCuttingProxy クラスと、アスペクトを表す属性を定義します。

ログ出力を表す TraceLogAttribute クラスとデータベース トランザクションを表す TransactionScopeAttribute クラスを用意し、
既存の処理の前後に割り込むようにしてそれぞれの処理を追加しています。

以上のようにすれば、ビジネス ロジックを次のように記述するだけで済むようになります。

実行結果です。ログが出力されています:

CrossCuttingConsole

 

前回:インターフェイスに対する透過プロキシ
次回:透過プロキシでアスペクト指向プログラミング (2)

作成したサンプル
CrossCuttingConsole (GitHub)

バージョン情報
C# 7.0
.NET Framework 4.5

参照
RealProxy クラス
アスペクト指向プログラミング (Wikipedia)

Azure Table の検索条件を LINQ で指定する

Azure Storage の SDK (Windows Azure Storage) を利用して .NET のクライアントから Table のデータを取得する際に、
検索条件を指定しようとすると、通常の実装では次のようなコードになり少し複雑です。
この例では、フィルター条件を 2 つ指定しています。


var query = new TableQuery<Person>()
    .Where(TableQuery.CombineFilters(
        TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, "2015"),
        TableOperators.And,
        TableQuery.GenerateFilterConditionForInt("Age", QueryComparisons.LessThan, 20)));

var result = PeopleTable.ExecuteQuery(query).ToArray();


ここで、Person  は TableEntity を継承したクラスで、PeopleTable は CloudTable 型のオブジェクトです。

結局、上記の Where メソッドに渡されるのは、

(PartitionKey eq ‘2015’) and (Age lt 20)

という文字列になります。
これなら string.Format メソッドでもよいのではないのかという気もしますが、
プログラミングのミスを防ぐためには、フィルターや射影などの検索条件は LINQ で指定したいところです。

検索条件を LINQ で指定する方法として TableServiceContext クラスを使う方法もあるようですが、
現在は Obsolete 属性が指定されており、非推奨となっています。

とはいえ、自力で IQueryable<T> を実装するのも骨が折れるので、
ここでは簡易的に、TableQuery<T> の拡張メソッドとして Select および Where メソッドを実装していきます。
ラムダ式で指定された検索条件を式ツリーとして受け取って解析し、動的にクエリを生成します。

このような TableHelper クラスを実装することで、Azure Table の検索条件を LINQ で指定できるようになります。
ただし、文字列の不等式については、String クラスの演算子として不等号が定義されていないため、

p.LastName >= "W"

と書くことができず、

p.LastName.CompareTo("W") >= 0

のようにせざるを得ませんでした。

 

作成したサンプル
ExpressionsConsole (GitHub)

バージョン情報
Windows Azure Storage 6.0.0

参照
Windows Azure Storage
Expression<TDelegate> クラス
TableQuery<TElement> Class

Windows Azure Storage Extensions
テーブル サービスに対する LINQ クエリの作成 (古い形式)
TableServiceContext Class (古い形式)

Visual Studio から Windows Azure にデプロイする

データベースと連携する Web アプリケーションを、
Visual Studio の発行コマンドで Windows Azure Web サイトにデプロイするための手順を示します。

 

■ Windows Azure SQL データベースを作成する

Windows Azure の管理ポータルで、新規の SQL データベースを作成します。
[カスタム作成] を選択します。

image

照合順序およびサーバーを選択します。
SQL データベースは、「SQL データベース サーバー」の中に作成されるという構成になるため、
まだ存在していない場合は作成しなければなりません。

image

SQL データベース サーバーを新しく作成する場合のみ、ログインや地域を設定します。

image

 

作成が完了したら、ダッシュボードで [接続文字列の表示] をクリックして、ADO.NET 用の接続文字列を入手しておきます。

image

次と同等の接続文字列が得られるはずです。

Data Source=(servername).database.windows.net;Initial Catalog=(dbname);User ID=(userid);Password=(password);Connect Timeout=30;Encrypt=True

 

■ Windows Azure Web サイトを作成する

Windows Azure の管理ポータルで、新規の Web サイトを [簡易作成] で作成します。

作成が完了したら、ダッシュボードで [発行プロファイルのダウンロード] をクリックして、
発行プロファイル (.PublishSettings) を入手しておきます。

image

 

■ Web アプリケーションを発行する

Visual Studio で、Web アプリケーション プロジェクトを作成します。
ここでは例として、[ASP.NET MVC 4 Web アプリケーション] の [シングル ページ アプリケーション] を作成します。
すると、データベースを利用する Todo List という Web アプリケーションが作成されます。

プロジェクトを右クリックして [発行] をクリックします。

image

[インポート] をクリックして、入手しておいた発行プロファイルを指定します。

image

入手しておいた接続文字列を指定します。
[発行] をクリックします。

image

 

以上の手順で Windows Azure Web サイトにデプロイされ、
データベースと連携した Web アプリケーションが利用可能になります。

image

 

■ Windows Azure Web サイト側で接続文字列を指定する方法

上記の手順では発行の際に接続文字列を指定しましたが、
Windows Azure Web サイトに接続文字列を最初から設定しておくこともできます。

このように設定するにはまず、Windows Azure Web サイトを [カスタム作成] で作成し、利用する SQL データベースを選択します。
あとは、Web アプリケーションで利用する接続文字列の名前 (Web.config で定義されているもの) と、
SQL データベースのログイン情報を入力します。

image

 

すると、SQL データベースが Web サイトの [リンク済みリソース] として構成されます。

image

[構成] タブに、接続文字列が登録されます。

image

このように構成されていると、Visual Studio から発行する際に接続文字列を指定する必要がなくなります。
こちらの方法ではデータベースのログイン情報を持ち出さなくて済むため、より安全であると言えるでしょう。

Web サイトと SQL データベースのリンクは、あとからでも [リンク済みリソース] タブで追加できます。
また、SQL データベースがリンク済みリソースとして登録されていなくても、
[構成] タブの接続文字列に直接設定することもできます。
ただし、リンク済みリソースに登録しておくと、Web サイトと同時にデータベースなども監視できるようになるため便利です。

 

データベースと連携する Web アプリケーションで継続的インテグレーションを構成する方法
Windows Azure と Visual Studio Online で継続的インテグレーション (2)

バージョン情報
Visual Studio 2012

カテゴリー: クラウド, データベース. タグ: . 2 Comments »

Windows Phone で既存のデータベース ファイルを使う

(Windows Phone Advent Calendar 2011 の 18 日目です。)

Windows Phone 7.5 アプリケーションでは、ローカル データベースとして SQL Server Compact を利用できます。
データベース ファイルの拡張子は .sdf です。
アプリケーションから接続するには LINQ to SQL を利用します。

今回は、Database First で既存のデータベース ファイル (.sdf) からコードを自動生成し、
Windows Phone 7.5 アプリケーションで動作させるまでの一連の手順について紹介します。

ちなみに、MSDN ライブラリの Windows Phone のローカル データベースの概要を見ると、
Code First な方法は載っているのですが Database First な方法は載っていません。
なお、Code First な方法についての日本語の解説は、
はぢめてのWindows Phone 7でのデータベース(Linq to Sql) などを参照するとよいでしょう。

■ データベース ファイルを準備する

「既存のデータベース ファイル (.sdf)」と書きましたが、
これまでに Windows Phone プラットフォーム以外で使用されていたものや、新規に作成したものも含みます。

ただし、方法: Windows Phone アプリケーションと共に参照データベースを展開する
「重要な注」に記述されている通り、デスクトップで作成された SQL Server Compact データベースを
Windows Phone アプリケーションで動作させることは公式にサポートされていません

したがって、自己責任で利用しましょう。

以下に、SQL Server Compact のデータベース ファイルを新規に作成する方法を簡単に示します。

(1) SQL Server Management Studio を使う場合

[サーバーへの接続] ダイアログの [サーバーの種類] で SQL Server Compact を選択し、
[データベース ファイル] で <新しいデータベース…> を選択します。
すると別のダイアログが表示されるので、ファイル名を指定します。

SQL Server Management Studio でデータベース ファイルを作成

あとはこのデータベース ファイルに接続してテーブルを定義します。
ただし、SQL Server の場合とは異なり、データベース ダイアグラムは利用できません。

SQL Server Management Studio でデータを作成

(2) Visual Studio を使う場合

Windows Phone 系のプロジェクトからでは作成できません。
Windows アプリケーション系のプロジェクトから次の図のように作成します。

Visual Studio でデータベース ファイルを作成

あとはサーバー エクスプローラーからデータベース ファイルに接続してテーブルを定義します。
コンテキスト メニューの [テーブル データの表示] からデータをグリッド形式で操作することもできます。
SQL を利用することもできます。

Visual Studio でデータを作成

さて、初期データを外部から一括で取り込みたい場合についてですが、
SQL Server Compact データベースには bcp ユーティリティでデータを一括でインポートできません。
次のような方法が考えられます。

  • Integration Services を利用する (未検証)
  • 後述する Proxy コードを生成してからコンソール アプリケーションを作成してデータを投入する
  • INSERT 文を生成して Management Studio や Visual Studio で実行する

■ データベース ファイルからコードを生成する

SQL Server などのデータベースを対象とした開発では、
Visual Studio で既存のデータベースから Proxy クラスを自動生成するという方法がよく用いられています。
しかし残念ながら、Visual Studio には .sdf ファイルから LINQ to SQL 用のコードを自動生成する機能がありません。

そこで、SqlMetal.exe というコマンドライン ツールを利用します。
これを使うと、.sdf ファイルから LINQ to SQL 用のコードを自動生成できます。
SqlMetal.exe は Windows SDK に含まれています (Visual Studio とともにインストールされます)。

例えば、次に示すようなバッチ ファイルを作成します。
SqlMetal.exe の引数には、.sdf ファイルのパス、作成されるコード ファイルのパス、
型指定された DataContext の名前空間とクラス名を指定します。絶対パスおよび相対パスが使えます。

CreateDataContext.bat


rem 64 bit OS の場合
set SqlMetal="C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\SqlMetal.exe"
%SqlMetal% Test.sdf /code:TestDataContext.cs /namespace:PhoneApp1 /context:TestDataContext /pluralize


このバッチ ファイルを実行すると TestDataContext.cs が生成されます。
ただし、本来これは .NET Framework 用のコードであり、Windows Phone 用のプロジェクトでは
型指定された DataContext クラスの一部のコンストラクターがコンパイル エラーとなるので、それらを手動で削除します。

TestDataContext.cs


// ・・・ (省略) ・・・
public partial class TestDataContext : System.Data.Linq.DataContext
{
// ・・・ (省略) ・・・

    public TestDataContext(string connection) :
            base(connection, mappingSource)
    {
        OnCreated();
    }

/* IDbConnection は存在しません。
    public TestDataContext(System.Data.IDbConnection connection) :
            base(connection, mappingSource)
    {
        OnCreated();
    }
*/

    public TestDataContext(string connection, System.Data.Linq.Mapping.MappingSource mappingSource) :
            base(connection, mappingSource)
    {
        OnCreated();
    }

/* IDbConnection は存在しません。
    public TestDataContext(System.Data.IDbConnection connection, System.Data.Linq.Mapping.MappingSource mappingSource) :
            base(connection, mappingSource)
    {
        OnCreated();
    }
*/

    public System.Data.Linq.Table<Company> Companies
    {
        get
        {
            return this.GetTable<Company>();
        }
    }
        
    public System.Data.Linq.Table<Line> Lines
    {
        get
        {
            return this.GetTable<Line>();
        }
    }

// ・・・ (省略) ・・・


その他の注意点
(1) Visual Studio コマンド プロンプトを利用すれば、SqlMetal.exe の絶対パスは必要ありません。
(2) このコードは、同じテーブル構造を持った SQL Server などに接続する場合にも利用できます。
     コンストラクターの引数に渡す接続文字列を変えるだけです。
(3) 引数に /pluralize を付加すると、単語の単数形・複数形を適切に判断してコードを生成します。
     上記の例では、Companies テーブルのエンティティの名前が Company になっています。

■ データベース ファイルを配置する

上記で準備したデータベース ファイル (.sdf) とコード ファイル (.cs) を Windows Phone アプリケーションの
プロジェクトに追加します (自動的にデータベース ファイルの [ビルド アクション] がコンテンツ、
コード ファイルの [ビルド アクション] がコンパイルになるはずです)。
また、プロジェクトの参照設定に System.Data.Linq を追加します。

初回実行時にこのデータベース ファイルを分離ストレージに配置するために、
アプリケーションの Launching イベントの処理を次のように追加します。
なお、接続文字列が特殊なので注意が必要です。isostore:/ のあとに分離ストレージ内でのパスを指定します。

App.xaml.cs


    // ・・・ (省略) ・・・

    private static readonly Uri TestContentUri = new Uri("Test.sdf", UriKind.Relative);
    private const string TestStoragePath = "Test.sdf";
    private const string TestConnectionString = "Data Source=isostore:/Test.sdf";

    // (たとえば、[スタート] メニューから) アプリケーションが起動するときに実行されるコード
    // このコードは、アプリケーションが再アクティブ化済みの場合には実行されません
    private void Application_Launching(object sender, LaunchingEventArgs e)
    {
        using (var context = new TestDataContext(TestConnectionString))
        {
            if (!context.DatabaseExists())
            {
                using (var input = Application.GetResourceStream(TestContentUri).Stream)
                using (var storage = IsolatedStorageFile.GetUserStoreForApplication())
                using (var output = storage.CreateFile(TestStoragePath))
                {
                    input.CopyTo(output);
                }
            }
        }
    }

    // ・・・ (省略) ・・・


■ データベース ファイルに接続する

ここまでできれば、もう難しいことはありません。
データベースに接続してデータを操作するには LINQ to SQL を利用します。


    private const string TestConnectionString = "Data Source=isostore:/Test.sdf";

    public static Company[] GetCompanies()
    {
        using (var context = new TestDataContext(TestConnectionString))
        {
            return context.Companies.ToArray();
        }
    }


バージョン情報
Windows Phone OS 7.1
Visual Studio 2010 SP1

参照
Windows Phone のローカル データベースの概要 (MSDN)
方法: Windows Phone アプリケーションと共に参照データベースを展開する (MSDN)
SqlMetal.exe (コード生成ツール) (MSDN)
Using SqlMetal to generate Windows Phone Mango Local Database classes (WindowsPhoneGeek)
はぢめてのWindows Phone 7でのデータベース(Linq to Sql) (neue cc)
Local Database for Windows Phone (雑記 – otherwise)

ファントム リードとその解決方法 その 2

前回のファントム リードとその解決方法 その 1 では SqlCommand クラスを利用したコードを示しましたが、
今回は ADO.NET Entity Framework を利用したコードを示します。
ただし、特に説明する部分はないので、ステップ 2 (Serializable) の場合のみ示します。

なお、下記のコードを記述する前に、ロスト アップデートとその解決方法 その 3 と同様に
Visual Studio で ADO.NET Entity Data Model を作成しておきます。


using System;
using System.Linq;
using System.Threading.Tasks;
using System.Transactions;

namespace PhantomRead
{
    class Program
    {
        private static readonly TransactionOptions transactionOptions = new TransactionOptions { IsolationLevel = IsolationLevel.Serializable };

        private static int phantomCount;

        static void Main(string[] args)
        {
            // 挿入と読み取りを並列に実行します。
            Parallel.Invoke(
                () =>
                {
                    for (int i = 0; i < 200; i++)
                    {
                        InsertCategory();
                    }
                },
                () =>
                {
                    for (int i = 0; i < 100; i++)
                    {
                        SelectCategoriesCount();
                    }
                }
            );
        }

        /// <summary>
        /// Northwind データベースの Categories テーブルの行を挿入します。
        /// </summary>
        private static void InsertCategory()
        {
            using (var scope = new TransactionScope(TransactionScopeOption.Required, transactionOptions))
            using (var context = new NorthwindEntities())
            {
                // カテゴリを追加します。
                context.AddToCategories(Category.CreateCategory(0, "New Category"));
                context.SaveChanges();

                // コミットします。
                scope.Complete();
            }
        }

        /// <summary>
        /// Northwind データベースの Categories テーブルの行数を読み取ります。
        /// </summary>
        private static void SelectCategoriesCount()
        {
            using (var scope = new TransactionScope(TransactionScopeOption.Required, transactionOptions))
            using (var context = new NorthwindEntities())
            {
                // カテゴリの数を読み取ります。
                int count1 = context.Categories.Count();
                int count2 = context.Categories.Count();

                if (count1 != count2)
                {
                    Console.WriteLine("{0} = {1} ({2} 回目)", count1, count2, ++phantomCount);
                }
                else
                {
                    Console.WriteLine("{0} = {1}", count1, count2);
                }
            }
        }
    }
}


注意点
(1) CategoryID 列には IDENTITY が指定されているため、
     新しい Category オブジェクトを作成するときに指定する CategoryID は任意の値でかまいません (上記では 0)。
     SaveChanges メソッドが呼ばれると、テーブルに行を挿入するとともに、採番された CategoryID を取得します。
     具体的には、次と等価な SQL が送信されます。

insert [Categories] ([CategoryName], [Description], [Picture]) values (N’New Category’, null, null)
select [CategoryID] from [Categories] where @@ROWCOUNT > 0 and [CategoryID] = scope_identity()

バージョン情報
.NET Framework 4
SQL Server 2008, 2008 R2