ラベル C# の投稿を表示しています。 すべての投稿を表示
ラベル C# の投稿を表示しています。 すべての投稿を表示

2012/11/22

【EF CodeFirst】リレーションの連鎖削除について

 何かすごく久しぶりの更新なのに大した話ではないんですが、メモついでに…。

 コードファーストでリレーションを組んだ場合の連鎖削除についてです。
 まぁ、以下のようなCategoryとSubCategoryと名付けた親子のテーブルがあるとします。

Categoryテーブル
public class Category
{
    public int Id { get; set; }

    public string Name { get; set; }

    public virtual ICollection<subcategory> SubCategories { get; set; }
}

SubCategoryテーブル
public class SubCategory
{
    public int Id { get; set; }

    public int ClassId { get; set; }

    public string Name { get; set; }

    public virtual Category Category { get; set; }
}

 見ての通り、ClassIdを外部キーとしてリレーションするわけですが、Categoryのデータを削除した場合、SubCategoryのデータはどうなるのでしょうか?答えは連鎖削除されちゃいます。まったくEntityFrameworkって気が利いてますねって感じです。

 では逆に連鎖削除してほしくない時はどうすればよいのでしょうか?自分、他のテーブルとの関係で連鎖削除OFFにしたくてずいぶん悩みました。答えは以下のようにテーブルを変更します。

Categoryテーブル
public class Category
{
    public int Id { get; set; }

    public string Name { get; set; }

    public virtual ICollection<subcategory> SubCategories { get; set; }
}

SubCategoryテーブル
public class SubCategory
{
    public int Id { get; set; }

    public int? ClassId { get; set; }

    public string Name { get; set; }

    public virtual Category Category { get; set; }
}

 ちょっとどこが変わったか分かりにくいですが、SubCategoryのClassIdの型をNull許容型に変更しています。こうすることでリレーションを保ったまま連鎖削除を切ることができます。

 ちなみにこれらの設定はMigrationのForeignKeyをいじることでも行うことができます。

2012/10/06

【C#】列挙型での日本語の取り扱い…ついてでにMVCも【MVC】

 列挙型で日本語っつーか全角文字列の扱いってどうすりゃいんだ?っての話の始まりです。どういうことかというと下記のコードを見てもらった方が早いでしょう。

列挙型A
public enum Positions
{
    Start,
    Middle,
    End
}

列挙型B
public enum Positions
{
    最初,
    間,
    最後
}

 どっちでいいだろ?って言われたらお終いなんですが、今まで半角英数で統一してんのに列挙型だけ例外とするのも気持ちが悪いというか…。当然、開発上のルールとしてメンバは必ず半角英数でって話だってあると思うのでとりあえず何とかしたいと思ったわけです(そんなものとは無縁の世界で生きてはいますが…)。

 一番簡単な案としては列挙型をやめちゃってDictionaryなりListなりのユーザー定義型でやっちゃえばいいんですが、それだと根本的な趣旨から外れちゃうんで却下。で、色々と調べたところカスタム属性を使う方法等もあったんですが今回は拡張メソッドで何とかなりそうだぞってことが分かったのでこれでやってみます。(参考にさせていただいた記事:当面C#と.NETな記録)(ついでにMSDN:方法 : 列挙型対応の新しいメソッドを作成する

列挙型Aに拡張メソッドを実装
public enum Positions
{
    Start,
    Middle,
    End
}
 
public static class PositonExtensions
{
    public static string GetDisplayName(this Positions position)
    {
        switch (position)
        {
            case Positions.Start:
                return "最初";
            case Positions.Middle:
                return "間";
            case Positions.End:
                return "最後";
            default:
                return string.Empty;
        }
    }
}

 コード自体はシンプルです。さて、これで今日は終了としても良いですが、このままじゃ中身のないマルパク記事じゃねーかって話になっちゃうので使い回しでこの間作ったサンプルにこの列挙型を追加して、MVCで表示してみます。

Userモデルを修正
public class User
{
    //・・・中略

    public Positions Position { get; set; }
}

コントローラーを修正
public ActionResult Edit()
{
    //・・・中略

    //SelectListで使えるようLinqを使って定義してみた
    var divisons = from p in (Positions[])Enum.GetValues(typeof(Positions))
                    select new
                    {
                        Value = p,
                        Display = p.GetDisplayName()
                    };
 
    ViewBag.Positions = new SelectList(divisons, "Value", "Display", user.Position);
 
    return View(user);
}

ビューを修正(一部抜粋)
<div class="editor-label">
    @Html.LabelFor(model => model.Position)
</div>
<div class="editor-field">
    @Html.DropDownListFor(model => model.Position, ViewBag.Positions as SelectList)
    @Html.ValidationMessageFor(model => model.Position)
</div>

実行結果

image

 駆け足ですがこんな感じになります。コントローラーでのViewBagのSelectListに値と表示を定義するためにLINQを使った以外はほとんど過去記事と変わりません。

 では今日はここまで。







ビックカメラ.com

2012/09/04

【LINQ】匿名化した固まりをまるごとグループ化【C#】

 なんのこっちゃって感じのタイトルになってしまいましたが、ちょっとLINQでグループ化してたら気がついたことがあったのでメモっときます。…今開発でLINQばっかり書いてるんで記事もLINQが多くなっちゃいますね。
 さて本題ですが、自分今までLINQでグループ化するとき、(こういう言い方が正しいかはわかりませんが、)匿名化した固まりを要素単位まで分解してグループ化してたんですよって言うかそうしないとグループ化できないと思い込んでたんですよ。ところがたまたま固まりごとグループ化してみたらできちゃったんでびっくりってなわけです。どういうことかは以下のサンプルで。

データ
Users
Id
Name

Points
Id
UserId
Point

 見ての通り、UsersとPointsはIdとUserIdで連結可能であり、今回はIdとNameでグループ化し、Pointの合計を求めたいということにします。

今までコード
from p in 
    (from p in users
     join g in points
     on p.Id equals g.UserId
     select new 
     {
        users= p,
        points = g  
     })
group p by new
{
    p.users.Id,
    p.points.Name
} into g
select new
{
    g.Key.Id,
    g.Key.Name,
    Point = g.Sum(p.points.Point)    
}


新しいコード
from p in 
    (from p in users
     join g in points
     on p.Id equals g.UserId
     select new 
     {
        users = p,
        points = g  
     })
group p by p.users into g
select new
{
    g.Key.Id,
    g.Key.Name,
    Point = g.Sum(p.points.Point)    
}


 ちょっと、変化が少なすぎて分かりにくいかもしれませんが、IdとNameを指定してグループ化していたのをusersのみの指定でグループ化を済ましています。まぁグループ化の単位によっては全然使えなかったりするわけですが、頭の片隅には置いておこうと思った次第で…。

 う~ん、毎度このながらなんてセンスのないサンプルでしょうか…。あとテーブルって簡単に横並びにできんもんだろうか…、float組むのはさすがに面倒だし。

デル株式会社

2012/09/01

【LINQ】LEFT OUTER JOIN(左外部結合)の話【C#】

 まぁ、MSDN(方法 : 左外部結合を実行する)見りゃ分かる話ですが一応メモっときます。単純な内部結合の場合は「join~on~equals~」でOKですが、相手先が存在しないの場合でもレコードを取得したい場合は左外部結合が必要となります。
 とりあえずサンプルデータ。
Users
ID Name
1
Yamada
2
Tanaka
Cards
ID UserId CardNo
1
1
999999

 相変わらず手抜きなデータですが、見ての通りUsersテーブルには2つのレコードがありますが、Cardsテーブルに一つのレコードしかありません。
 これを内部結合すると…。
コード
from p in Users
join g in Cards
on p.Id equals g.UserId
select new
{
    p.Id,
    p.Name,
    g.CardNo
};

結果
IDNameCardNo

1

Yamada

999999


 当然ながら結合可能なのはYamadaさんのみなのでそれしか表示されません。んでここから本題。Cardsテーブルが結合できんくてもとにかくUsersテーブルにあるレコードは全件引っ張りたいんだけどって場合にどうするかという話です。そこで登場するのがLEFT OUTER JOIN(左外部結合)ってわけです(何か話がSQLの初歩をやってる感じになってますが、まぁいいかってことで…)。

 LINQで左外部結合を実現する場合、簡単に言ってしまえばDefaultIfEmptyメソッドを利用し、存在しないものにnull値を割り当ててもらっちゃいます(こんな表現で正しいのか?)。

 早速、実験します。

コード
from p in Users
join g in Cards
on p.Id equals g.UserId into gg
from ggg in gg.DefaultIfEmpty()
select new
{
    p.Id,
    p.Name,
    CardNo = (ggg != null ? ggg.CardNo : null)
};

結果
IDNameCardNo

1

Yamada

999999

2

Tanaka

(null)


 この結果って<table>タグで書いてるだけなんで「ほらできた」って言うわけにはいきませんが…。ggとかgggとかってなんやねん?って感じですが、他に良い書き方が思いつかなかったので…。into句とDefaultIfEmptyメソッドを使うことでgggにはUsersテーブルに対して結合されるレコードと存在しないものには初期値が用意されるわけです。その後、select句でnull値を判定することでCardNoにCardNoかnullを代入してます。

 以上、LINQでLEFT OUTER JOIN(左外部結合)を利用する方法でした。

2012/08/30

【雑記】本日の箇条書き

1.単体テストでのCollection要素の比較方法

 VisualStudioの単体テスト機能を使ってCollection要素を比較する方法です。って言っても「CollectionAssert.AreEqual メソッド(http://msdn.microsoft.com/ja-jp/library/ms243763(v=vs.100))」で一発です。Collection要素を比較するってこと事態まれではあると思いますが、たまたま使ったので…。
 以下、しょうもないサンプルです。
[TestMethod()]
public void MyMethodTest()
{
    MyClass target = new MyClass();
    var expected = new Collection<string> { "Tanaka", "Suzuki", "Yamada" };
    Collection<string> actual;
    actual = target.MyMethod();
    
    //デフォルトのAssert.AreEqualだとこけます
    //Assert.AreEqual(expected, actual);
    
    CollectionAssert.AreEqual(expected, actual);
 
    //Assert.Inconclusive("このテストメソッドの正確性を確認します。");
}


2.LINQでテーブルを多重結合する際の悩み

 LINQでちょこちょこテーブルを多重に結合する訳なんですが、これが下記のコードのように多重化すればするほどややこしくなっていかんって話。
var myQuery = from p in 
        (from p in
            (from p in elements1
             join g in elements2
             on p.ACd equals g.ACd
             select new { p, g })
         join g in elements3    
         on p.p.BCd equals g.BCd
         select new { p, g })
    join g in elements4
    on p.p.p.CCd equals g.CCd
    select new
    {
        elements1 = p.p.p,
        elements2 = p.p.g,
        elements3 = p.g,
        elements4 = g
    };
見ての通り、pとgのオンパレードです。とりあえず最終的にテーブル名に戻していますが、何かもうちょっとスマートにならんかなぁって感じです。かといっていちいちSELECT句で名前付けたり、LINQを小分けする気にもならないし…。

2012/08/28

【C#】LINQPadのご紹介

 手軽さと便利さに感動したのでLINQPadを軽く紹介します。その名の通り、LINQに関連するアプリで、LINQ単体でのテスト・評価を行うことができ、自分のように開発中にLINQの成否に自信がないとか、結果セットが思った通りになっているか気になるって人にとてもおすすめです。上記リンク先からダウンロード&インストールができます。起動画面は下画像のようにいたってシンプルで、データベースを選択し、LINQを書いて実行してみるって感じです。

デフォルト画面
linqpad0

 実行するとLINQの成否だけでなく下画像のように結果セットやラムダ式等も表示してくれます。

結果セットやラムダ式の表示
linqpad1linqpad2


画像では単純なLINQを書いていますが、もっと複雑なLINQを組む時はこれを使うだけで安心感がぐっとアップします。また、一度作成したLINQの式は保存することもできます。

 自分はデータベースでしか使ったことはないのですがWCFデータサービスでも使えるようです。また言語はC#の他にVBとF#等に対応してます。

 表記が英語なのでメニュー等は分かりづらく感じるかもしれませんが、シンプルだし慣れれば大丈夫だと思います。

 以上、LINQPadのご紹介でした。

2012/08/25

【C#】CodeFirstのMigrationが元に戻せない

 以前、CodeFirstのMigrationに感動したって記事でマイグレーションで制約(インデックス作成)を作成したんですけど、分け合って一度それを初期化したいなぁって思ったんですよ。

 それで初期化方法である「パッケージマネージャーコンソール→コマンド実行:Update-Database –TargetMigration: “0”」ってやって処理が走ったんですけど最後に赤字で「Automatic migration was not applied because it would result in data loss.」と仰るわけです。「データがロスするから自動実行せんかったでー。」って意味っぽいなと思ってデータベース自体を見てみるとやっぱりインデックスが削除されてない。

 どうやらSeedメソッドでテストデータを突っ込んだんでレコードが存在するんでストップをかけてくれてるみたいですね。親切は親切なんだけどこれじゃ元に戻せないじゃないか…。とは言ってもまだ全然作り始めなんでデータベース自体をサクッと削除して再度コード走らせれば済むんですけどね。こういう点はやっぱりコードファーストは便利がいいです。

 しかし実際、稼働後にそういう自体になったらどう対応するかって話しですよ。直接データベース触っちゃったらMigrationと差異が出ちゃうわけだし。う~ん、どうしよ。とりあえず明示的にインデックスを削除するマイグレーションを実行してみようかな。

  という訳で実験です。テストテーブルに下記のようなマイグレーションを作成・実行しデータベースにインデックスを作成したとします。

//インデックス作成マイグレーション
public partial class CreateIndexMigration : DbMigration
{
    public override void Up()
    {
        CreateIndex("TestTable", new[] { "TestIndex1", "TestIndex2"}, true);
    }
 
    public override void Down()
    {
        DropIndex("TestTable", new[] { "TestIndex1", "TestIndex2"});
    }
}


 これのUpメソッドとDownメソッドの中身を入れ替えインデックス削除マイグレーションを作成します。

//インデックス削除マイグレーション
public partial class DropIndexMigration : DbMigration
{
    public override void Up()
    {
        DropIndex("TestTable", new[] { "TestIndex1", "TestIndex2"});
    }
 
    public override void Down()
    {
        CreateIndex("TestTable", new[] { "TestIndex1", "TestIndex2"}, true);
    }
}


 それでこれを実行してみると…。

PM> Update-Database -TargetMigration:DropIndexMigration
Specify the '-Verbose' flag to view the SQL statements being applied to the target database.
Applying code-based migrations: [201208250220033_DropIndexMigration].
Applying code-based migration: 201208250220033_DropIndexMigration.
Running Seed method.

 はい、あっさり成功しちゃいました。このようにマイグレーションで定義したインデックスを削除したい場合はインデックスの削除を行うマイグレーションを作成してやれば実行可能なようです。

2012/08/19

【C#】いつの間にやら2012

いつの間にかVisualStudio2012の開発が終了してるみたいです(Visual Studio 2012)。それに合わせてNuGetの方でEntityFramework5.0の配布も始まってるようです。Enumがサポートされるようですが、いまいちピンときてませんのでこれはまた今度ってことで…。このへん読んだら何となくは分かるかも…。

とりあえず現在開発中のものを何も考えず5.0に引き上げてみました。今のところTable属性の名前空間が変更になってる(System.ComponentModel.DataAnnotations → System.ComponentModel.DataAnnotations.Schemaになってる模様)以外特に手直しした点はありません。

それにしてももう2012リリースすんのかよ…。ついこの間2010買ってもらったばっかりな気がするんだけど…。


2012/08/15

【C#】EntityFrameworkとWCFに関する悩み事

前々からの悩みと言えば悩みだけど、EntityFrameworkで作成したエンティティをWCFのデータコントラクトで使っちゃっていいのかって話。

現行システムはデータベースからエンティティを生成した関係でWCFでそのままデータコントラクトで公開しちゃうとフィールド以外の内包されたデータが見えちゃうのが何か嫌でエンティティをコピーしたモデルを作ってそれでWCFでやりとりしたりしたんだけど、ぶっちゃけかなり面倒だったりした(特にデータベース修正時)。

今回はコードファーストでやってるからエンティティにデータコンテキスト属性つけてそのまま使っちゃえばいいんじゃないか?って最初考えてたんだけど、たとえばテーブルを結合したビューとかはどうすんの?って考えたときコードファーストのエンティティ達とそれらとは役割的に別物なんじゃないの?って思っちゃたりしたわけで…。じゃあWCF上でデータコントラクトで括って同一視したらまずいんでないの?って話になっちゃうんですよね。

こんなこと考えてると制約ばっかりになりそうな気もするし、社内向けのシステムでそこまで凝る必要ないだろって突っ込まれそうな気もするけど性分なんで…。

とりあえず面倒だけど現行を踏襲する方向かなぁ。なんかただの愚痴みたな感じなってしまった気がする。


2012/08/08

【C#】コードファーストでくだらないミス

いやぁー、参りました。ここ2、3日コードファーストからデータベースが作成できなくてモデルとにらめっこしてました。また、忘れて同じ事しないようにメモっとく。

原因1

テーブルAとテーブルBを複合外部キーを利用してリレーションしてやろうと思ってモデル側に「ForeignKey」属性をつけてみたんだけど、どうやら複数指定できない模様。じゃぁFluentAPIか?と思ってDbModelBuilder.Entity<>().HasRequired().WithMany().HasForeignKey()ってのを見つけたけどこれまた複数指定の方法がないっぽい。
…う~ん。複数指定ができないなら複合型を使ってモデル側でフィールドをまとめちゃえばできなくはないかって気はするけど、外部キーにしたいからって複合型使うのってどうなんだろ?
結局のところ、とりあえず複合外部キーはあきらめてIDを振ってそれを外部キーにすることにしました。フィールドにIdとは異なるコードとかあるからごちゃごちゃするの嫌だったんだけどしょうがないか。

原因2

原因1を解決して、さぁ今度こそデータベース作成しちゃうぞって思った矢先に「System.NotSupportedException Message=PrimitiveType 'SByte' の概念側の型 'Edm.SByte(Nullable=True,DefaultValue=)' に対応する格納型がありません。」なんてよく分からんエラーが…。
これ、原因言っちゃうと「string型をsbyte型にしてた」っていう馬鹿なタイプミスなんですど、これにたどり着くまでこれまた馬鹿みたいに時間食っちゃいました。だってsbyteなんて馴染みがなさすぎて…。まぁエラー自体、そんな型使えませんよ的なエラーだったからもっと早く気がつくべきだったんでしょうけどsbyte自体馴染みが無かったもんで…。散々悩んでソース内を検索してみたら一発でタイプミスの箇所が出てきてかなりショックでした。

てな感じで困った数日間でした。原因1に関してはもうちょと整理できたら複合型も考えてみてもよいかもって気もします。


2012/07/26

ラムダ式の自分なりの解釈2

前回に引き続きラムダ式の自分なりの解釈(自分用)です。

前回は匿名メソッドを「デリゲートを使用する際に外部に書き出してたメソッドをインラインで書けるようにしちゃおうというもの」という大雑把な解釈(?)で片付けて終わらせちゃいました。今回はそれを踏まえてラムダ式の解釈を行います。

早速、前回のサンプルをラムダ式で表してみましょう。ラムダ式の基本的な文法は「(入力パラメータ) => {式やステートメント}」となります。

static void Main(string[] args)
{
var message1 = "20の倍数です(スコープ外も参照できます)";

var thread1 = new Thread
(() =>
{
for (int i = 1; i < 100; i++)
{
Console.WriteLine(i.ToString());
Thread.Sleep(250);

if (i % 20 == 0)
{
Console.WriteLine(message1);
}
}
});

var thread2 = new Thread
(() =>
{
for (int i = 100; i >= 1; i--)
{
Console.WriteLine(i.ToString());
Thread.Sleep(500);

if (i % 20 == 0)
{
Console.WriteLine(message1);
}
}
});

thread1.Start();
thread2.Start();
}
}

…我ながらなんていまいちなサンプルなんだ。どこが変わったか分かんないじゃないか。パラメータもないし(パラメータが無い場合は先頭が「()」空の括弧となります。)。

このままではあんまりなのでLINQを使ったサンプルをひとつ考えます。まずは前準備として以下のようなコードがあるとします。

class Employee
{
public string Cd { get; set; }
public string Name { get; set; }
}

class Program
{
static void Main(string[] args)
{
var employeeList = new List
{
new Employee{ Cd = "001", Name = "Tanaka" },
new Employee{ Cd = "002", Name = "Sato" },
new Employee{ Cd = "003", Name = "suzuki" },
new Employee{ Cd = "004", Name = "Tanaka" }
};

//タナカさんを探せ!!
foreach (var employee in employeeList)
{
if (employee.Name == "Tanaka")
{
Console.WriteLine(string.Format(
"Cd:{0},Name:{1}",
employee.Cd,
employee.Name));
}
}

Console.ReadLine();
}
}

なんだか毎度気合いの抜けたサンプルですが、Employeeデータを作成し、そこからタナカさんを探してコンソールに出力しています。これをLINQとラムダ式を使って変更すると以下のようになります。

class Employee
{
public string Cd { get; set; }
public string Name { get; set; }
}

class Program
{
static void Main(string[] args)
{
var employeeList = new List
{
new Employee{ Cd = "001", Name = "Tanaka" },
new Employee{ Cd = "002", Name = "Sato" },
new Employee{ Cd = "003", Name = "suzuki" },
new Employee{ Cd = "004", Name = "Tanaka" }
};

//タナカさんを探せ!!
foreach (var employee in employeeList.FindAll(n => n.Name == "Tanaka"))
{
Console.WriteLine(string.Format(
"Cd:{0},Name:{1}"),
employee.Cd,
employee.Name);
}

Console.ReadLine();
}
}

コードの量があんまり変わっていませんが、FindAllメソッドにラムダ式「n => n.Name == “Tanaka”」を割り当てています。FindAllメソッドに渡されるパラメータに「n」を指定していますが、別に「m」でも「c」でも「nn」でも「nnn」でも何でもよいです。最初は何だよ「n」だとか「p」だとかってと思ってましたがラムダ式の慣例だと思ってしまえば使っていく内に慣れると思います(下手な名付けするより使いやすいです)。ラムダ式を利用した結果、foreachの前にタナカさんが絞り込まれ、ループ回数が少なくなります。

このようにLINQ内で使う場合はラムダ式自体目的が明確になるので分かりやすいです。問題は以前の記事に出てくるようなメソッドのパラメータにラムダ式を入れなきゃいけないパターンです。まぁ、インテリセンスのパラメータのヒントにFuncデリゲートだとかActionデリゲートとかって出たらラムダ式かなって思うことにしようかと(自分的に)。

今更ながら全然解釈してない気もするけどまぁいいや。


2012/07/25

ラムダ式の自分なりの解釈

先日の記事でFuncデリゲートが苦手と書いたけど、Funcデリゲート自体よりラムダ式が苦手だったりするので改めて自分用に自分なりの解釈をしておこうと思った。Funcデリゲート自体はラムダ式のおまけだとだと思ってしまえばそれまでだし…。

基本的にLINQでの使用が多いラムダ式ですが、MSDNによると「ラムダ式は式とステートメントを含めることができる匿名関数であり、デリゲート型または式ツリー型を作成するために使用できます。 」(引用元:ラムダ式 (C# プログラミング ガイド))とのこと。はぁ…、まずは匿名関数(匿名メソッド)が分かってないと駄目ってことですよね。

というわけでまずは匿名メソッドを解釈してみます。まぁ読んで字のごとく名前の無いメソッドで、C#2.0から登場した機能です。正直、C#3.0でラムダ式というかLINQが登場するまでその存在をほとんど忘れてました。だっていまいち利便性が分からなかったし、使いどころがピンとこなかったから。放置してたらこの有様です。

では早速解説を…と思ったんですが、この匿名メソッド、なんだか難しく考えない方が良さそうな気がします。まぁ簡単に(ほんとうに簡単に)「デリゲートを使用する際に外部に書き出してたメソッドをインラインで書けるようにしちゃおうというもの」と考えるのが良さそうです。

デリゲートとはとか言い出すと切りがなくなるのでそれは勘弁してもらって、以下のようなサンプルがあるとします。

static void Main(string[] args)
{
var thread1 = new Thread(new ThreadStart(Plus));
var thread2 = new Thread(new ThreadStart(Minus));
thread1.Start();
thread2.Start();
}

static void Plus()
{
for (int i = 0; i < 100; i++)
{
Console.WriteLine(i.ToString());
Thread.Sleep(250);
}
}

static void Minus()
{
for (int i = 100; i >= 0; i--)
{
Console.WriteLine(i.ToString());
Thread.Sleep(500);
}
}

このサンプルを匿名メソッドを利用する形に変更すると以下のようになります。PlusとMinusそれぞのメソッドがインラインで表現されてるのが分かると思います。ついでにスコープ外の参照ができることも確認できると思います。

static void Main(string[] args)
{
var message1 = "20の倍数です(スコープ外も参照できます)";

var thread1 = new Thread
(delegate()
{
for (int i = 1; i < 100; i++)
{
Console.WriteLine(i.ToString());
Thread.Sleep(250);

if (i % 20 == 0)
{
Console.WriteLine(message1);
}
}
});

var thread2 = new Thread
(delegate()
{
for (int i = 100; i >= 1; i--)
{
Console.WriteLine(i.ToString());
Thread.Sleep(500);

if (i % 20 == 0)
{
Console.WriteLine(message1);
}
}
});

thread1.Start();
thread2.Start();
}

改めて見ても使いどころがピンときません。このサンプルももうちょっと他で使えるサンプルをと思ってたけどこんなのしか思いつかなかったし…。こんなんじゃ本質を理解していると言えない気がするけどラムダ式への通過点ということで…。

なんだか長くなりそうなので本命のラムダ式はまた次回で!


2012/07/24

コードファーストのMigration機能に感動した話

話の発端はコードファーストでは無く、複合キーの善し悪しについていろいろ調べてたことでした。自社システム更新にあたりデータベースまわりを見直してたんですが、複合キーが結構あったりして今考えてみるとこれって合理的なのか分かんないよなぁって思ったりなんかしたわけです。それで調べてみると複合キーは使わずにサロゲートキー+複合ユニーク制約(この辺の用語解説は長くなるので省略。)を使ってみなさいというお言葉があったので早速影響されてみました。

 …コードファーストで複合ユニーク制約ってどうすんのよ。この複合ユニーク制約、SQLServerで言うとこの「インデックスで一意キーの作成」になるわけですが、どうモデルに書けばいいんだ?そんな属性見当たらないんだけど?てなことで調べてみたわけですが、どうやらモデル作成時には無理っぽいです。

いきなり挫折しそうになりましたが、どうにかこうにか探ってみたところこの辺を参考にMigrationをいじれば出来そうな気がする。早速実験してみます。

 以下のようなEmployeeテーブルがあったとしてこれの「EmployeeCd」と「Name」に複合ユニーク制約を設けたいと考えます。

public class Employee
{
public int EmployeeId { get; set; }
public string EmployeeCd { get; set; }
public string Name { get; set; }
}



public class MyContext: DbContext
{
public DbSet Employees { get; set; }
}

当然このまま作成しても何もなりませんが、Migrationはテーブルが作成されてない状態では実行できないので一端これでテーブルを作成します。

 テーブルが作成できたら「ツール」から「パッケージマネージャーコンソール」を開き、「Enable-Migrations」を実行します(Enable-Migrationと打つだけです。)。すると以下のようなクラスが自動で作成されます。これがMigration時のコンフィグにたぶん使われるわけです。これの「AutomaticMigrationsEnabled = false;」を「AutomaticMigrationsEnabled = true;」に変えると自動Migrationを利用できたりしますが、今回はこのまま使います。

internal sealed class Configuration : DbMigrationsConfiguration
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}

protected override void Seed(PmsDataBase.PmsContext context)
{
//  This method will be called after migrating to the latest version.

//  You can use the DbSet.AddOrUpdate() helper extension method
//  to avoid creating duplicate seed data. E.g.
//
//    context.People.AddOrUpdate(
//      p => p.FullName,
//      new Person { FullName = "Andrew Peters" },
//      new Person { FullName = "Brice Lambson" },
//      new Person { FullName = "Rowan Miller" }
//    );
//
}
}

 続けてコンソールで、「Add-Migration CreateIndexEmployeeMigration」を実行します。CreateIndexEmployeeMigrationの部分は任意です。まぁ頻繁にMigrationしなければ主要な変更点を名前にした方が無難かなと思います。実行すると以下のようなクラスが作成されます。

public partial class CreateIndexEmployeeMigration: DbMigration
{
public override void Up()
{
}

public override void Down()
{
}
}

これのUpメソッドに変更処理を、Downメソッドに取消処理を書けばよいわけです。今回は以下のようになります。

public partial class CreateIndexEmployeeMigration: DbMigration
{
public override void Up()
{
CreateIndex("Employees", new[] { "EmployeeCd", "Name" }, true);
}

public override void Down()
{
DropIndex("Employees", new[] { "EmployeeCd", "Name" });
}
}

「CreateIndex(テーブル名, カラム名, ユニークの場合TRUE)」でユニーク制約付きのインデックスが作成され、「DropIndex(テーブル名, カラム名)」でそのインデックスが削除されます。インデックスに名前を付け、それを指定して削除することも可能です。

これでいよいよ更新です。コンソールで「Update-Database -TargetMigration:CreateIndexEmployeeMigration」を実行すると…エラーが起きます。エラーの内容はSQLServerが「文字数MAXのカラムをインデックス化できるか!」てなことみたいです。全然Migration関係無いエラーでした。なんてこった。

まぁせっかくなのでこれまたMigrationを利用してテーブルを修正しましょう。コンソールで「Add-Migration EmloyeeCdAndNameLengthMigration」(長すぎても気にしない)とし、以下のように編集します。

public partial class EmloyeeCdAndNameLengthMigration: DbMigration
{
public override void Up()
{
AlterColumn("Employees", "EmployeeCd", c => c.String(maxLength: 5));
AlterColumn("Employees", "Name", c => c.String(maxLength: 20));
}

public override void Down()
{
AlterColumn("Employees", "EmployeeCd", c => c.String());
AlterColumn("Employees", "Name", c => c.String());
}
}

「AlterColumn(テーブル名, カラム名, Funcデリゲート)」でカラムの編集が可能です。Funcデリゲート苦手です。これと先ほど作ったMigrationをUpdate-Databaseコマンドで順次実行すると今度こそ思った通りのテーブルになります。

実際のプロジェクト見ると分かると思いますが、Migrationはクラス名の前にタイムスタンプが付けられ世代と整合性保持に使われてるようです。Update-Databaseコマンドにパラメータなしに実行するとこのタイムスタンプ順に全てのMigrationが実行されます。よって今回の作り方だと先にインデックス作成が走るのでエラーになるので注意下さい。

ちなみに「Update-Database –TargetMigration: “0”」とすると全てのMigrationのDownメソッドが実行され、初期化されます。

かなり長くなりましたが、こんな感じでMigrationの便利さを感じた今日この頃でした。


pagetop