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

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

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

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

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

using System;
namespace ProxyableConsole
{
public interface IProxyable<TResult>
{
TResult Execute();
}
class BodyProxyable<TResult> : IProxyable<TResult>
{
Func<TResult> execute;
public BodyProxyable(Func<TResult> execute)
{
this.execute = execute ?? throw new ArgumentNullException(nameof(execute));
}
public TResult Execute() => execute();
}
class AspectProxyable<TResult> : IProxyable<TResult>
{
IProxyable<TResult> source;
Func<Func<TResult>, TResult> execute;
public AspectProxyable(IProxyable<TResult> source, Func<Func<TResult>, TResult> execute)
{
this.source = source ?? throw new ArgumentNullException(nameof(source));
this.execute = execute ?? throw new ArgumentNullException(nameof(execute));
}
public TResult Execute() => execute(source.Execute);
}
public static class Proxyable
{
public static IProxyable<TResult> Body<TResult>(Func<TResult> execute) => new BodyProxyable<TResult>(execute);
public static IProxyable<TResult> Aspect<TResult>(this IProxyable<TResult> source, Func<Func<TResult>, TResult> execute) => new AspectProxyable<TResult>(source, execute);
}
}
using System;
namespace ProxyableConsole
{
class Program
{
static void Main(string[] args)
{
var result = Proxyable.Body(() =>
{
Console.WriteLine("Body");
return 123;
})
.Aspect(f =>
{
Console.WriteLine("Before");
var r = f();
Console.WriteLine("After");
return r;
})
.Execute();
}
}
}
view raw Program.cs hosted with ❤ by GitHub

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

Before
Body
After

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

using System;
namespace ProxyableConsole
{
public interface IProxyable : IProxyable<object>
{
new void Execute();
}
class BodyProxyable : BodyProxyable<object>, IProxyable
{
public BodyProxyable(Action execute) : base(() =>
{
execute?.Invoke();
return null;
})
{
}
void IProxyable.Execute() => Execute();
}
class AspectProxyable : AspectProxyable<object>, IProxyable
{
public AspectProxyable(IProxyable source, Action<Action> execute) : base(source, f =>
{
execute?.Invoke(() => f());
return null;
})
{
}
void IProxyable.Execute() => Execute();
}
public static class Proxyable
{
public static IProxyable<TResult> Body<TResult>(Func<TResult> execute) => new BodyProxyable<TResult>(execute);
public static IProxyable Body(Action execute) => new BodyProxyable(execute);
public static IProxyable<TResult> Aspect<TResult>(this IProxyable<TResult> source, Func<Func<TResult>, TResult> execute) => new AspectProxyable<TResult>(source, execute);
public static IProxyable Aspect(this IProxyable source, Action<Action> execute) => new AspectProxyable(source, execute);
}
}
view raw IProxyable.cs hosted with ❤ by GitHub
using System;
namespace ProxyableConsole
{
class Program
{
static void Main(string[] args)
{
Proxyable.Body(() => Console.WriteLine("Body"))
.Aspect(a =>
{
Console.WriteLine("Before");
a();
Console.WriteLine("After");
})
.Execute();
}
}
}
view raw Program.cs hosted with ❤ by GitHub

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

using System;
using System.Transactions;
namespace ProxyableConsole
{
class Program
{
static void Main(string[] args)
{
var units = NorthwindBusiness.SelectUnitsInStock();
NorthwindBusiness.InsertCategory("Books");
try
{
NorthwindBusiness.ErrorTest();
}
catch (Exception)
{
}
}
}
public static class NorthwindBusiness
{
public static short SelectUnitsInStock() =>
Proxyable.Body(() =>
{
Console.WriteLine("SelectUnitsInStock");
// cf. https://sakapon.wordpress.com/2011/10/02/dirtyread2/
using (var context = new NorthwindEntities())
{
var product = context.Products.Single(p => p.ProductID == 1);
return product.UnitsInStock;
}
})
.TransactionScope()
.TraceLog()
.Execute();
public static void InsertCategory(string name) =>
Proxyable.Body(() =>
{
Console.WriteLine("InsertCategory");
// cf. https://sakapon.wordpress.com/2011/12/14/phantomread2/
using (var context = new NorthwindEntities())
{
context.AddToCategories(Category.CreateCategory(0, name));
context.SaveChanges();
}
})
.TransactionScope(IsolationLevel.Serializable)
.TraceLog()
.Execute();
public static void ErrorTest() =>
Proxyable.Body(() => throw new InvalidOperationException("This is an error test."))
.TraceLog()
.Execute();
}
}
view raw Program.cs hosted with ❤ by GitHub
using System;
using System.Runtime.CompilerServices;
using System.Transactions;
namespace ProxyableConsole
{
public static class ProxyableExtension
{
public static IProxyable<TResult> TraceLog<TResult>(this IProxyable<TResult> source, [CallerMemberName]string caller = "") =>
source.Aspect(f =>
{
try
{
Console.WriteLine($"Begin: {caller}");
var result = f();
Console.WriteLine($"Success: {caller}");
return result;
}
catch (Exception ex)
{
Console.WriteLine($"Error: {caller}");
Console.WriteLine(ex);
throw;
}
});
public static IProxyable<TResult> TransactionScope<TResult>(this IProxyable<TResult> source,
IsolationLevel isolationLevel = IsolationLevel.ReadCommitted,
double timeoutInSeconds = 30,
TransactionScopeOption scopeOption = TransactionScopeOption.Required)
{
var transactionOptions = new TransactionOptions
{
IsolationLevel = isolationLevel,
Timeout = TimeSpan.FromSeconds(timeoutInSeconds),
};
return source.Aspect(f =>
{
using (var scope = new TransactionScope(scopeOption, transactionOptions))
{
var result = f();
scope.Complete();
return result;
}
});
}
}
}

実行結果です:

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 クラスを継承させると、通常のコンストラクターを利用することができます。
ただし、クラスに属性を付ける必要があります。
次のように実装します。

using System;
using System.Runtime.Remoting.Proxies;
namespace CrossCuttingConsole
{
public sealed class CrossCuttingAttribute : ProxyAttribute
{
public override MarshalByRefObject CreateInstance(Type serverType)
{
return (MarshalByRefObject)new CrossCuttingProxy(serverType).GetTransparentProxy();
}
}
}
using System;
using System.Transactions;
namespace CrossCuttingConsole
{
[CrossCutting]
public class NorthwindBusiness : ContextBoundObject
{
[TraceLog]
[TransactionScope]
public short SelectUnitsInStock()
{
Console.WriteLine("SelectUnitsInStock");
// cf. https://sakapon.wordpress.com/2011/10/02/dirtyread2/
using (var context = new NorthwindEntities())
{
var product = context.Products.Single(p => p.ProductID == 1);
return product.UnitsInStock;
}
}
[TraceLog]
[TransactionScope(IsolationLevel.Serializable)]
public void InsertCategory(string name)
{
Console.WriteLine("InsertCategory");
// cf. https://sakapon.wordpress.com/2011/12/14/phantomread2/
using (var context = new NorthwindEntities())
{
context.AddToCategories(Category.CreateCategory(0, name));
context.SaveChanges();
}
}
public int PropertyTest { get; [TraceLog]set; }
[TraceLog]
public void ErrorTest()
{
throw new InvalidOperationException("This is an error test.");
}
}
}
view raw NorthwindBusiness.cs hosted with ❤ by GitHub
using System;
namespace CrossCuttingConsole
{
class Program
{
static void Main(string[] args)
{
var nw = new NorthwindBusiness();
var units = nw.SelectUnitsInStock();
nw.InsertCategory("Books");
nw.PropertyTest = 123;
try
{
nw.ErrorTest();
}
catch (Exception)
{
}
}
}
}
view raw Program.cs hosted with ❤ by GitHub

以上で、

var nw = new NorthwindBusiness();

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

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

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

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

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

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

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

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

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

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

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

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

using System;
using System.Runtime.Remoting.Messaging;
using System.Transactions;
namespace CrossCuttingConsole
{
[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public abstract class AspectAttribute : Attribute
{
public abstract IMethodReturnMessage Invoke(Func<IMethodReturnMessage> baseInvoke, MarshalByRefObject target, IMethodCallMessage methodCall);
}
public class TraceLogAttribute : AspectAttribute
{
public override IMethodReturnMessage Invoke(Func<IMethodReturnMessage> baseInvoke, MarshalByRefObject target, IMethodCallMessage methodCall)
{
var methodInfo = $"{methodCall.MethodBase.DeclaringType.Name}.{methodCall.MethodName}({string.Join(", ", methodCall.InArgs)})";
Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff}: Begin: {methodInfo}");
var result = baseInvoke();
if (result.Exception == null)
Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff}: Success: {methodInfo}");
else
{
Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff}: Error: {methodInfo}");
Console.WriteLine(result.Exception);
}
return result;
}
}
public class TransactionScopeAttribute : AspectAttribute
{
public TransactionOptions TransactionOptions { get; }
public TransactionScopeOption TransactionScopeOption { get; }
public TransactionScopeAttribute(
IsolationLevel isolationLevel = IsolationLevel.ReadCommitted,
double timeoutInSeconds = 30,
TransactionScopeOption scopeOption = TransactionScopeOption.Required)
{
TransactionOptions = new TransactionOptions
{
IsolationLevel = isolationLevel,
Timeout = TimeSpan.FromSeconds(timeoutInSeconds),
};
TransactionScopeOption = scopeOption;
}
public override IMethodReturnMessage Invoke(Func<IMethodReturnMessage> baseInvoke, MarshalByRefObject target, IMethodCallMessage methodCall)
{
using (var scope = new TransactionScope(TransactionScopeOption, TransactionOptions))
{
var result = baseInvoke();
scope.Complete();
return result;
}
}
}
}
view raw AspectAttribute.cs hosted with ❤ by GitHub
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Activation;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Proxies;
namespace CrossCuttingConsole
{
public class CrossCuttingProxy : RealProxy
{
public static T CreateProxy<T>() where T : MarshalByRefObject, new()
{
return (T)new CrossCuttingProxy(new T()).GetTransparentProxy();
}
public MarshalByRefObject Target { get; private set; }
// For non-ContextBoundObject.
internal CrossCuttingProxy(MarshalByRefObject target) : base(target.GetType())
{
Target = target;
}
// For ContextBoundObject.
internal CrossCuttingProxy(Type classToProxy) : base(classToProxy)
{
}
public override IMessage Invoke(IMessage msg)
{
if (msg is IConstructionCallMessage constructionCall)
return InvokeConstructor(constructionCall);
if (msg is IMethodCallMessage methodCall)
return InvokeMethod(methodCall);
throw new InvalidOperationException();
}
IConstructionReturnMessage InvokeConstructor(IConstructionCallMessage constructionCall)
{
var constructionReturn = InitializeServerObject(constructionCall);
Target = GetUnwrappedServer();
SetStubData(this, Target);
return constructionReturn;
}
IMethodReturnMessage InvokeMethod(IMethodCallMessage methodCall)
{
Func<IMethodReturnMessage> baseInvoke = () => RemotingServices.ExecuteMessage(Target, methodCall);
var newInvoke = methodCall.MethodBase.GetCustomAttributes<AspectAttribute>(true)
.Reverse()
.Aggregate(baseInvoke, (f, a) => () => a.Invoke(f, Target, methodCall));
return newInvoke();
}
}
}
view raw CrossCuttingProxy.cs hosted with ❤ by GitHub

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

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

using System;
using System.Transactions;
namespace CrossCuttingConsole
{
public class NorthwindBusiness : MarshalByRefObject
{
[TraceLog]
[TransactionScope]
public short SelectUnitsInStock()
{
Console.WriteLine("SelectUnitsInStock");
// cf. https://sakapon.wordpress.com/2011/10/02/dirtyread2/
using (var context = new NorthwindEntities())
{
var product = context.Products.Single(p => p.ProductID == 1);
return product.UnitsInStock;
}
}
[TraceLog]
[TransactionScope(IsolationLevel.Serializable)]
public void InsertCategory(string name)
{
Console.WriteLine("InsertCategory");
// cf. https://sakapon.wordpress.com/2011/12/14/phantomread2/
using (var context = new NorthwindEntities())
{
context.AddToCategories(Category.CreateCategory(0, name));
context.SaveChanges();
}
}
public int PropertyTest { get; [TraceLog]set; }
[TraceLog]
public void ErrorTest()
{
throw new InvalidOperationException("This is an error test.");
}
}
}
view raw NorthwindBusiness.cs hosted with ❤ by GitHub
using System;
namespace CrossCuttingConsole
{
class Program
{
static void Main(string[] args)
{
var nw = CrossCuttingProxy.CreateProxy<NorthwindBusiness>();
var units = nw.SelectUnitsInStock();
nw.InsertCategory("Books");
nw.PropertyTest = 123;
try
{
nw.ErrorTest();
}
catch (Exception)
{
}
}
}
}
view raw Program.cs hosted with ❤ by GitHub

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

CrossCuttingConsole

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

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

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

参照
RealProxy クラス
アスペクト指向プログラミング (Wikipedia)
RealProxy クラスによるアスペクト指向プログラミング (MSDN Magazine)

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 メソッドを実装していきます。
ラムダ式で指定された検索条件を式ツリーとして受け取って解析し、動的にクエリを生成します。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.WindowsAzure.Storage.Table;
namespace ExpressionsConsole
{
public static class TableHelper
{
static readonly MethodInfo String_CompareTo = typeof(string).GetMethod("CompareTo", new[] { typeof(string) });
public static TableQuery<TElement> Select<TElement>(this TableQuery<TElement> query, Expression<Func<TElement, object>> selector)
{
if (query == null) throw new ArgumentNullException("query");
if (selector == null) throw new ArgumentNullException("selector");
var @new = selector.Body as NewExpression;
if (@new == null) throw new InvalidOperationException();
query.SelectColumns = @new.Constructor.GetParameters().Select(p => p.Name).ToArray();
return query;
}
public static TableQuery<TElement> Where<TElement>(this TableQuery<TElement> query, Expression<Func<TElement, bool>> predicate)
{
if (query == null) throw new ArgumentNullException("query");
if (predicate == null) throw new ArgumentNullException("predicate");
var binary = predicate.Body as BinaryExpression;
if (binary == null) throw new InvalidOperationException();
var filter = GenerateFilter(binary);
query.FilterString = string.IsNullOrWhiteSpace(query.FilterString) ? filter : TableQuery.CombineFilters(query.FilterString, TableOperators.And, filter);
return query;
}
static string GenerateFilter(BinaryExpression binary)
{
switch (binary.NodeType)
{
case ExpressionType.AndAlso:
case ExpressionType.OrElse:
return CombineFilters(binary);
case ExpressionType.Equal:
case ExpressionType.NotEqual:
case ExpressionType.GreaterThan:
case ExpressionType.GreaterThanOrEqual:
case ExpressionType.LessThan:
case ExpressionType.LessThanOrEqual:
return (binary.Left is MethodCallExpression) ? GenerateFilterConditionForMethodCall(binary) : GenerateFilterCondition(binary);
default:
throw new InvalidOperationException();
}
}
static string CombineFilters(BinaryExpression binary)
{
var left = binary.Left as BinaryExpression;
if (left == null) throw new InvalidOperationException();
var right = binary.Right as BinaryExpression;
if (right == null) throw new InvalidOperationException();
var op = ToCombinationOperator(binary.NodeType);
return TableQuery.CombineFilters(GenerateFilter(left), op, GenerateFilter(right));
}
static string GenerateFilterCondition(BinaryExpression binary)
{
var left = binary.Left as MemberExpression;
if (left == null) throw new InvalidOperationException();
var op = ToComparisonOperator(binary.NodeType);
var rightValue = binary.Right.Invoke();
return
left.Type == typeof(byte[]) ? TableQuery.GenerateFilterConditionForBinary(left.Member.Name, op, (byte[])rightValue) :
left.Type == typeof(bool) ? TableQuery.GenerateFilterConditionForBool(left.Member.Name, op, (bool)rightValue) :
left.Type == typeof(DateTime) ? TableQuery.GenerateFilterConditionForDate(left.Member.Name, op, (DateTime)rightValue) :
left.Type == typeof(DateTimeOffset) ? TableQuery.GenerateFilterConditionForDate(left.Member.Name, op, (DateTimeOffset)rightValue) :
left.Type == typeof(double) ? TableQuery.GenerateFilterConditionForDouble(left.Member.Name, op, (double)rightValue) :
left.Type == typeof(Guid) ? TableQuery.GenerateFilterConditionForGuid(left.Member.Name, op, (Guid)rightValue) :
left.Type == typeof(int) ? TableQuery.GenerateFilterConditionForInt(left.Member.Name, op, (int)rightValue) :
left.Type == typeof(long) ? TableQuery.GenerateFilterConditionForLong(left.Member.Name, op, (long)rightValue) :
TableQuery.GenerateFilterCondition(left.Member.Name, op, rightValue.To<string>());
}
static string GenerateFilterConditionForMethodCall(BinaryExpression binary)
{
var methodCall = binary.Left as MethodCallExpression;
if (methodCall == null) throw new InvalidOperationException();
if (methodCall.Method != String_CompareTo) throw new InvalidOperationException();
var left = methodCall.Object as MemberExpression;
if (left == null) throw new InvalidOperationException();
var op = ToComparisonOperator(binary.NodeType);
var rightValue = methodCall.Arguments[0].Invoke();
return TableQuery.GenerateFilterCondition(left.Member.Name, op, rightValue.To<string>());
}
static string ToCombinationOperator(ExpressionType nodeType)
{
switch (nodeType)
{
case ExpressionType.AndAlso:
return TableOperators.And;
case ExpressionType.OrElse:
return TableOperators.Or;
default:
throw new InvalidOperationException();
}
}
static string ToComparisonOperator(ExpressionType nodeType)
{
switch (nodeType)
{
case ExpressionType.Equal:
case ExpressionType.NotEqual:
case ExpressionType.GreaterThan:
case ExpressionType.GreaterThanOrEqual:
case ExpressionType.LessThan:
case ExpressionType.LessThanOrEqual:
return (string)typeof(QueryComparisons).GetField(nodeType.ToString()).GetValue(null);
default:
throw new InvalidOperationException();
}
}
}
}
view raw TableHelper.cs hosted with ❤ by GitHub
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.Linq;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Table;
namespace ExpressionsConsole
{
public static class TableTest
{
static readonly CloudTable PeopleTable;
static TableTest()
{
var accountString = ConfigurationManager.ConnectionStrings["StorageAccount"].ConnectionString;
var account = CloudStorageAccount.Parse(accountString);
var tableClient = account.CreateCloudTableClient();
PeopleTable = tableClient.GetTableReference("people");
}
public static void SelectTest()
{
var query = new TableQuery<Person>()
.Select(p => new { p.FirstName, p.Age });
var result = PeopleTable.ExecuteQuery(query).ToArray();
}
public static void WhereTest1()
{
// (PartitionKey eq '2015') and ((LastName ge 'W') or (Age lt 20))
var query = new TableQuery<Person>()
.Where(p => p.PartitionKey == "2015" && (p.LastName.CompareTo("W") >= 0 || p.Age < 20));
var result = PeopleTable.ExecuteQuery(query).ToArray();
}
public static void WhereTest2()
{
// (PartitionKey eq '2015') and ((LastName ge 'W') or (Age lt 20))
var query = new TableQuery<Person>()
.Where(p => p.PartitionKey == "2015")
.Where(p => p.LastName.CompareTo("W") >= 0 || p.Age < 20);
var result = PeopleTable.ExecuteQuery(query).ToArray();
}
public static void WhereSelectTest()
{
var query = new TableQuery<Person>()
.Where(p => p.PartitionKey == "2015")
.Where(p => p.LastName.CompareTo("W") >= 0 || p.Age < 20)
.Select(p => new { p.FirstName, p.Age });
var result = PeopleTable.ExecuteQuery(query).ToArray();
}
}
[DebuggerDisplay(@"\{{PartitionKey}/{RowKey}\}")]
public class Person : TableEntity
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
}
view raw TableTest.cs hosted with ❤ by GitHub

このような 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 »