2012年12月15日土曜日

[C#]式木とLINQ

式木がどんな風に役に立っているか、みんな大好き LINQ で見ていきたいと思います。
普段 LINQ はモリモリ使っていますが、その中身はボンヤリとしか理解していなかったのでおさらいしてみました。

LINQと式木の概要

例として LINQ to SQL を挙げてみていきます。LINQ to SQL で以下のようなクエリ式を書いた場合、

IQueryable<Student> query =
    from
        student in db.Student
    where
        student.Gender == "Male" 
    select
        student
    ;

db.Student を foreach して Genderプロパティ が "Male" の項目を探す... ではなく。
ご存知の通り実際にはこんな感じのSQL文が生成され、ADO.NET によって実行されます。

SELECT 
    [t0].[Id], 
    [t0].[Name], 
    [t0].[Gender]
FROM 
    [dbo].[Students] AS [t0]
WHERE 
    [t0].[Gender] = @p0
-- @p0 = 'Male'

このSQL文を作る過程に式木が利用されています。まず、上記のクエリ式は実際にはシンタックスシュガーで、以下の様なメソッド式に変換されます。

db.Student.Where(student => student.Gender == "Male");

さらにWhereメソッドは拡張メソッドなので、こうなりますね。

Where(db.Student, student => student.Gender == "Male");

この式がこんな感じの式木として扱われます。

同等の Expression を組み立てるとこんな感じです。

var whereMethod = typeof (Queryable)
    .GetMethods()
    .First(m => m.Name == "Where")
    .MakeGenericMethod(new Type[] { typeof(Student) });

var paramStudent = Expression.Parameter(typeof (Student), "student");

var expression =
    Expression.Call(whereMethod,
        Expression.Constant(db.Student),
        Expression.Lambda<Func<Student, bool>>(
            Expression.Equal(
                Expression.Property(paramStudent, "Gender"),
                Expression.Constant("Male")
                ),
            paramStudent
            )
        )
        ;

LINQ to SQL では結果を取得する際、まず式木を評価・解析することで適切なSQL文を作成してくれています。

LINQ to XXXXX

式自体(式木)を評価することで、LINQ to SQL や LINQ to Entities では式を SQL に変換しますが、LINQ to XML, LINQ to DataSet, LINQ to NHibernate ... などでは式を各種データソースに適切なアクセス方法に変換しています。

このような仕組みのおかげで統一的な式で、各種データソースへのアクセス、操作をうまく統合しています。
これが LINQ (統合言語クエリ) と呼ばれるゆえんであり、式木を最も活用している例でもあります。

応用してみる(?)

例えば LINQ to SQL では一括の UPDATE や DELETE ができません。例えば先程の例で 性別(Gender) が 男(Male) の生徒を全て削除しようと思うと以下のようになります。

var query =
    from
        student in db.Student
    where
        student.Gender == "Male"
    select
        student
    ;

db.Student.DeleteAllOnSubmit(query);
db.SubmitChanges();

この操作は以下の様な SQL を発行します。まず一度 Gender = 'Male' な生徒を全て取得してから...

SELECT 
    [t0].[Id], 
    [t0].[Name], 
    [t0].[Gender]
FROM 
    [dbo].[Students] AS [t0]
WHERE 
    [t0].[Gender] = @p0
-- @p0 = 'Male'

それぞれ1つずつ DELETE句 を発行していきます。

DELETE FROM [dbo].[Students] WHERE [Id] = @p0 -- @p0 = 1
DELETE FROM [dbo].[Students] WHERE [Id] = @p0 -- @p0 = 2
DELETE FROM [dbo].[Students] WHERE [Id] = @p0 -- @p0 = 3
...

これはあまりスマートとはいえないやり方ですね。本来以下のような SQL を発行すべきです。

DELETE FROM [dbo].[Students] WHERE [Gender] = @p0 -- @p0 = 'Male'

このように LINQ to SQL は更新や削除の操作がちょっと弱いです。しかしこの問題に対しては、自前の拡張メソッドを作って上記のような適切な SQL を発行するように実装することで対処できます。
以下の記事が大変参考になりますので、是非ご覧になってみて下さい。

もうちょっと具体的には

今回ご紹介した LINQ の仕組みは、IQueryableインターフェイス 及び IQueryProviderインターフェイス の組み合わせで実現されています。
以下の記事が大変わかり易く参考になりますので、紹介させて頂きます。

IQueryable と IQueryProvider の仕組みが把握できると、独自の LINQ to XXXXX を作ることができて夢が広がりますね。

さいごに

LINQ初心者の方や、式木がよくわからないぜ... という方の参考になれば幸いです。

※ 余談ですが http://ufcpp.net/ さんの C# の情報の充実度はハンパじゃないですね... C# について何か調べると必ずといっていいほど検索で挙がってきます。MSDNよりわかりやすい事が多く、いつも参考にさせて頂いています。

2012年12月4日火曜日

[C#]dynamicと拡張メソッド

ひょっとして dynamic って拡張メソッドもイケるのかな?とふと思ったので試してみました。

こんな感じの適当なクラスと拡張メソッドを用意します。

class Hoge
{
    public string Method()
    {
        return "Hoge's instance method.";
    }
}

static class HogeHelper
{
    public static string ExtensionMethod(this Hoge hoge)
    {
        return "Hoge's extension method.";
    }
}

まずは普通にインスタンスメソッドを dynamic から呼び出します。

var hoge = new Hoge();
dynamic dynamicHoge = hoge;

System.Console.WriteLine(dynamicHoge.Method());
 

結果はこんな感じで普通です。

Hoge's instance method.
 

では本題のこうしたときです。

System.Console.WriteLine(dynamicHoge.ExtensionMethod());

拡張メソッドは単なるシンタックスシュガーなので、

hoge.ExtensionMethod();

このような拡張メソッドの利用は、

HogeHelper.ExtensionMethod(hoge);

こんな風に展開されて実行されます。
なので、Hoge に ExtensionMethod というメソッドがあるわけではなく、dynamic はバインドできないはずです。

さっきのコードを実行してみます。

System.Console.WriteLine(dynamicHoge.ExtensionMethod());
Runtime Binder Exception: 'Hoge' に 'ExtensionMethod' の定義がありません

はい、予想通りでした。もしかしたら拡張メソッドもバインドできてしまうのか...!? と思ったのですが、そんなことはありませんでした。

2012年11月28日水曜日

[C#]式木入門

C#の式木について勉強しながら、勉強したことの理解を書いていきます。

式木ってなに?

式木(Expression tree)とは、式(数式)を木構造で表したものの事です。
以下の様な式を例にします。

int result = 5 + 7 * 3; // result == 26
 

この式をみるとき、加算演算子(+)をAdd関数、乗算演算子(*)をMultiply関数とするとこんな感じですね。

int result = Add(5, Multiply(7, 3));
 

ちょっと改行とかインデントを入れてみます。

int result = 
    Add(
        5, 
        Multiply(
            7, 
            3
            )
        );
 

なんとなく木構造にみえてきませんか?図にしてみます。

はい、どーみても木です。このように式は木で表現することができます。これが式木です。

なににつかうの?

式が木で表せることがわかりました。しかしこれは何の役に立つのでしょうか。

式を組み立てることができる

木構造を作ってあげる事で、好きな式を組み立てることができるようになります。

式を分析することができる

式を木構造にしてやることで、式を分析することができます。

つまり...どういうことだってばよ?

プログラム(式)を作ることができます。またプログラム(式)を分析することができます。

C#での式木

C#(.NET Framework)では式木を扱うための仕組みが用意されています。
System.Linq.Expressions 名前空間に式木を扱うためのオブジェクトが揃っています。

C#の中で式木をオブジェクトとして扱えるということは、C#の中でC#を書くというようなことができるということです。
つまり、プログラムの中でプログラムを作ったり、分析したりできます。いわいるメタプログラミングが可能になります。

さっきの式を組み立ててみる

さっきの簡単な式をC#で組み立てる例を示します。

// 式: 5 + 7 * 3
Expression body = 
    Expression.Add(
        Expression.Constant(5), 
        Expression.Multiply(
            Expression.Constant(7), 
            Expression.Constant(3)
            )
        );
 

このように、式は Expression型 として扱います。Expression型にはいろいろな式を表すための派生型が用意されています。
Expression型にファクトリメソッド(上の例のAddメソッドやConstantメソッドなど)が用意されているので、そこから各派生型のExpressionを作成できます。

加算演算子はAddメソッド、乗算演算子はMultiplyメソッドで作れます。5, 7, 3 などの定数はConstantメソッドで作れます。

このようにして作った式を実行したい場合は、以下のようにします。

Expression<Func<int>> lambda = Expression.Lambda<Func<int>>(body) // () => 5 + 7 * 3;
Func<int> func = lambda.Compile();
int result = func(); // result == 26
 

まず、Expression.Lambdaメソッドを使って、作った式をラムダ式に変換します。この場合は「() => 5 + 7 * 3」というラムダ式ですね。

次に作ったラムダ式を実行可能なデリゲートへコンパイルします。
コンパイルした後は通常のデリゲートと同じなので、普通に実行できます。

まとめ

式木を使うことでC#でメタプログラミングができます。プログラムのなかでプログラムを作るという面白いことが可能です。

こんどは

次は実際に式木がどんな事に役に立つのか、みてみたいと思います。

参考にさせていただきました

2012年11月27日火曜日

[C#]Equalsメソッドの実装について

Equalsメソッドと等値演算子(==)の実装、つまり等価比較について自分の理解が怪しかったので改めてまとめてみました。

きっかけは @xin9le さんのこちらのツイートです。

私は謂わいる ValueObject の実装で、よく値比較のクラスを作るために Equals(とGetHashCode) をもりもり実装していたので、「なんか自分間違ってるのかも!?」と心配になりました。

はじめに

まずはじめにEqualsメソッドや等値演算子(==)の実装について、仕組みや注意点、実装ポリシーをおさらいです。以下の記事が大変参考になります。

Equalsメソッドと等値演算子の実装には、いくつかのケースが考えられるので、それぞれ見ていきたいと思います。

値型の等価比較について

値型のEqualsメソッドをオーバーライドしない場合、既定の実装(ValueTypeの実装)により、定義された各フィールドの等価性を調べるように実装されています。

ただし、この比較はリフレクションを用いて行われるので、パフォーマンスが求められる場合は自分でEqualsメソッドをオーバーライドして実装する必要があります。
(参考:値型については equals と等値演算子をオーバーライドします)

また、値型でEqualsメソッドを実装する場合は、等値演算子を実装する必要があります。この場合、Equalsメソッドと等値演算子は同じ結果を返すようにします。

このように値型の等価比較はあまり複雑なことはありません。通常、値型を作成する場合は、Equalsメソッドと等値演算子を合わせて実装するのが良いようです。

話がややこしくなってくるのは、参照型の場合です。

参照型の等価比較について

参照型のEqualsメソッドは、既定の実装では参照の等価性を評価します。
また、等値演算子も同様に参照の等価性を評価します。(参照型なので当然ですね)

しかし、参照型でも値の比較によって等価性を評価する場合があります。
例えば、文字列型(string)は参照型ですが、Equalsメソッドも等値演算子も値の比較で等価性を評価するように実装されています。

これは参照型であっても、文字列型(string)は値のような特性を持つためにこのようになっています。(たぶん)
文字列型(string)が参照比較だった場合、直感的な挙動でなくなると思うので、なんとなくやりづらそうです。

値型と参照型の選択

「じゃあややこしいから値っぽい型は全部値型でいいじゃん!」と思うわけですが、そういうわけにもいかない事情があります。

値型は代入時にコピーされるので、サイズが大きいと高コストになってしまいます。(逆にサイズが十分小さいなら値型のほうが高速です)
また、ボックス化やボックス化解除が頻繁に発生する場合も高コストになります。

そうなると、値のような特性を持つ型でも、サイズが大きい、ボックス化がよく起きる、というようなケースでは参照型のほうが適切になります。(stringはサイズが大きいですね)

まとめ

というわけで、値のような特性を持つ型については、参照型でもEqualsや等値演算子を実装するケースはあります。
ただ「じゃあそれってどのくらいあるの?」となると、私の想像できる範囲だと最初に挙げた ValueObject くらいしか思いつきません。

なので、そういったオブジェクトを作る必要がなければ、実装することはあまりなさそうです。
あと、Equalsの実装にはGetHashCodeを合わせて実装する必要がありますし、その際にも気をつける必要のある実装ポリシーがいろいろあって、なかなか大変です。

なんとなく自分の理解がそれほど外れた所になさそうな気がして一安心ですが、あまり深く考えずにバシバシ参照型で ValueObject を作っていたので、値型で作ったほうが適切でないか見直しが必要そうです。

あと、Equalsを実装する場合は、タイプセーフに比較できる IEquatable<T> インターフェイスを積極的に使ったほうがよさそうです。

2012年10月20日土曜日

[C#]IDataReader をちょっと使いやすくする

IDataReader を使うときそのままだとこんな感じだと思うんですが、

int intValue = (int) reader["int_field"];
string strValue = (string) reader["str_field"];
 

NULL のときとかちょっと面倒です。(なんで IDataReader.IsDBnull には IsDBNull(string name) みたいなオーバーロードがないんだろう

Nullable<int> nullableValue = null;
if (!reader.IsDBNull(reader.GetOrdinal("nullable_field")))
{
    nullableValue = (int) reader["nullable_field"];
}
 

なのでこんな風にできるようにしたい。

int intValue = reader.Field<int>("int_field");
string strValue = reader.Field<string>("str_field");
Nullable<int> nullableValue = reader.Field<Nullable<int>>("nullable_field");
int ifNullValue = reader.Field<int>("if_null_field", ifNull:-1);
 

3.5以上なら拡張メソッド作ればいいんですが... 残念ながら 2.0 な環境だったのでこんなヘルパクラスを作って実装しました。

まあ実際は VB.NET なんですが... これで IDataReader が少し使いやすくなりました。

2012年10月3日水曜日

[C#]式木を触ってみる

メタプログラミングには以前から興味があったので、ぜひ式木について知りたい!というわけで。
こんなケースで考えてみました。

Equals と GetHashCode の実装

Equals と GetHashCode を実装するとき、大抵はプロパティや内部フィールドの比較を繋げるだけのことが多いので、その式を作ってくれる EqualityComparer があると嬉しいなあ。
※ まあ ReSharper があれば自動で実装してくれますが…

たぶんこんなクラスがあったら、


class Version
{
    public Version(int major, int minor)
    {
        Major = major;
        Minor = minor;
    }

    public int Major { get; set; }

    public int Minor { get; set; }
}

こんな感じに実装すると思います。

class Version
{
    public Version(int major, int minor)
    {
        Major = major;
        Minor = minor;
    }

    public int Major { get; set; }

    public int Minor { get; set; }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != this.GetType()) return false;
        return Equals((Version) obj);
    }

    private bool Equals(Version other)
    {
        return Major == other.Major && Minor == other.Minor;
    }

    public override int GetHashCode()
    {
        return Major.GetHashCode() ^ Minor.GetHashCode();
    }
}

これの実装を EqualityComparer に移したらこんな感じになるでしょうか。※ null判定は省略します

class VersionEqualityComparer : IEqualityComparer<Version>
{
    public bool Equals(Version x, Version y)
    {
        return x.Major == y.Major && x.Minor == y.Minor;
    }

    public int GetHashCode(Version obj)
    {
        return obj.Major.GetHashCode() ^ obj.Minor.GetHashCode();
    }
}

この EqualityComparer の Equals と GetHashCode の式を作ってくれる汎用EqualityComparer を作りたいと思います。

使い方をきめる

どんな風に使うかですが…

class Version
{
    public Version(int major, int minor)
    {
        Major = major;
        Minor = minor;
    }

    public int Major { get; set; }

    public int Minor { get; set; }

    private static readonly ComplexEqualityComparer<Version> Comparer = new ComplexEqualityComparer<Version>()
        .AddMember(v => v.Major)
        .AddMember(v => v.Minor)
        .Compile();

    public override bool Equals(object obj)
    {
        return Comparer.Equals(this, obj as Version);
    }

    public override int GetHashCode()
    {
        return Comparer.GetHashCode(this);
    }
}

こんな感じで実装に利用するプロパティやフィールドをラムダ式で指定できるようにします。

Compile メソッドを呼ぶと指定されたプロパティやフィールドを用いた式がコンパイルされて、あとは使うだけになる、という感じです。

書きやすくするために安直な感じですが、thisを返してメソッドチェーンできるようにしておきます。

メンバの指定を実装する

Equals に利用する “x.PropertyName == y.PropertyName” という式と、GetHashCode に利用する “obj.GetHashCode()” という式を作ります。

それぞれあとで “&&” や “^” でつなぐので、リストに放り込んでいきます。

    private readonly ParameterExpression _paramX = Expression.Parameter(typeof(T), "x"); // Equals(x, y) の引数 "x" を表す式
    private readonly ParameterExpression _paramY = Expression.Parameter(typeof(T), "y"); // Equals(x, y) の引数 "y" を表す式
    private readonly ParameterExpression _paramObj = Expression.Parameter(typeof(T), "obj"); // GetHashCode(obj) の引数 "obj" を表す式

    private readonly IList<BinaryExpression> _equalsList = new List<BinaryExpression>();
    private readonly IList<Expression> _getHashCodeList = new List<Expression>();

    public ComplexEqualityComparer<T> AddMember<TMember>(Expression<Func<T, TMember>> member)
    {
        if (member.Body.NodeType != ExpressionType.MemberAccess)
            throw new ArgumentException("メンバの指定はメンバアクセスの式でお願いします。");

        var memberExpression = (MemberExpression) member.Body;
        var memberInfo = memberExpression.Member;

        var memberX = Expression.PropertyOrField(_paramX, memberInfo.Name); // x.PropertyName
        var memberY = Expression.PropertyOrField(_paramY, memberInfo.Name); // y.PropertyName
        var equals = Expression.Equal(memberX, memberY); // x.PropertyName == y.PropertyName

        var paramMember = Expression.PropertyOrField(_paramObj, memberInfo.Name); // obj.PropertyName
        var getHashCode = Expression.Call(paramMember, typeof(TMember).GetMethod("GetHashCode")); // obj.PropertyName.GetHashCode()

        _equalsList.Add(equals);
        _getHashCodeList.Add(getHashCode);

        return this;
    }

式を完成させる

Complile メソッドで式を完成させて、コンパイルをかけます。

メンバの指定の際にリストに放り込んでおいた式を “&&” や “^” で繋いで組み立てます。
最後に Expression.Lamda<>() でコンパイルして出来上がったデリゲートを保存します。

    // コンパイルした式のデリゲート
    private Func<T, T, bool> _compiledEquals;
    private Func<T, int> _compiledGetHashCode;

    public ComplexEqualityComparer<T> Compile()
    {
        // x.Property1 == y.Property1 && x.Property2 == y.Property2 && ...
        BinaryExpression equalsExpression = null;
        foreach (var equals in _equalsList)
        {
            equalsExpression = equalsExpression == null
                                   ? @equals
                                   : Expression.AndAlso(equalsExpression, @equals);
        }

        if (equalsExpression == null)
            throw new InvalidOperationException();

        _compiledEquals = Expression.Lambda<Func<T, T, bool>>(equalsExpression, _paramX, _paramY).Compile();

        // obj.Property1.GetHashCode() ^ obj.Property2.GetHashCode() ^ ...
        Expression getHashCodeExpression = null;
        foreach (var getHashCode in _getHashCodeList)
        {
            getHashCodeExpression = getHashCodeExpression == null
                                        ? getHashCode
                                        : Expression.ExclusiveOr(getHashCodeExpression, getHashCode);
        }

        if (getHashCodeExpression == null)
            throw new InvalidOperationException();

        _compiledGetHashCode = Expression.Lambda<Func<T, int>>(getHashCodeExpression, _paramObj).Compile();
        
        _compiled = true;
        return this;
    } 

できあがり

だいたいこんな感じになりました。ほんとは null のチェックとかも必要ですが面倒なのでとりあえず。

public class ComplexEqualityComparer<T> : IEqualityComparer<T>
{
    public ComplexEqualityComparer()
    {
        _compiled = false;
        _paramX = Expression.Parameter(typeof(T), "x");
        _paramY = Expression.Parameter(typeof(T), "y");
        _paramObj = Expression.Parameter(typeof(T), "obj");
        _equalsList = new List<BinaryExpression>();
        _getHashCodeList = new List<Expression>();
    }

    private bool _compiled;

    private readonly ParameterExpression _paramX; // Equals(x, y) の引数 "x" を表す式
    private readonly ParameterExpression _paramY; // Equals(x, y) の引数 "y" を表す式
    private readonly ParameterExpression _paramObj; // GetHashCode(obj) の引数 "obj" を表す式

    private readonly IList<BinaryExpression> _equalsList;
    private readonly IList<Expression> _getHashCodeList;

    public ComplexEqualityComparer<T> AddMember<TMember>(Expression<Func<T, TMember>> member)
    {
        if (member.Body.NodeType != ExpressionType.MemberAccess)
            throw new ArgumentException("メンバの指定はメンバアクセスの式でお願いします。");

        var memberExpression = (MemberExpression) member.Body;
        var memberInfo = memberExpression.Member;

        var memberX = Expression.PropertyOrField(_paramX, memberInfo.Name); // x.PropertyName
        var memberY = Expression.PropertyOrField(_paramY, memberInfo.Name); // y.PropertyName
        var equals = Expression.Equal(memberX, memberY); // x.PropertyName == y.PropertyName

        var paramMember = Expression.PropertyOrField(_paramObj, memberInfo.Name); // obj.PropertyName
        var getHashCode = Expression.Call(paramMember, typeof(TMember).GetMethod("GetHashCode")); // obj.PropertyName.GetHashCode()

        _equalsList.Add(equals);
        _getHashCodeList.Add(getHashCode);

        return this;
    }

    // コンパイルした式のデリゲート
    private Func<T, T, bool> _compiledEquals;
    private Func<T, int> _compiledGetHashCode;

    public ComplexEqualityComparer<T> Compile()
    {
        // x.Property1 == y.Property1 && x.Property2 == y.Property2 && ...
        BinaryExpression equalsExpression = null;
        foreach (var equals in _equalsList)
        {
            equalsExpression = equalsExpression == null
                                   ? @equals
                                   : Expression.AndAlso(equalsExpression, @equals);
        }

        if (equalsExpression == null)
            throw new InvalidOperationException();

        _compiledEquals = Expression.Lambda<Func<T, T, bool>>(equalsExpression, _paramX, _paramY).Compile();

        // obj.Property1.GetHashCode() ^ obj.Property2.GetHashCode() ^ ...
        Expression getHashCodeExpression = null;
        foreach (var getHashCode in _getHashCodeList)
        {
            getHashCodeExpression = getHashCodeExpression == null
                                        ? getHashCode
                                        : Expression.ExclusiveOr(getHashCodeExpression, getHashCode);
        }

        if (getHashCodeExpression == null)
            throw new InvalidOperationException();

        _compiledGetHashCode = Expression.Lambda<Func<T, int>>(getHashCodeExpression, _paramObj).Compile();
        
        _compiled = true;
        return this;
    } 

    private void CompileIfNotCompiled()
    {
        if (_compiled) return;
        Compile();
    }

    public bool Equals(T x, T y)
    {
        CompileIfNotCompiled();
        return _compiledEquals(x, y);
    }

    public int GetHashCode(T obj)
    {
        CompileIfNotCompiled();
        return _compiledGetHashCode(obj);
    }
}

テスト

    [Test]
    public void UseCase()
    {
        var eqComparer = new ComplexEqualityComparer<Version>()
            .AddMember(v => v.Major)
            .AddMember(v => v.Minor)
            .Compile();

        var version_1_0 = new Version(1, 0);
        var version_2_3 = new Version(2, 3);

        Assert.IsTrue(eqComparer.Equals(version_1_0, version_1_0));
        Assert.IsTrue(eqComparer.Equals(version_2_3, version_2_3));
        Assert.IsTrue(eqComparer.Equals(version_2_3, new Version(2, 3)));
        Assert.IsTrue(eqComparer.Equals(new Version(2, 3), version_2_3));

        Assert.IsFalse(eqComparer.Equals(version_1_0, version_2_3));
        Assert.IsFalse(eqComparer.Equals(version_2_3, version_1_0));
    }

テスト結果

ちゃんと動くっぽいですね。

これで Equals と GetHashCode の実装は楽できそうです。

式木おもしろい!

式木とてもおもしろいですね。メタプログラミングって面白いなあと思います。

使い方を誤ると魔物が生まれそうですが、上手に使えばすごく役立ちそうです。(ぼくの頭ではなかなかアイデアが浮かびませんが…

参考にさせていただきました

大変参考にさせていただきました。

2012年9月24日月曜日

Jenkins で ASP.NET のWebアプリケーションをデプロイする

Untitled Page

Jenkins を使って自動ビルドや自動テストを走らせていたので、デプロイも Jenkins からできるようにしてみました。

ちなみに ASP.NET MVC のプロジェクトにて行いました。おそらく WebForms でも同じようにできると思います。

必要なもの

Jenkins で Visual Studio のソリューションやプロジェクトをビルドするには、Jenkins の MSBuild プラグインがあると便利です。Jenkins のプラグインマネージャから簡単にインストールできます。

また、デプロイには MSDeploy を利用しますので、デプロイ先の環境に MSDeploy がインストールされている必要があります。

パッケージの作成

MSDeploy でデプロイするためのパッケージを MSBuild で簡単にパッケージを作ることができます。“ビルド手順の追加” から “Build a Visual Studio project or solution using MSBuild” をこんな感じで追加して設定。

パッケージの作成

ターゲットを “Package” にすると、MSDeploy を利用して配置するためのパッケージが ”obj\Release\Package” のようなパスに作成されます。

ちなみにターゲット “Package” が必要なので、パッケージの作成は .NET Framework 4 の MSBuild で行う必要があります。たぶん…

ステージングとかプロダクションとかそれぞれの環境に応じたビルド構成(Staging, Production, etc)や構成変換ファイル(Web.Staging.config とか)がある場合は、Configuration=Staging とかでビルドすればOKです。

MSDeploy

MSDeploy については、こちらのMSDNの記事(http://msdn.microsoft.com/ja-jp/library/dd483479(v=vs.100).aspx)が詳しいです。

先ほどの MSBuild で作られたパッケージの中に、”プロジェクト名.deploy.cmd” というコマンドファイルが生成されています。(obj\Release\Package\Sample.Mvc.deploy.cmd みたいな)
これを実行すると配置が実行されます。が、その前に配置先の設定や接続文字列を設定したりする必要があります。

※ ちなみに “プロジェクト名.deploy-readme.txt” みたいなファイルが一緒に生成されています。非常に丁寧な解説が載っていますのでぜひ目を通して下さい。

配置先と接続文字列の設定

先ほどのコマンドファイルと同じディレクトリに、”プロジェクト名.SetParameters.xml” という下のようなXMLファイルが作られます。デプロイのための設定などはこのファイルを利用して指定します。

SetParametersの中身

配置先は、”IIS Web Application Name” パラメータで指定します。value を配置先のアプリケーションにします。接続文字列も同じように設定してやります。

このパラメータファイルの設定ですが、私はあらかじめステージング用とかプロダクション用などそれぞれ用意しておいて、MSDeploy を走らせる前に上書きしています。

MSDeploy の実行

パラメータファイルが用意できたら、あとはこんな感じで実行するだけです。

Sample.Mvc.deploy.cmd /Y

この/Yパラメータを指定すると、実際にパッケージがデプロイされます。/T パラメータなど指定することで、パッケージをデプロイせずにシミュレートだけを行ったりできます。(詳しい解説が上記の deploy-readme.txt に載っています

MSDeploy の実行を Jenkins に設定する

上記の MSDeploy の実行手順を Jenkins に設定しましょう。
ビルド手順を追加からWindowsバッチコマンドの実行を追加します。

たぶんこんな感じの内容になると思います。


まとめ

とても簡単です。あとは Jenkins でビルドを実行すればめでたく配置されるはずです。

最後に

このようにデプロイを自動化することで、継続的インテグレーションが捗ります。
ASP.NET でのこういった記事が少ない気がしたので書いてみました。(もしかして当たり前すぎるんでしょうか…)

私の環境では、ステージングにはビルドマシンからアクセスできるため、紹介したような方法でデプロイしていますが、プロダクションは別のネットワークにあるため直接デプロイできません。

なので、プロダクション用のパッケージを作ってZIPファイルで圧縮してダウンロードできるようにし、実際のデプロイではそれをプロダクション環境に持って行って解凍、先ほどのコマンドファイルを実行しています。

ビルドトリガでSCMをポーリングしてやれば、コミットした内容がすぐにデプロイされるのでいろいろな環境を素早く最新の状態に保てます。

Jenkinsさんのおかげで面倒なビルドやデプロイの作業から開放されてずいぶん楽になりました。これからも Jenkins さんと仲良くやって行きたいと思います。

2012年9月15日土曜日

イベント・ソーシング

たまたま「イベント・ソーシング」について知る機会があり、調べて試してみました。
非常に興味深い考え方です。



実際に動作するコードを書いてみようと、軽い気持ちでやってみたところなかなか難しくて時間がかかってしまいました。途中で別の作業をしなければならなくなったので、一旦中断...

ちなみに以下の記事が大変わかりやすいです。


また、以下に DDD, CQRS, Event Sourcing のサンプルがあります。こちらのサンプルを参考に実際にコードを書いてみています。


2012年7月23日月曜日

[C#]エンティティのEqualsとGetHashCode

しばらくの間、曖昧な理解のまま Equals と GetHashCode を実装していたので、反省。

まず、Equals と GetHashCode の実装については、以下の記事が参考になります。

概ね理解しているつもりだったんですが、今日ハッシュのコレクションを扱うときに、コレクション内のある要素を削除(Remove)しようとしても、削除できない(Contains(item) が false になってしまう)、というバグが出たので調べていたら、こちらの記事に行き当たりました。

  1. 2 つのオブジェクトが等しい (== 演算子の定義によって等しい) 場合、それらのオブジェクトからは同じハッシュ値が生成されなければならない。同じハッシュ値が生成されない場合、コンテナからオブジェクトを探しだすためのキーとしてハッシュコードを使用することができない。
  2. 任意のオブジェクト A に対して、A.GetHashCode() は各インスタンス毎に異なる値でなければならない。A のどんなメソッドが呼ばれたとしても、A.GetHashCode() は常に同じ値を返さなければならない。これによって、オブジェクトが格納されている正しいバケツを常に見つけだすことができる。
  3. ハッシュ関数は任意に入力された複数の整数値に対し、ランダム分布となる返り値を返す必要がある。この性質により、ハッシュベースのコンテナへ効率的にオブジェクトを格納できる。

この実装ポリシーのうち、2番を思いっきり無視していました。つまり、ハッシュのコレクションを生成したあとに、コレクション内のある要素の GetHashCode の演算に利用してるプロパティが変化して、ハッシュ値が変わってしまったようです。

また、以下の様な記事もありました。

 

エンティティにおける、Equals と GetHashCode の実装

今回このハッシュのコレクションというのは、いわいるエンティティのコレクションだったわけですが、その実装が以下のようなもの。

public class User
{
    public int Id { get; set; }

    public string FirstName { get; set; }

    public string LastName { get; set; }

    public override bool Equals(object obj)
    {
        var other = obj as User;
        return 
            other != null && 
            FirstName == other.FirstName && 
            LastName == other.LastName;
    }

    public override int GetHashCode()
    {
        return 
            (FirstName ?? string.Empty).GetHashCode() ^ 
            (LastName ?? string.Empty).GetHashCode();
    }
}

テーブルのほうはこんな感じです。

CREATE TABLE Users (
    Id INT IDENTITY PRIMARY KEY,
    FirstName NVARCHAR(50) NOT NULL,
    LastName NVARCHAR(50) NOT NULL,
    UNIQUE (FirstName, LastName)
)

Id が人工キーで、スキーマ上のメインの識別子になります。自然キーとして FirstName と LastName が利用できる(実際には名前は被るので使えないと思いますが…例です)ので、FirstName と LastName に一意キー制約を付けてあります。

この Equals 及び GetHashCode の実装では、以下のガイドラインの推奨に従って、人工キーではなく自然キーによる等価比較の実装を行なっています。(ちなみに ORM は NHibernate です

ビジネスキーの等価性 を使って、 equals() と hashCode() を実装することをお勧めします。ビジネスキーの等価性とは、 equals() メソッドが、ビジネスキー、つまり現実の世界においてインスタンスを特定するキー(自然 候補キー) を形成するプロパティだけを比較することを意味します。

このとき、FirstName と LastName が変化する場合、上記のような実装だとハッシュ値が変わってしまってポリシー違反を起こします。

なので、FirstName と LastName が変化する属性としての側面をもつ場合は、Equals と GetHashCode の実装に使うとマズイということになります。(FirstName と LastName が不変ならオッケーなんですが

このような場合は、素直に変化しない(はずの) Id を用いたほうがよさそうです。

public class User
{
    public int Id { get; set; }

    public string FirstName { get; set; }

    public string LastName { get; set; }

    public override bool Equals(object obj)
    {
        var other = obj as User;
        return
            other != null &&
            Id == other.Id;
    }

    public override int GetHashCode()
    {
        return Id.GetHashCode();
    }
}

本来なら自然キーでの実装のほうが、なにかと扱いやすいのですが仕方ないですね。

ちなみにこの例でも、Id のセッタが公開されてしまっているので、キチンと実装する場合は、Id が不変になるように工夫したほうがよさそうです。

2012年7月18日水曜日

[C#]Enumに振る舞いを

Enumにプロパティとかメソッドとか、豊富な振る舞いを持たせたい時どうすればいいんだろうという疑問です。

拡張メソッドを使う

public enum AircraftType
{
    Fighter,
    Attacker,
    Bomber
}

public static class AircraftTypeHelper
{
    public static string DisplayName(this AircraftType value)
    {
        return _displayNames[value];
    }

    private readonly static IDictionary<AircraftType, string> _displayNames = new Dictionary<AircraftType, string>()
    {
        { AircraftType.Fighter, "戦闘機" },
        { AircraftType.Attacker, "攻撃機" },
        { AircraftType.Bomber, "爆撃機" }
    };
}
 string displayName = AircraftType.Fighter.DisplayName();

こんな感じに拡張メソッドを使うと、プロパティは無理だけど、それなりに振る舞いをつけることができます。

素直にクラスで実装する

public class AircraftType
{
    private AircraftType(AircraftTypeValue value)
    {
        value = value;
    }

    private enum AircraftTypeValue
    {
        Fighter,
        Attacker,
        Bomber
    }

    public static readonly AircraftType Fighter = new AircraftType(AircraftTypeValue.Fighter);
    public static readonly AircraftType Attacker = new AircraftType(AircraftTypeValue.Attacker);
    public static readonly AircraftType Bomber = new AircraftType(AircraftTypeValue.Bomber);

     private readonly AircraftTypeValue _value;

    public override bool Equals(object obj)
    {
        var other = obj as AircraftType;
        return other != null && _value == other._value;
    }

    public override int GetHashCode()
    {
        return _value.GetHashCode();
    }

    public override string ToString()
    {
        return _value.ToString();
    }
}

こんな感じでシングルトンで実装する方法です。Equals とか ToString なんかの基本的なメソッドの実装は内部に隠してある Enum に丸投げ。

これならプロパティでもなんでも追加できます。(当たり前だけど...)

    public string DisplayName
    {
        get { return _displayNames[_value]; }
    }

    private readonly static IDictionary<AircraftTypeValue, string> _displayNames = new Dictionary<AircraftTypeValue, string>()
    {
        { AircraftTypeValue.Fighter, "戦闘機" },
        { AircraftTypeValue.Attacker, "攻撃機" },
        { AircraftTypeValue.Bomber, "爆撃機" }
    };
    string displayName = AircraftType.Fighter.DisplayName;

どっちがいいんだろう

拡張メソッドを使う方はまんま Enum を使うので手軽ですね。でも拡張メソッド以上のことはできないので、複雑な振る舞いを持たせたりするのには向いていないですね。

シングルトンでの実装は、自由にプロパティでもメソッドでも追加できるので柔軟性はあります。でも、実装がちょっと面倒です。

また、Enum をそのまま使うと FlagsAttributes でビット演算できたりして重宝することもあります。

個人的には後者(シングルトン)を使うことが多いのですが、振る舞いなんかがあまりない純粋な列挙値だったら普通に Enum を使います。あと、ビット演算したいときとか…

ケース・バイ・ケースなのかな?

2012年7月17日火曜日

[ASP.NET MVC]検証周りの設計

ASP.NET MVC では、ModelState を利用してビューに検証結果を表示したり、コントローラ側からエラーを追加したりできます。

ModelBinder を使えば、DataAnnotation で面倒な検証作業を省略でき、エラーを ModelState に手動で追加する必要もなく、とても素晴らしいフレームワークです。

でも、実際に作ってみると躓く(というか躓いた)のが、モデル内部の検証結果をどのようにビューへ伝達するか、でした。

ASP.NET MVC の検証機構と、モデルでの検証

まず ModelBinder や ModelState はあくまでも、コントローラとビュー側の仕組みです。うまく設計されたMVCアーキテクチャであれば、モデルは単体で独立していて、コントローラやビューに依存していないはずです。

そうなると、モデルはしっかり内部に検証機構を備える必要があります。コントローラやビューが変わっても、ちゃんと動作する必要がありますから。

ASP.NET MVC は ビューとコントローラについてはフレームワークとしてサポートしていますが、モデルについては開発者が自身で設計していかないといけません。

どうしたか

検証自体の実装は省略します。DataAnnotation を使ったり、オリジナルの検証機構を実装したりしてみました。個人的には DataAnnotation がいいと思います。基本的な検証なら用意された検証属性を使うだけで実装できますし、カスタム検証属性を作れば、多様なケースに対応できます。

肝心なのは検証結果をいかにビューまで伝達するかです。私が最初に試したのは…

ModelState をモデルに渡す

最初はよくわからずにこうなりました。ModelState をビューとコントローラ、モデルまで一貫した検証結果のコンテナとして利用しました。

しかし ModelState は ASP.NET MVC の仕組みの一つなので、それをモデルに入れてしまうのはなんとも言えない気持ち悪さ、ビューやコントローラへの依存が生まれてしまいます。

検証結果のコンテナを自前で作る

次は自前で検証結果を扱うコンテナを用意しました。モデルが返してくるこの独自の検証結果のコンテナを、コントローラ側で ModelState にマップしてビューへ伝達します。

この方法は最初うまくいったように思いました。でも大きな問題が。すごく面倒です。

また、最終的には ModelState を利用するので、ModelState を意識せざるを得なくなり、最初の試みとだんだん大差なくなってしまいました。

モデルは特別なことをしない

最終的にどうなったかというと…

例外結果のコンテナの仕組みをモデルから全部排除しました。モデルで検証に失敗したら単に例外をスローするだけです。つまり、モデルでの検証はデータを守る最後の砦としてのみ機能させるということです。

その代わり、ModelBinder による検証を充実させ、モデルでの処理の際、極力検証に失敗しないように作ることにしました。ユーザの通常の操作の範囲なら、ModelBinder による検証で十分拾えると思います。

ModelBinder による検証が充実していれば、モデルでの検証が失敗するケースは、特殊なケースになります。例えば悪意ある操作などです。

そんな特殊なケースまで丁寧に検証結果を表示したりする必要はないということにしてしまいました:)

こうすることでモデルの作りがシンプルに保たれ、モデルは必要最小限の仕事に集中すればよくなり、随分すっきりしたように思います。

まとめ?

結局モデルの検証結果を伝達するのをやめるという、なんとも身も蓋もない結論になってしまいましたが、個人的にいろいろ試したところでは、これがいちばんしっくりきました。

ModelBinder は非常に優れた仕組みなので、カスタムバインダや検証属性を作って有効活用しましょう!

2012年6月26日火曜日

[L2S]EntitySet.Removeでレコードを削除する

Linq to SQL - Using EntitySet.Remove to delete records | Joe Stevens' Blog

LINQ to SQL のデザイナでそれらしい設定が全くなかったので、「もしかしてレコード削除したいときは、DeleteOnSubmit をちまちま呼ばないといけないのか、ウゲー。」とか思っていたら、ちゃんとあった。

.dbml を右クリックでXMLエディタから開いて、XMLを直接編集することで設定できた。

うーん、機能自体はあるのにデザイナに無いなんて。

2012年6月12日火曜日

Windows Live Writer

Windows Live にはこういうのもあったんですね。試しに使って投稿してみる。

無料ブログの編集はWriter - Windows Live on MSN

この記事を読んで知りました。他にも便利そうなツールがたくさん。

Scott Hanselmanの開発者とパワーユーザーのための究極のWindowsツールリスト2009

2012年5月14日月曜日

[NHibernate]インターフェイスのマッピング

この間ふと、「NHibernateってインターフェイスをマッピングできるのかな。」と気になったので、実験をしてみました。

どういうこと?

例えば以下のようなエンティティがあったとします。

public class Employee : IPerson
{
 public virtual int Id { get; set; }

 public virtual string Name { get; set; }

 public string FullName
 {
  get { return Name; }
 }
}
public class Customer : IPerson
{
 public virtual int Id { get; set; }

 public virtual string FirstName { get; set; }

 public virtual string LastName { get; set; }

 public string FullName
 {
  get { return LastName + " " + FirstName; }
 }
}
いずれも見ての通り IPerson というインターフェイスを実装したクラスです。

public interface IPerson
{
 string FullName { get; }
}
マッピングは以下のようにします。

<class name="Employee" table="Employees" lazy="false">
  
 <id name="Id">
  <generator class="identity"></generator>
 </id>

 <property name="Name"></property>

</class>

<class name="Customer" table="Customers" lazy="false">
  
 <id name="Id">
  <generator class="identity"></generator>
 </id>

 <property name="FirstName"></property>
 <property name="LastName"></property>

</class>
この場合に、IPerson のエンティティ全体に対してクエリを投げたりできるのか。ということです。
以下のような問い合わせを想定します。

var persons = session.Query<IPerson>();
こんなことをしたい場合、どうすればいいんだろう?という疑問です。

ちなみに、適当に以下のようなデータを入れておきます。

Employees
Id Name
1 さいとー
2 ぱず
3 ぼーま
4 いしかわ
Customers
Id FirstName LastName
1 かずんど ごうだ
2 ひでお くぜ

モノは試し

せっかくなので上記のコードを試してみます。今は IPerson なんてマッピングファイルには一切記述していないので、失敗するだろうと思って試すと...


何事も無く動いてしまいました。しかも期待通りの結果です。

NHibernateが出したログから実行されたSQLを見てみます。

select employee0_.Id as Id0_, employee0_.Name as Name0_ from Employees employee0_
select customer0_.Id as Id1_, customer0_.FirstName as FirstName1_, customer0_.LastName as LastName1_ from Customers customer0_

どうやら IPerson に対するクエリを書いたので、実装エンティティを順番に問い合わせて、結果を合わせて返してくれたようですね。たまげたなぁ。


公式サイトのドキュメントに載ってた

ここに載ってました。暗黙的ポリモーフィズムと呼ばれるタイプのマッピングアプローチのようです。

インターフェイスのマッピングに関しては、他にも数種類のアプローチがあり、また今度試して見ることにします。(ドキュメントを眺めているだけだとなんだかよくわからないので...)


気になる

ただ、この方法だと実装エンティティの数だけ問い合わせが行われてしまいますね。
また、条件や並び順を指定したときどうなるのかも少し試してみたのですが、投稿をわけてまとめたいと思います。

2012年4月18日水曜日

DisplayNameを表示するHtmlHelperの拡張メソッド

たぶん誰もが思いつくであろう、DisplayName属性の値を表示するHtmlHelperの拡張メソッドです。

どうして標準でないんだろう。あれ、知らないだけなのか...?


2012年4月14日土曜日

ASP.NET MVC ひとり反省会

最近、ちょこちょこASP.NET MVC(主に2)の開発を経験して、失敗とそれに対する自分なりの正解を覚え書きします。

今まで試行錯誤とWebで先輩方の記事を読んだり、ソースコードを読んだりして、頭の中だけでやっていたんですが、やっぱり少しづつでも文章にして、整理していかないといけないと思ったので。



反省と対策


まずURL、URLをしっかり考える

ちゃんとURLを設計してから、組み立てる。


なぜ

しっかりしたURL設計がないと、ルーティングやコントローラの構成が破綻してしまいがち。
あとでこれを調整するのは辛い作業だった。


どうする

それぞれの機能をちゃんと考えて、相応しいURLを設計する。全てはそれから。


ビューのモデル(ビューモデル)は、ビュー毎に作成する

似た構成のビューのモデルは、つい共用してしまいがちだけど...

なぜ

片方に仕様変更が入った時、もう片方にも影響が出て大変。
この歪を下手に吸収しようとすると、どんどんワケのわからない状態になって、分けておいたほうがよほど楽だったということになってしまった。


どうする

最初からビューごとにビューモデルを分けて作っていく。


追記(2012/04/17)

@onosさんにご指摘頂いたので追記です。
単にビューごとにビューモデルを分けて作るのではなく、どこを切り離し、どこを共通化するのか、しっかり設計する必要がある。

例えば、検証周りの設定は単に分割して作ってしまうと、同じ設定を多方でバラバラに設定することになってしまう可能性がある。
そういった共通化と分離のバランスを取っていく。



クライアント側の検証と、サーバ側(ドメインモデル)の検証は別に考える

検証のやり方はともかく、これは混同しないほうがいいと思った。


なぜ

そもそも、クライアント側とサーバ側とでは、検証の目的が違うはず。
クライアント側は、ユーザビリティの向上が主な目的。
サーバ側は、データを守ることが主な目的。


どうする

検証の設定を無理に共有させようとしない。
それぞれのコンテキストに必要十分な検証を別々に設定する。また、各検証の目的を曖昧にしない。


ドメインのエンティティは、コントローラやビューに露出させない


なぜ

エンティティの制御はモデル内で完結しないと、何が起こるのかわからなくなってしまう。
また、ビューでエンティティを使っていると、エンティティがビューに依存していってしまう。
そうして、ビジネスロジックとは関係ないモノがモデル内で増殖してしまった。


どうする

モデルのアウトラインとなる、サービス層のインターフェイスにエンティティを使用しない。
DTO的なものを、それぞれのコンテキスト別に用意し、それにエンティティのデータを移して使う。
AutoMapperはとても便利。


まとめ

ふと書いておこうと思いついて、パッとでてくるものだけ殴り書きです。
こうやってベストプラクティス的なものを構築していくのは楽しい。

TFT 10.14 Peeba Comp

こちらのガイドの自分用まとめです。 https://www.reddit.com/r/CompetitiveTFT/comments/hraunp/tft_1014_break_the_meta_new_peeba_comp_set_35/ 難しいですが完成すると非常に強く、プレ...