BEACHSIDE BLOG

Azure と GitHub と C# が好きなエンジニアの個人メモ ( ・ㅂ・)و ̑̑

C# Json.NET 入門3 - 動的なRootNameのJsonをデシリアライズ

Root name がdynamicなjson ?ルートネームが動的なjson ?(なんと表現するのが正しいのでしょうか...)
以下のようなjsonをデシリアライズするときのお話です。

f:id:beachside:20160830184015j:plain

"araragi koyomi"と"oshino meme"の部分が動的に変わっていくケースを想定しています。 動的な名前の下には、userId、name、ageとfamilyを並べました。familyは、Personの配列です。
(familyの中のpersonにfamilyがないとかは...サンプルなので書いてないだけです)。

続きを読む

ASP.NET5 MVC6 でのModelStateの単体テスト

ASP.NET 5 MVC6 と xUnit で ModelState の単体テストを書こうのコーナーです。

「ModelStateの単体テストコード、うまくできませぬ」と言われたのでコードを見せてもらったら、コントローラーをnewしてなんちゃらしていた事件があったので、書いておこうと思いました。

f:id:beachside:20160202230255p:plain

Environment

  • Visual Studio 2015 Enterprise Update1
  • ASP.NET and Web Tools 2015 (RC1 Update 1)
  • xUnit(2.2.0-beta1..あたり)

Overview

ASP.NET5 MVC6 のプロジェクトを作って、Depenency Injection(DI)について、4種類がどう異なるのかを見たい今日この頃です。

いつものことですが、Visual Studioは英語版なので日本語の正確な項目名わかりません...涙。
それにしてもASP.NET5が 「ASP.NET Core 1.0」に名称変わるのでタイトルもそう書きたいところですが、名称変更となるRC2、今月(2016/2)リリース予定がTBDに戻ってたので、もう少しこの名称で書いておきましょう。。。

続きを読む

ASP.NET MVC5 とかでバックグラウンド処理( QueueBackgroundWorkItem 編)

f:id:beachside:20160130162918p:plain
AzureをプラットフォームにしてASP.NETで、戻りを待つ必要のないおもし蟹な重い処理をバックグラウンド処理として実装したいなーと思うと、

  • QueueBackgroundWorkItem でサクッと実装?
  • WebJobsでささっと実装
  • Worker Roleで?
  • Azure Batchでごりっごり...

その他、Web Roleなどもあると思いますが、それはさておき...

バックグラウンド処理をする際の手法の選択は、その処理数や処理自体の重さに応じて、どのリソースを使うべきかに応じて選択肢が変わりますでしょうか。
今回は、色んな意味で簡易・容易な QueueBackgroundWorkItem のお話です。

> Environment

サンプルを書いた環境は以下です。

DNX周りの話ではないのでご注意を。

1. QueueBackgroundWorkItem 概要

QueueBackgroundWorkItem は、.NET Framework 4.5.2で実装された機能で、それ以前の.NET Frameworkではもちろん使えません。
リリースノートはこちら。
What's New in the .NET Framework
IIS がすべての処理が完了するまで待ってくれるようなので、安心安定ですね。

QueueBackgroundWorkItem は、System.Web.Hosting 名前空間は、HostingEnvironmentクラスのメソッドです。
2つのオーバーロードを持ってます。

ざっくりまとめると、
QueueBackgroundWorkItemとは....
「QueueBackgroundWorkItemメソッドにデリゲートを渡せばバックグラウンド処理してくれる」
ということです。

最も簡単に実装できる類のバックグラウンド処理かなーと思ってます。

注意点として、
IISの環境下でバックグラウンド処理をしてくれるので、それ以外だとあたーーーりまめに死にます。
以前、QueueBackgroundWorkItemを使って、Worker Roleで動かないと悩んでいた方が相談に来たことがあったので...念のため書いておきました。

2. QueueBackgroundWorkItem の実装

おもむろにASP.NET MVC5プロジェクトを作り、HomeControllerクラスのIndexメソッドに例を追記してみましょう。
workItemというFuncを定義して渡すだけです。
Funcには、LongLongMethodAsyncメソッドを用意しておきました。

public ActionResult Index()
{
    Func<CancellationToken, Task> workItem = LongLongMethodAsync;
    HostingEnvironment.QueueBackgroundWorkItem(workItem);

    return View();
}

private async Task LongLongMethodAsync(CancellationToken cancellationToken)
{
    //なんか重い処理...
    await Task.Delay(10000);
}

ActionやFuncを定義せず、ラムダ式で書いてしまうのもよく使いますでしょうか。

public ActionResult Index()
{
    HostingEnvironment.QueueBackgroundWorkItem(cancellationToken =>
    {
        // なにかおもーい処理を...
    });
//...以下省略

ラムダか~ら~の~awaitableな何かを呼びたい場合は、QueueBackgroundWorkItemのパラメータの最初にasyncを書いて...

public ActionResult Index()
{
    HostingEnvironment.QueueBackgroundWorkItem(async cancellationToken =>
    {
      await LongLongMethodAsync(cancellationToken);
        // なにかおもーい処理を...
    });
//...以下省略

ですね。

次は、WebJobsを書く予定....

ASP.NET5 MVC6 Entity Framework 7 を使って Database First する

ASP.NET Advent Calendar 2015 20日目です。二日酔いで原因不明で体調が悪くて遅刻でした。すいませんm(_ _)m。....次のチャック先生もまだ未公開のようですね....ふふふ...

個人的に Database First 派なので、EntityFramework 7で対応方法をここでお勉強♪。
「Dabase First って何?」をザックリいうと、既存のデータベースからリバース エンジニアリングでPOCOなエンティティ・クラスを作成することです。

> Environment

ASP.NET5は、現時点での上記ツールを入れたバージョンとなります。
ASP.NET and Web Tools 2015 (RC1 Update 1)入れてないとテンプレートがでない(んでしたっけ?)とかあるので、試される方は、ググっていただきインストールが必要です。
バージョンによって多少テンプレートの中身も異なる可能性が大きいし、直ぐにバージョンも上がると思うので、きっと数週間~数か月でここで書く方法とは古くなるんだろうなーと思ってます。

> 事前準備

DBは、Azure の SQLDatabase に接続しています。事前にデータベースとテーブルを作っておきます。
うむ、それくらいでしょうか。

>1. ASP.NET5 MVC6 のプロジェクト作成

VSでプロジェクトを作成します。新規プロジェクトで、[ASPNET Web Appication]を選び、targetのframeworkは、デフォルトで表示される4.6.1、テキトーにプロジェクト名とかつけ、[OK]します。
f:id:beachside:20151221173241p:plain

テンプレートの選択では、MVCのテンプレートとなる[Web Application]を選択して[OK]します。
f:id:beachside:20151221173631p:plain

MVC6のテンプレートということで、ControllersとかServiceとかModelsなどのフォルダができています。
bowerの設定変えたほうがいんじゃね的な内容が、ハンセルマン先生のブログにあったりなかったりしますが、
www.hanselman.com
今回の趣旨ではないので、放置プレイです。

>2. Nuget Packageで必要なもの導入

現時点でプロジェクトを作成すると、Entityframework関連は、以下が入ってます。

  • EntityFramework.MicrosoftSqlServer(7.0.0-rc1-final)
  • EntityFramework.Commands(7.0.0-rc1-final)
  • EntityFramework.AspNet.Identity.Framework(3.0.0-rc1-final)

Identityは、個人認証のプロジェクトを作ったから私のには入っているだけですが、その他は必須なはずです。
これに加え、DabaseFirstするために以下をインストールします。

  • EntityFramework.MicrosoftSqlServer.Design(7.0.0-rc1-final)


コマンド叩いてインストールしてもOKですが、今回は何となくNugetPackageManagerでやってます。
画面上部の[Tools] > [Nuget Package Manager] > [Manager Nuget Packages for Solution...]を開きます。
f:id:beachside:20151221180122p:plain

[Browse]を選択して、「EntityFramework.MicrosoftSqlServer.Design」と入力して検索します。一つヒットすると思いますので、その子をインストールします。
f:id:beachside:20151221175907p:plain

>3. コマンドで Database First する

「Database First する」とか日本語変じゃね?的なことはさておき、Database First するにはコマンド叩きますので、コマンドプロンプトとか開きます(画面キャプチャはConsole2です。)

まず、プロジェクトのディレクトリに移動します。今回だと、プロジェクト名が「mvc6ef7」なので、
「{プロジェクトを作ったディレクトリ}/mvc6ef7/src/mvc6ef7」
になります。ようは、「Models」とか「Controllers」とかあるディレクトリです。

ここまでの操作は、nugetで「productivity power tools 2015」を入れていると、
プロジェクト名(「mvc6ef7」)の上で右クリック > [Power Commands] > [Opne Command Prompt]でさっと開けます。

次に、dnvmコマンドを使う準備的なこととして、以下のコマンド打ちます。(バージョンが上がるとコマンドも変わるのでご注意を)

dnvm use 1.0.0-rc1-update1

f:id:beachside:20151221183757p:plain


次に、本題の Database First するわけですが、コマンドはこんな感じです。

dnx ef dbcontext scaffold "Server={接続文字列!}" EntityFramework.MicrosoftSqlServer --outputDir {出力先のディレクトリ}

今回はAzure SQL Databaseに接続するので、Azureのポータルから接続文字列を確認しつつておきます。出力先はご自由にということで、DAL/DbContextsというディレクトリに出力します。
具体的なコマンドのサンプルですが、

  • 接続先のサーバーが「tcp:beachside-dev-sv.database.windows.net,1433」
  • Database名が「sqldatabase」
  • SQL認証のユーザーは「Hogehoge」
  • パスワードは「passHogehoge」

だと、以下のコマンドになります。

dnx ef dbcontext scaffold "Server=tcp:beachside-dev-sv.database.windows.net,1433;Database=dev-sqldatabase;User ID=Hogehoge;Password=passHogehoge" EntityFramework.MicrosoftSqlServer --outputDir DAL/DbContexts

コマンドを実行すると、ダラララララーと動いて、問題なければ最後にDoneとでます。
ソリューションエクスプローラーを確認すると、テーブルの定義に添ったエンティティ・クラスが追加されていることが確認できます。

f:id:beachside:20151221185035p:plain

>4. 接続文字列の設定、動作検証

接続文字列は、デフォルトではDbContextのクラスに書かれます。今回、データベース名が「dev-sqldatabase」だったので、DbContextのクラスは、「dev_sqldatabaseContext」になっています。
クラスを開くと、DbContextクラスが継承されており、最初の方に接続文字列の設定が書かれています。

f:id:beachside:20151221191723p:plain

接続文字列は、「Startup.cs」でごねごねするのが一般的なようなので、[OnConfiguring]メソッドごと削除します。
あ、その前に、接続文字列自体は利用するので、どこかにコピーしておきましょう。

ごねごねする前に、ソリューションエクスプローラー直下にある「appsettings.json」を開いて接続文字列を書きます。こんな感じで。

{
  "Data": {
    "DefaultConnection": {
      "ConnectionString": "Server=tcp:beachside-dev-sv.database.windows.net,1433;Database=dev-sqldatabase;User ID={ひみつ};Password={ひみつ};MultipleActiveResultSets=true"
    }
  },
  "EntityFramework": {
    "dev_sqldatabaseContext": {
      "ConnectionStringKey": "Data:DefaultConnection:ConnectionString"
    }
  },
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Verbose",
      "System": "Information",
      "Microsoft": "Information"
    }
  }
}

「"Logging"」はデフォルトのままです(今回のネタとは関係ないので)。

接続文字列となる「ConnectionString」の値はコピペした通り書きますが、最後に「MultipleActiveResultSets=true」つけておくと良いでしょう。「それ何?」という方は以下あたりを参照に...。
複数のアクティブな結果セット (MARS) の有効化

さらに小ネタとして、「EntityFramework」を追加し、DbContextのクラス名(今回だと「dev_sqldatabaseContext」)を指定して、「ConnectionStringKey」に上記の通り書きます。お察しの通りですが、さっき書いた接続文字列を指定しているだけです。

次に、「Startup.cs」を開きましょう。
クラス内に[public void ConfigureServices(IServiceCollection services)...]というメソッドがありますので、以下を追記します。

var connection = Configuration["Data:DefaultConnection:ConnectionString"];
services.AddEntityFramework()
    .AddSqlServer()
    .AddDbContext<dev_sqldatabaseContext>(options => options.UseSqlServer(connection));

これで、設定は完了です。
動作検証ということで、ざっくりなコードをHomeControllerに書いて試してみましょう。

public class HomeController : Controller
{
public async Task<IActionResult> Index()
{
    using (var context = new dev_sqldatabaseContext())
    {
        var customers = await context.Customer.Where(c => c.Title == "Mr.").ToListAsync();
        Debug.WriteLine(customers.Count);
    }
            
    return View();
}

雑にもHomeのIndexにコードを追加してしまったので、デバッグしてUrlを叩くと無事に動作することが確認できました。今回のサンプルでは、「Customer」テーブルが存在し、そこに「Title」列があって、その中で「Mr.」の行を取得した例になります。
(AzureのSQL Databaseでサンプルデータ付きで作れるのでそれを使ってます。中身は、みんな大好きAdventureWorksです。)


Startupクラスで接続文字列を取得してごねごねしてますが、別の書き方もできます。
「appsettings.json」で"EntityFramework"に関するコードを書いていますので、[public void ConfigureServices(IServiceCollection services)...]にて、こんな書き方でも問題なく接続できます。

services.AddEntityFramework()
    .AddSqlServer()
    .AddDbContext<dev_sqldatabaseContext>();

「AddDbContext」でoptionを書かないパターンです。ケースバイケースでどちらでもって感じですね。

>まとめ

現時点では、問題なくデータベース・ファーストで作れます。
以前から同様にpartialなクラスになってるので、必要に応じてコンストラクタを追加したり色々できますね。
一日半以上の遅刻となりましたが以上です。

Oh、次のチャック先生は、既にブログ公開済みになってますね...。

Visual Studio Team Servicesで CI Build する

C# Advent Calendar 2015 - Qiitaの16日目です。

「DevOpsサイコー」とか「継続的インテグレーションなんて常識だよねー」な昨今なので、
Visual Studio Team Services(こないだまでの名称は Visual Studio Online)での継続的インテグレーションについて書きます。

> Environment

  • Visual Studio 2015 Update1
  • .NET Framework 4.5.2(要注意!)
  • C#
  • Visual Studio Team Services でソースコード管理(Gitで)

> Overview

今回試したことは、

  • リモートリポジトリにプッシュしたら、自動でビルド&テストが実行される仕組み構築
  • 自動テストの結果をみてバグがあったら、VSTSにチケットを登録
  • チケットに紐づけてVisual Studioでプログラム改修&プッシュ!
  • 継続的ビルド&テスト....

デプロイももちろんできますが、長くなりそうな(長くなって力尽きた...)のでやめときます...。
なお、英語版のVS使ってるので日本語メニューの正式な名称がわかりません...各種操作は画面のキャプチャからお察しください。。。

> 準備:Visual Studio Team Services でプロジェクト作成

Visual Studio Team Services (以下、VSTS)のアカウントがない場合...うう...ググっていただき作成します。

ブラウザでVSTS開き、テキトーなプロジェクト(今回は「CI Build Demo」)を作っておきましょう。
[new]をポチッとして、
f:id:beachside:20151215185914p:plain

以下を入力します。

  • [Project name]にプロジェクト名「CI Build Demo」
  • [Process template]はなんでもいいですが今回は「Scrum」を選択
  • [Version control]は「Git」を選択

こんな感じ。
f:id:beachside:20151215190204p:plain
[Create project]をポチッとしてプロジェクトはできあがり。


> テキトーなプログラム作成

Visual Studioでの作業に移ります。
ささっとConsoleのプロジェクトと、そのテストプロジェクトをつくりまーす。

要注意点ですが、各プロジェクトのtarget frameworkは、「.NET Framework 4.5.2」を選択しています。「.NET Framework 4.6.1」でやったらVSTSのビルドでwarning出てそのあとの作業に影響が出たので....。

この辺の作業の手順は省略しますが、手抜きな感じで以下のプログラムかきました。

using System.Collections.Generic;
using System.Linq;

namespace CIBuildDemo2
{
    public class Demo1
    {
        public static string GetHoge(int number) => Enumerable.Repeat("ほげ", number).ToJoinString();
        public static string GetMofuMofu(int number) => Enumerable.Repeat("モコ", number).ToJoinString();
    }


    public static class EnumerableStringExtensions
    {
        public static string ToJoinString(this IEnumerable<string> items) => string.Join(string.Empty, items.ToArray());
    }
}

テストプログラムも手抜きな感じで....「hu」なのか「fu」なのかにも手抜き感がありますね...

using CIBuildDemo2;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace CIBuildDemo2Tests
{
    [TestClass()]
    public class Demo1Tests
    {
        [TestMethod()]
        public void GetHogeTest()
        {
            var expected = "ほげほげほげ";

            var actual = Demo1.GetHoge(3);
            Assert.AreEqual(expected, actual);
        }

        [TestMethod()]
        public void GetMohuTest()
        {
            var expected = "モフモフ";

            var actual = Demo1.GetMofuMofu(2);
            Assert.AreEqual(expected, actual);
        }
    }
}

> Visual StudioをVSTSに接続

Visual StudioをVSTSに接続しておきましょう。

Visual Studioでチームエクスプローラーを開きます。
上部のコンセントのようなアイコンをクリックします。
f:id:beachside:20151215194012p:plain


下図の感じでクリックします。
f:id:beachside:20151215194214p:plain


ウインドウが表示されますので、Serverをクリックします。
f:id:beachside:20151215194326p:plain


プロジェクトを追加するウインドウが表示されますので、VSTSのアカウントのURLを入力して追加します。
今回のデモの場合、「https://beachside.visualstudio.com/」です。

そして、プロジェクトへ接続します。接続するプロジェクトは、先ほど作った「CI Build Demo」です。
f:id:beachside:20151215194706p:plain


> VSTSでソースコード管理

先ほどVSTSで作ったプロジェクトをリモートリポジトリとして、ソースコード管理をしましょう。

Visual Studioで、ソリューションエクスプローラーを開きます。ソリューションを右クリックしてソリューションをソースコード管理に追加的なメニューをクリックします。
f:id:beachside:20151215191602p:plain

ソース管理は、VSTSでGitを指定しているので、「Git」を指定します。
(TFVCで管理したい場合は、VSTS側もTFVCに設定します。)
f:id:beachside:20151215191852p:plain

チームエクスプローラーが表示されますので、メッセージを入力してコミットします。
f:id:beachside:20151215192031p:plain

同期をクリックして画面に移動します。
f:id:beachside:20151215192151p:plain


初回だとリモートリポジトリと紐づいていないので、こんな画面が表示されます。下図の矢印のところのURLにリポジトリーのURLを入力します。
f:id:beachside:20151215192714p:plain


URLは、VSTSで確認することができます。ということで、ブラウザでVSTSをみます。
VSTSの「CI Build Demo」プロジェクトを開き、[CODE]をクリックして、確認しましょう。
f:id:beachside:20151215192948p:plain


コピーのボタンがありますが、ボタンでコピーすると「git remote add origin...」もコピーされるというトラップがあります。(CLI用のコピーなので、トラップっていうよりは優しさでしょうか)
URLだけを貼り付けて、Publishしましょう。

ブラウザーでVSTSみてみましょう。先ほどURLを確認したプロジェクトの[CODE]をリロードすると、コードが同期されていることが確認できます。
f:id:beachside:20151215193725p:plain


> 継続的ビルド&テストの設定

引き続き、VSTSで継続的ビルド&テストの設定します。

VSTSで[BUILD]をクリックします。デフォルトで、[All build definitions]が選択された状態になっていますので、新規追加できそうな気配のボタンをクリックします。
f:id:beachside:20151215195614p:plain


ウインドウが表示されます。
「Visual Studio」を選択し、[Next]をクリックします。
f:id:beachside:20151215195958p:plain


以下のウインドウが表示されます。
[Repository type]、[Repository]や[Default branch]を選択することができます。状況に応じてブランチごとの設定はここらへんでできます。
チェックボックスが一つありますが、文章の通りです。Gitなので、リモートリポジトリにプッシュする毎に動作させたい場合、このチェックボックスをチェックします。今回はチェックしておきましょう。
[Create]をクリックします。
f:id:beachside:20151215200430p:plain


動作の定義をする画面が表示されます。今回は、諸事情により[Index Sources & Publish Symbols]と[Copy and Publish Build Artifacts]を削除します。
f:id:beachside:20151215200740p:plain


残った2つの項目[Visual Studio Build]と[Visual Studio Test]の設定をします。
これは文言の通り、ビルドをするStepとテストをするStepです。
今回の設定は、[Visual Studio Test]で、エラーが出ても継続するチェックを入れるだけで大丈夫です。
f:id:beachside:20151215201110p:plain


今回は利用しませんが、スケジュール起動したい場合は、[Triggers]をクリックし、[Scheduled]から設定することもできます。
f:id:beachside:20151215201458p:plain


[save]をクリックして保存します。名称は「ci build - master」で保存しましたので、左ペインの[Build definitions]に表示されます。
これで、リモートリポジトリへプッシュするたびにビルドとテストが動きます。
試しに、この定義を手動で動かしてみましょう。[Queue build]をクリックすると手動で起動できます。
別のウインドウが出てきますので、OKをクリックすると実行されます。

> 継続的ビルドからチケット登録

さっそくバグが出たようですね。
f:id:beachside:20151215202142p:plain


実行結果についてみてます。左ペインの「Build...」をクリックして[Summary]をてみましょう。色々と表示されていていい感じです。
左ペインのその他の項目をクリックすると、それぞれの処理のログなどを見ることができます。
f:id:beachside:20151215202506p:plain


左ペインの「Build...」をクリックしたところで表示されている[Tests]をクリックすると、失敗したテストを確認することができます。さらに、[Create bug]をクリックし、作業項目(チケット)を登録することができます。
f:id:beachside:20151215202755p:plain


チケットが自動生成されます。テストのエラーメッセージも出ています。モフモフが期待値だったのに、モコモコになっているようですね。必要に応じて追記などをし、保存しましょう。
f:id:beachside:20151215203050p:plain


保存したチケットは、画面上部の[Work]をクリックして確認することができます。ここは、FeatureやBacklog Item、そしてバグといったチケットを管理する画面です。[Backlog]による一覧や、[Board]によるタスクボードのビューが用意されており、見せる化ができます。


次の作業に進む前に、ちょっと仕込みをします。
画面左上の [Queries] > [New] > [New query] をクリックします。
f:id:beachside:20151215203736p:plain


チケットを検索するクエリを登録する画面が表示されます。[Field]にはデフォルトで「Work Item Type」が入力されていますのでそのままにして、[Value]に「Bug」と入力します。再生してくれそうなボタンをクリックすると、画面の下の方にBugのチケットが表示されますでしょうか。(されない場合は入力が何か変な可能性ありです)

f:id:beachside:20151215204009p:plain


Bugのチケットが表示できることを確認し、保存をします。クエリの[Name]はわかりやすく「Bug list」、[Folder]は、「Shared Queries」を選びました。このクエリは、Visual Studioから実行してチケットを検索することができます。「Shared Queries」にしておくと、チームのメンバーで見ることができます。「My Queries」を選ぶと自分しか見えないので、自分に割り当てられたタスクを取得するクエリを作っておくと便利です。
f:id:beachside:20151215204216p:plain


> Visual Studio からチケットと紐づけてプログラム修正

Visual Studioでプログラムを修正しましょう。

先ほど登録したチケットはVisual Studioから確認することができます。
チームエクスプローラーでホームを開くと、作業項目という項目が表示されているはずです(表示されていない場合は、リロードとかしてみると表示されると思います)。
f:id:beachside:20151215205209p:plain


クリックすると、先ほど作ったクエリ「Bug list」が見えます。クリックして実行すると、チケットを確認することができます。
f:id:beachside:20151215205332p:plain


Viaual Studioでチケットを確認して作業ができるので便利ですね。

ではプログラムの修正ですが、Demo1.GetMofuMofuでモフモフするように修正するだけなので操作は省きます。
プログラム修正をしたら、チケットと紐づけてプッシュしましょう。
チームエクスプローラーを開き、[Changes]を開きます。[Related Work Item by ID]をクリックするとIDを入力できます。チケットのIDは先ほどの画面で確認できますので、入力してコミットしちゃいましょう。
f:id:beachside:20151215205902p:plain


そしてプッシュまでしてしまいます。
(VSTSのBuildを開く準備を....)

> CI Build

では、ブラウザでVSTSを見てみましょう。
[Build]を開きます。実行中のものは、[Queued]で確認することができます。
(起動していない場合は、設定がおかしくて動いていないか、既に完了して[Completed]にいるかのどちらかと...)
f:id:beachside:20151215210504p:plain


処理が終わると、[Completed]から完了した結果を見ることができます。今回はビルドして無事にすべてのテストをパスすることができました。Commitの情報も記載がありますので便利です。
f:id:beachside:20151215210729p:plain

> まとめ

うう...思った以上に長くなってしまいました...
CI Buildもこんな簡単にセットアップできるのでVSTSを使う価値あるなーと思っています。
まだまだ機能はたくさんあったり(バグもあったり...)しますので、有効に活用してDevOpsしたいと思っています。

Visual Studio 2015 を初めて使う方に、最初に知ってほしい ショートカットキー など

Qiita Visual Studio / Visual Studio Code Advent Calendar 2015 の9日目です。

Visual Studio / Visual Studio Code Advent Calendar 2015 - Qiita

VS CodeがこないだErich Gammaのポチッでオープンソース化されたので、そっちを書きたいなーと思いつつ...全然使ってないので、VSの入門ネタ行きます。

まだまだこれからVisual Studioを使って開発を始める方もきっとたくさんいると思いますので、私の独断と偏見で最初に知っておいてもらいたいショートカットキー(など)の機能を書きます。

Environment

Visual Studio 2015 Update1

Overview

以下に関する操作について紹介します。

  • フォーマット(整形)
  • コメントアウト / アンコメント
  • ナビゲーション関連
  • 小ネタ

テキストエディターに関する機能を中心です。
Visual Studio 2015よりも古いバージョンで動くものがほとんどではありますが、環境依存のものもあります。
またResharperやVS拡張を入れていると、別の動きをする場合もあります。

フォーマット(整形)

コードの修正やリファクタリングをしていてインデントがモコモコになったときに使います。

Ctrl + K, Ctrl + D

「Ctrl押したままK押してD押す」って操作です。

派手にモコモコにされたインデントでも、一瞬でビシーっとなります。
手でマメに直してる方がたまにいるので、知ってほしい機能でした。

f:id:beachside:20151208115405g:plain

覚え方...フォーマットする機能なので、 「K + フォーマットのF!」ではないのが残念。
実はその機能もあるんです。

Ctrl + K, Ctrl + F

これは、選択した範囲を整形します。選択するの面倒だし、特にC#を打ってる間はあまりな出番はないかも?

最初に紹介した「K→D」の方は、ドキュメント全体を整形するので、
「ドキュメントをフォーマっっっ!(のD!)」
ノリでCtrl + K,Ctrl + Dを押せそうでしょうか....

コメントアウト / アンコメント

コメントアウトしたり、アンコメントするショートカットキーです。
ツールバーのボタンを押してやってもいいんですが、結局ショートカットキー使った方が生産的な気がします。

コメントアウト(コメントにする)

Ctrl + K, Ctrl + C 

アンコメント(コメントアウトされているものを解除する)

Ctrl + K, Ctrl + U

行にカーソルを当てて操作すれば、行全体をコメントアウト/アンコメントできます。
複数行を選択すれば、選択した行すべてをコメントアウト/アンコメントできます。

f:id:beachside:20151208121607g:plain

覚え方は簡単ですね。コメントアウトの「C」か、アンコメントの「U」です。

ナビゲーション関連

「NavigateBackward」という機能です。

Ctrl + -(マイナス)

カーソルを当てたところを記憶していて、ショートカットキーを押すことで戻る機能です。ファイルをまたがっても戻ってくれます。
以下の例では、index.cshtmlで作業していて、なにげにMessageServices.csで作業→HomeController.csで2箇所をいじってから、NavigateBackwardで戻るところです。

f:id:beachside:20151208123315g:plain

オーノー!、Gifを張っても全く伝わらないですね....

メソッドの中からメソッドの定義に移動して、そこからまた別のメソッドに移動して、Modelの定義に移動したが続いて迷子になったときに便利です。
この機能を考慮してカーソルを置いていくと、一層効率的に利用できるでしょうか。

話はそれますが、
「定義に移動する」といえば、呼び出し側のメソッド名にカーソルを当てて

F12

ですね。
定義が書かれているメソッド名の上で

Shift + F12

を押すと、そのメソッドを読んでいる参照一覧が表示できます。

f:id:beachside:20151208123829g:plain 


VS2015からCodeLensが下位エディションでも使えるようになったので、似たことができます。結局CodeLensの機能ができたころからこれしか使ってません。
f:id:beachside:20151208124238g:plain


「定義に移動」といえば(その2)、
Interfaceを実装している場合、定義がInterfaceにするので、実装は別でしているってのはよくよくあることですよね。
この場合、F12で定義へ移動するとInterfaceへとんでしまい、実装へ移動できずに不便に感じる方も過去には多くいたと思います。
(Resharperにはこの機能がついてるので、私にはその不便さに気づかなかったわけですが.....)

ところが、Visual Studio 2015 Update1で、ResharperやVS拡張がなくても、実装へ飛ぶことができるようになりました。

操作は、呼ばれているメソッド名の上で右クリックして、「Go To Implementation」です。
f:id:beachside:20151208125521g:plain

Resharperだとショートカットキーが「Ctrl + F12」ですが、「Go To Implementation」にショートカットがついていないのは...まいっか...

小ネタ1

特定の行にカーソルを当てて、

Alt + ↑

MoveSelectedLinesUpができます。「↓」を押せば、反対の動作をします。ふとした時に便利です。

f:id:beachside:20151208130102g:plain

複数行選択で移動もできます。

小ネタ2

コピペが「Ctrl +C」「Ctrl +V」、カットも「Ctrl +X」なのは、この業界では誰もが知ってそうですね。
カットについては、カーソルを当てただけの状態でカットすると、一行すべてをカットします!(あれ、みんな知ってましたかね...)

ペーストといえば、CycleClipboardRing。クリップボードの履歴をたどることができます。

Ctrl + Shift + V

「Ctrl + Shift」を押した状態で「V」を何度も押すと、クリップボードの履歴をたどって貼り付けることができるので、連続してコピーして連続して貼り付けることもできます。これは便利!。
(といっても....個人的にはあまり使いませんが。。。)

小ネタ3

Ctrl + Tab

これでVisual Studio内で開いてるファイルのタブを切り替えることができます。複数のファイルを操作しているときは、「Ctrl +Tab」で切り替えた方が早いですね。

f:id:beachside:20151208160225g:plain


まとめ

ショートカットキーの既定の一覧はMSDNにあります。
https://msdn.microsoft.com/ja-jp/library/da5kh0wa.aspx

紹介した他にもViaul Studioにはモリモリに機能があるので、初めて使う方に色々知ってもらって生産性が向上したらいいなーと思う今日この頃でした。

AdventCalendarにまだ空きがあるなぁ....
qiita.com

Azure DocumentDB のSQLクエリ基礎

Microsoft Azure Advent Calendar 2015 6日目です。
DocumentDB の SQLクエリ について入門的なことを書きます。

Azure Cosmos DB Query Playgroundで試すこともできますし、Azure の新しい方のポータルで、アカウントを作って試すことができます。
(ポータルで試すともちろん課金が発生します)

> Environment

2015/12時点でのAzureポータル & DocumentDBの仕様

> 事前準備

Azureのポータルで、データベースアカウントを作成し、データベースを作成します。
そして、SampleCollectionというコレクションを作成しています。
作成はポータルで直感的にできますが、ここら辺を参考にしていただければと。
azure.microsoft.com

一応書いておくと、「コレクション」とは、リレーショナルデータベース(以下RDB)でいうところのテーブルの概念です。ただ、DocumentDB の課金はコレクション単位なので、使い方やスキーマの考え方は、同じと思わないほうが良いです。


コレクションを作成した後は、こんなdocumentを投入してます。

[
  {
    "id": "P1",
    "firstName": "Dan",
    "lastName": "gerous",
    "friends": [
      "P2",
      "P1"
    ],
    "phone": [
      {
        "home": "03-1111-1111",
        "mobile": "080-111-1111"
      }
    ]
  },
  {
    "id": "P2",
    "firstName": "Dan",
    "lastName": "dy",
    "friends": [
      "P1",
      "P3"
    ],
    "phone": [
      {
        "home": "03-2222-2222",
        "mobile": "080-222-222"
      }
    ]
  },
  {
    "id": "P3",
    "firstName": "Per",
    "lastName": "vert",
    "friends": [
      "P2",
      "P4"
    ],
    "phone": [
      {
        "home": "03-3333-3333",
        "mobile": "080-333-333"
      }
    ]
  },
  {
    "id": "P4",
    "firstName": "big",
    "lastName": "boobs",
    "friends": [
      "P1",
      "P3"
    ],
    "phone": [
      {
        "home": "03-4444-4444",
        "mobile": "080-444-4444"
      }
    ]
  }
]

Azureポータル からjsonのファイルをインポートすることができますが、現時点だと、1ファイル=1documentじゃないとインポートできないようです(もしかしたら私のスキーマが変なのか?)

> 基本

documentも用意できたところで本題です。
RDBの基本的なSQLクエリを知っている方であれば、だいたいそんな感じです。

SQLクエリを実行する際、RDBだとデータベースに対してアクセスしますが、
この DocumentDB の場合は、コレクションに対してアクセスします。
何が言いたいかというと、基本的なSELECT句で、

select * from c 

を実行すると、対象のコレクションのすべてのdocumentを取得します。
RDBだと「from」の後にテーブル名を指定しますが、
DocumentDB だと単一のコレクションにアクセスしていますので、
「from」の後は、コレクション名を指定しません。
というより、何を書いても大丈夫です(予約語使ったらそれは死んじゃいますが)。

select ... from ...の後は、
「where」ですね。
これもある程度RDBのSQLと似た感じで使えますが、「like」は使えません。

...まぁ、スキーマフリーのデータにSQLっぽいのが使えるだけでもありがたいと思うべきでしょうか。。。。。

「select」句で「*」以外を使う場合は、こんな感じです。

SELECT c.id,c.firstName, c.lastName FROM c
where c.firstName ="Dan"

出力結果

[
  {
    "id": "P1",
    "firstName": "Dan",
    "lastName": "gerous"
  },
  {
    "id": "P2",
    "firstName": "Dan",
    "lastName": "dy"
  }
]

大文字や小文字を間違えると、死亡する点は注意です。

firstNameとLastNameを結合したい場合は、「c.firstName + c.lastName」とは書けません。
そんな時は、Built-in functionを使います。今回は文字列をconcatしてくれる「CONCAT」を使っています。

SELECT c.id, CONCAT(c.firstName, c.lastName) FROM c
where c.firstName ="Dan"

出力結果

[
  {
    "id": "P1",
    "$1": "Dangerous"
  },
  {
    "id": "P2",
    "$1": "Dandy"
  }
]

built-in functionを使うと出力された結果が「$1」とか出ちゃうのは微妙なので、AS句を指定してあげましょう。
あと、firstNameとlastNameの間にスペースを入れてあげましょう。

SELECT c.id, CONCAT(c.firstName, " ", c.lastName) AS fullName FROM c
where c.firstName ="Dan"

出力結果

[
  {
    "id": "P1",
    "fullName": "Dan gerous"
  },
  {
    "id": "P2",
    "fullName": "Dan dy"
  }
]

Built-in functionは数学的なやつもたくさん用意されていますので、多少複雑なことをする場合は以下を参考にできることを整理しておいたほうが良いです。
https://msdn.microsoft.com/ja-jp/library/azure/dn782250.aspx

>ちょっと実践的に

データを探すときによく使う2つを紹介します。
まずは、Built-in functionの「ARRAY_CONTAINS」
こんな感じで使います。

SELECT c.id,CONCAT(c.firstName, " ", c.lastName) AS fullName FROM c
where ARRAY_CONTAINS(c.friends,"P1") 

出力結果

[
  {
    "id": "P1",
    "fullName": "Dan gerous"
  },
  {
    "id": "P2",
    "fullName": "Dan dy"
  },
  {
    "id": "P4",
    "fullName": "big boobs"
  }
]

配列の中で何かを探したいときには便利です。

ただ、これだと今回のサンプルのdocumentで「phone」の中を調べることができない?感じなので、
そんな時は「JOIN」を使います。
RDBのJOINだと別のテーブルをくっつけるのに使うのが一般的ですが、DocumentDBの場合はちょっと違います。
以下の例を見てみましょう。

SELECT c.id, CONCAT(c.firstName, " ", c.lastName) AS fullName, p.home FROM c
join p in c.phone
where p.mobile ="080-222-222"

出力結果

[
  {
    "id": "P2",
    "fullName": "Dan dy",
    "home": "03-2222-2222"
  }
]

「JOIN」を使って、mobileの中を覗き込みます。
この「JOIN」を使うことで、azureポータルでデータの検索は大体できると思います(単純なものであれば)。

> その他

(結構前ですが)Azureポータルでは、選択した部分のみのクエリが実行できるようになったり、
「OrderBy」が使えるようになったりで便利になってきました。

Where likeが使えないのは、何かと不便だったりします。
(RDBと同じようなことができることを求めることは、なんか違うよねとか思ってはいますが)

ただ、DocumentDBは他のAzureの機能同様にポンポンUpdateしているので、今後に期待したいです。

最後に、参考情報のリンクを貼っておきます。

https://azure.microsoft.com/ja-jp/documentation/articles/documentdb-sql-query/azure.microsoft.com

https://azure.microsoft.com/ja-jp/documentation/articles/documentdb-sql-query-cheat-sheet/azure.microsoft.com

Azure DocumentDB の開発ことはじめ - Client クラスの開発(2/2)

さて、前回Azure DocumentDB の開発ことはじめ - Client クラスの開発(1/2) - BEACHSIDE BLOGの続きです。

> Environment

  • Visual Studio 2015 Update1(RC)で、コンソールアプリ
  • .NET Framework4.5.2
  • Nugetの Microsoft.Azure.DocumentDB (1.5.0)
  • Nugetの Microsoft.Azure.DocumentDB.TransientFaultHandling (1.2.0)

多少カスタマイズされてますが、ソースはこちらにあります。

> Overview


前回 >> Clientクラスの開発(1/2)
  0. 事前準備
  1. コンソールアプリ作成と下準備
  2. Clientクラス「DdbClinet」クラスの作成1(データベースの操作)
  3. Clientのインスタンスを生成するための「DdbClinetFactory」クラス作成

今回 >> Clientクラスの開発(2/2)
  4. Clientクラス「DdbClinet」クラスの作成2(コレクションの操作、Documentの操作)
  5. Documentを操作するクラスの作成
  6. Program.csからコールしてみる

>4. Clientクラス「DdbClinet」クラスの作成2(コレクションの操作、Documentの操作)

前回作成したDdbClinetクラスに追記していきます。

    class DdbClinet : IDisposable
    {
      //前回の部分は省略

      #region collectionの操作

        public DocumentCollection GetCollectionIfNotExistsCreate(string collectionName, CollectionOfferType offerType = CollectionOfferType.S1)
        {
            var collection = TryGetCollection(DatabaseInstance, collectionName) ??
                                CreateCollectionAsync(DatabaseInstance.CollectionsLink, collectionName, offerType).Result;
            if (collection == null) throw new InvalidOperationException($"コレクションの生成に異常ありんご!({collectionName})");

            return collection;
        }

        DocumentCollection TryGetCollection(Database database, string collectionName)
        {

            return _client.CreateDocumentCollectionQuery(database.CollectionsLink)
                        .Where(c => c.Id == collectionName).AsEnumerable().FirstOrDefault();
        }

        async Task<DocumentCollection> CreateCollectionAsync(string databaseLink, string collectionName, CollectionOfferType offerType)
        {
            var collection = new DocumentCollection() { Id = collectionName };
            var requestOptions = new RequestOptions() { OfferType = ComvertOfferTypeToString(offerType) };
            return await _client.CreateDocumentCollectionAsync(databaseLink, collection, requestOptions);

        }

        static string ComvertOfferTypeToString(CollectionOfferType offerType) => Enum.GetName(typeof(CollectionOfferType), offerType).ToUpperInvariant();

        #endregion

コレクションのインスタンスの取得(存在しなければ生成する)メソッドです。DocumentDBでは、DocumentのCRUDをする際に、コレクションのselflinkというものを使うので、その情報を取得するためのメソッドたちです。
コレクションの作成にはOfferType、つまり価格レベル(性能レベル)を設定するので、コレクションを作る際にその値を指定します。今回はテスト用でもあるので、価格そして性能が最小の「S1」を指定しています。
ここでは、その指定をenumで定義しますので、どっかに適当に定義しておきましょう。

public enum CollectionOfferType
{
    S1,
    S2,
    S3
}


さてさて、次はDocumentを操作するメソッドをいくつか用意してみます。上記の下にそのままコーディングします。

#region documentの操作

public async Task<dynamic> InsertDocumentAsync<TEntity>(string collectionSelfLink, TEntity entity, bool configureAwait)
{
    return await _client.CreateDocumentAsync(collectionSelfLink, entity).ConfigureAwait(configureAwait);
}

public IEnumerable<TEntity> GetDocumentsByPredicate<TEntity>(string collectionSelfLink, Expression<Func<TEntity, bool>> predicate)
{

    return _client.CreateDocumentQuery<TEntity>(collectionSelfLink, _feedOptions).Where(predicate).AsEnumerable();
}


public async Task DeleteDocumentsByQueryAsync(string collectionSelfLink, string query, bool configureAwait)
{
    var documents = _client.CreateDocumentQuery<Document>(collectionSelfLink, query).AsEnumerable().ToArray();
    if (documents.Any())
    {
        foreach (var doc in documents) await DeleteDocumentAsunc(doc, configureAwait);
    }
}

public static async Task DeleteDocumentAsunc(Document document, bool cofigureAwait = true)
{
    await _client.DeleteDocumentAsync(document.SelfLink).ConfigureAwait(cofigureAwait);
}

#endregion

ドキュメントをInsertする「InsertDocumentAsync」、
条件で指定してDocumentを取り出す「GetDocumentsByPredicate」、
クエリで指定したドキュメントを削除する「DeleteDocumentsByQueryAsync」を用意してみました。
ConfigureAwaitをどうしよう?どうすべき?みたいな悩んでます。とりあえずこんなことにしてますが....どうなんすかね?!用と次第だと思いますが...勉強不足です。
(GetDocumentsByPredicateは、Expression使うほどのこともないですが、別の検証で使ってたのでそのまま流用。。。)

さて、実際にこのメソッドを使ってみましょう。次に進みます。

>5. Documentを操作するクラスの作成

と、その前に、Documentのスキーマ的なクラスとして、今回はPersonクラスを作ります。

using Newtonsoft.Json;


public class Person
{
    [JsonProperty(PropertyName = "personId")]
    public string PersonId { get; set; }

    [JsonProperty(PropertyName = "firstName")]
    public string FirstName { get; set; }

    [JsonProperty(PropertyName = "lastName")]
    public string LastName { get; set; }

    [JsonProperty(PropertyName = "friends")]
    public int[] Friends { get; set; }
}

今回のサンプルでは、このModelをDocumentに入れたり取り出したりします。

Documentを操作するクラス「DemoCollecitonManager」(名前ダサい)を作ります。DemoCollecitonというコレクションを操作する用ですが、Managerとかないかなーと思いつつつつつつつつつつけちゃいました。。。。。

public class DemoCollecitonManager
{
    #region fields

    // ここら辺Lazy<T>とかThreadLocalにすべきかとかは、用途に応じて...
    static Lazy<DocumentCollection> _demoCollection;
    static Lazy<DdbClinet> _clinet;

    static DdbClinet Client => _clinet.Value;
    static DocumentCollection DemoCollection => _demoCollection.Value;

    static DemoCollecitonManager()
    {
        _clinet = new Lazy<DdbClinet>(DdbClinetFactory.GetInstance);
        _demoCollection = new Lazy<DocumentCollection>(() => Client.GetCollectionIfNotExistsCreate("DemoCollection"));
    }

    #endregion

    #region Documentの操作

    public static async Task InsertPersonDocument(Person model)
    {
        await Client.InsertDocumentAsync(DemoCollection.SelfLink, model, true);
    }

    public static IEnumerable<Person> GetPersonModelsById(int personId)
    {
        return Client.GetDocumentsByPredicate<Person>(DemoCollection.DocumentsLink, p => p.PersonId == personId);
    }


    #endregion
}

DocumentDBはとにかくネットワークアクセスの回数を減らすのが重要なので、コレクションとClientのメンバーを持っています。コレクションとかのインスタンス周り、コメントにも書いてますが、Lazy使うの?ThreadLocal使うの?とかは用途次第です。今回はサンプル程度ということで何となくでLazy使ってます。

プロパティは、それぞれのインスタンスがなければインスタンス化するだけのシンプルなものです。
Documentの操作は、DdbClinetのメソッドをコールしてるだけです。

>6. Program.csからコールしてみる

さて、最後にコンソールのProgram.csから呼んでみましょう。

class Program
{
    static void Main(string[] args)
    {
        var person1 = new Person()
        {
            PersonId = "P1",
            FirstName = "Char",
            LastName = "ming",
            Friends = new[] { 2, 3 }
        };

        var person2 = new Person()
        {
            PersonId = "P2",
            FirstName = "Dan",
            LastName = "Dy",
            Friends = new[] { 2, 3 }
        };

        //insert samples
        DemoCollecitonManager.InsertPersonDocument(person1).Wait();
        DemoCollecitonManager.InsertPersonDocument(person2).Wait();

        //select sample
        var dandy = DemoCollecitonManager.GetPersonModelsById(person2.PersonId);
        Console.WriteLine(JsonConvert.SerializeObject(dandy, Formatting.Indented));

        Console.ReadKey();

    }
}

Personを2つ用意して、Insertします。あとは、PersonIdで検索して、コンソールにjsonで出力します。
接続の初回は、データベースがなければ作成するし、コレクションがなければ作成するしで遅いですが、DemoCollecitonManagerクラスのDemoCollectionやDdbClinetクラスのDatabaseInstanceに値が取得できた状態でのアクセスならそんなに遅くないです。

ただ、やっぱりDocumentDBと同一のリージョンじゃないと、10倍くらいの速度差がでます(私の環境では)。

deleteについては触れてませんが、queryでdelete対象のdocumentsを取得して削除メソッド呼ぶだけです。クエリで取得すると、IEnumerableを返すので、複数の削除を前提に削除メソッドを作ってます。
コードのサンプルには書いてます。

さて、検証が終わったらデータベースやコレクションはポータルから削除しておきましょう。しないとガシガシ課金がかかります!
以前に性能検証とかでコレクションをS3でたくさん作ったときは、削除し忘れてクレジットが溶けた気分になりました。


今のところ情報がさほど多くないので、これ以外にいい方法あるのかなーとか気になってるところなので、情報が増えたらうれしいですね。

先日、この記事が出てましたが、気にはしてるけど見てないので、今週見よう...きっと...
azure.microsoft.com
github.com

Azure DocumentDB の開発ことはじめ - Client クラスの開発(1/2)

Azure DocumentDb に接続する Client を開発するのに、
Microsoft.Azure.Documents.Client名前空間の DocumentClient クラスでは、以前のブログAzure DocumentDB を使うときに知っておきたいいくつかのこと - BEACHSIDE BLOG
でちょっと触れた「 Request rate is too large 」のエラーに対応するのに大変だったり、その他諸々の設定するのが面倒と思ってます。
ということで、ちょっと拡張したClientクラスを作った際のメモです。

> Environment

  • Visual Studio 2015 Update1(RC)で、コンソールアプリ
  • .NET Framework4.5.2
  • Nugetの Microsoft.Azure.DocumentDB (1.5.0)
  • Nugetの Microsoft.Azure.DocumentDB.TransientFaultHandling (1.2.0)

多少カスタマイズされてますが、ソースはこちらにあります。

> Overview

以下の6章の編成です。

今回 >> Clientクラスの開発(1/2)
  0. 事前準備
  1. コンソールアプリ作成と下準備
  2. Clientクラス「DdbClinet」クラスの作成1(データベースの操作)
  3. Clientのインスタンスを生成するための「DdbClinetFactory」クラス作成


次回 >> Clientクラスの開発(2/2)
  4. Clientクラス「DdbClinet」クラスの作成2(コレクションの操作、Documentの操作)
  5. Documentを操作するクラスの作成
  6. Program.csからコールしてみる


DocumentDBのアクセスに関するクラスの構成は、3つに分けて構成しました。

  • DdbClinet

コアとなるDocumentDBのClientのクラス。データベースインスタンスの操作と、DocumentのCRUDに関する共通メソッドで構成。

  • DdbClinetFactory

DdbClinetのインスタンス生成クラス。Clientの構成情報をどっかから取得し、インスタンスを生成する。

  • データベースごとにDocumentのCRUDをするクラス

コレクション内のDocumentのCRUD操作の扱うクラス。今回は単一のコレクションしか扱ってませんが、複数のコレクションを扱うのであれば、コレクションの定義を増やして使う、または責務を考えて別クラスにするとか...使い方は用途次第。



余談ですが、
今はVisual Studio英語版を使ってるので、キャプチャーがそんな表示になってます。
(Update1CTPのとき、私だけ?!エディタのフォントが変更できなくなる事故があったのですが、Visual Studio 日本語版のデフォルトのフォント「MSゴシック」でコードを見てると精神不安定?精神崩壊に陥りそうでプログラミングに支障がでるので、デフォルトのフォントがConsolasな英語版にしました...)


>0. 事前準備

azureのポータル側で、DocumentDB のアカウントを事前に作っておきます。まだの方は、ここら辺を参考に...
azure.microsoft.com
まぁ、色々とお金かかると思うので、自己責任でご注意しながらやってくださいね。

接続に使うURLや、エンドポイント、認証キーの情報を取得するためです。

>1. コンソールアプリ作成と下準備

まずは、新規プロジェクトをconsoleアプリ作成します。
f:id:beachside:20151101155325p:plain
ここら辺はさっと。

今回の開発用にnugetでいくつかインストールします。

まず、nuget package managerを開きます。
f:id:beachside:20151101160122p:plain

DocumentDB の開発に必要なものを2つインストールします。
f:id:beachside:20151101160143p:plain
検索機能を使って「documentdb」で検索すると、さっと選択できます。そして以下をインストール。2015/10/30時点での最新版です。

  • Microsoft.Azure.DocumentDB (1.5.0)
  • Microsoft.Azure.DocumentDB.TransientFaultHandling (1.2.0)


次は、DocumentDBの接続情報は、とりあえず定番はアプリケーション構成ファイルに入れて、読みだそうと思うので、プロジェクトに参照の追加をします。
プロジェクトの「参照」を右クリックで参照の追加を選択します(日本語名称間違ってたらごめんなさい)。
f:id:beachside:20151101160748p:plain

画面左側は、「Assemblies」を選択し、右上の検索で、「config」と入力すれば、「System.Configuration」がでます。これを追加します。

>2. Clientクラス「DdbClinet」クラスの作成1(データベースの操作)

本家Azureのドキュメントのサンプルで使っているのは、Client、Microsoft.Azure.Documents.Client 名前空間の DocumentClient クラスですが、色々辛いです。

ということで、DocumentClientクラスの代わりに、
Microsoft.Azure.Documents.Client.TransientFaultHandling 名前空間の IReliableReadWriteDocumentClient クラスを使います。
これは、ざっくり説明すると「Request rate is too large」に容易に対応できるClientクラスです(ざっくり過ぎですいませぬ...)。

余談ですが、
staticに使いまわす前提でDisposeを明示的にしないのであれば、IReliableReadDocumentClient クラスを使ってImmutableにしてあげる方がいいなーと思ってます。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Client;
using Microsoft.Azure.Documents.Client.TransientFaultHandling;
class DdbClinet : IDisposable
{
    private IReliableReadWriteDocumentClient _client;
    private readonly FeedOptions _feedOptions;
    private readonly string _databaseName;
  // ここら辺、Lazy<T>とかThreadLocalにすべきかとかは用途に応じて...
    private Lazy<Database> _databaseInstance;
    private Database DatabaseInstance => _databaseInstance.Value;
  
    public DdbClinet(IReliableReadWriteDocumentClient client, string databaseName, int feedOptionMaxItemCount)
    {
        _client = client;
        _databaseName = databaseName;
        _feedOptions = new FeedOptions { MaxItemCount = feedOptionMaxItemCount };
        _databaseInstance = new Lazy<Database>(() => GetDatabaseIfNotExistsCreate(_databaseName));
    }

    #region databaseの操作

    Database GetDatabaseIfNotExistsCreate(string name)
    {
        var database = TryGetDatabase(name) ?? CreateDatabaseAsync(name).Result;
        if (database == null) throw new InvalidOperationException($"データベースの生成に異常ありんご!({name})");

        return database;
    }

    Database TryGetDatabase(string name)
    {
        return _client.CreateDatabaseQuery().Where(d => d.Id == name).AsEnumerable().FirstOrDefault();
    }

    async Task<Database> CreateDatabaseAsync(string name)
    {
        return await _client.CreateDatabaseAsync(new Database { Id = name });
    }

    #endregion
}

いきなりダーンと書いてしまいました...さっくり説明してきます。

データベースのインスタンス取得のためのメソッドが並んでますが、ぼちぼちデータベースの情報を使うのでプロパティに入れて使いまわすようにしています。
また、データベースのインスタンスを取得にいって、存在しなければデータベースを作っちゃってます。
ここでは書いてませんが、IDisposableをimplimentしてますので、エラーがでないようにDisposeメソッドは適当に書いておきましょうね。

その他諸々使って動かせば理解できるコードだと思いますので、とりあえず次に進んでClientのインスタンスを生成するクラスを作ります。

>3. Clientのインスタンスを生成するための「DdbClinetFactory」クラス作成

先ほど書いたDdbClinetクラスのコンストラクターを呼び出すための値を生成するクラスです。

using Microsoft.Azure.Documents.Client;
using Microsoft.Azure.Documents.Client.TransientFaultHandling;
using Microsoft.Practices.EnterpriseLibrary.TransientFaultHandling;
using static System.Configuration.ConfigurationManager;

namespace DocumentDbDemo.DocumentDbRepository
{
    static class DdbClinetFactory
    {

        static readonly ConnectionMode ConnectionMode = ConnectionMode.Direct;
        static readonly Protocol Protocol = Protocol.Tcp;

        static readonly string EndpointUri;
        static readonly string AuthorizationKey;
        static readonly int RetryCount;
        static readonly TimeSpan RetryInterval;
        static readonly string DatabaseName;
        static readonly int FeedOptionMaxItemCount;

        static DdbClinetFactory()
        {
            EndpointUri = AppSettings["DdbConfig.endpointUri"];
            AuthorizationKey = AppSettings["DdbConfig.authorizationKey"];
            RetryCount = int.Parse(AppSettings["DdbConfig.retryCount"]);
            RetryInterval = TimeSpan.FromSeconds(int.Parse(AppSettings["DdbConfig.retryInterval"]));
            FeedOptionMaxItemCount = int.Parse(AppSettings["DdbConfig.feedOptionMaxItemCount"]);
            DatabaseName = AppSettings["DdbConfig.databaseName"];
        }
        public static DdbClinet GetInstance()
        {
            var policy = CreateConnectionPolicy(ConnectionMode, Protocol);
            var documentClinet = CreateDocumentClient(EndpointUri, AuthorizationKey, policy);
            var strategy = GetRetryStrategy(null, RetryCount, RetryInterval, false);
            return new DdbClinet(documentClinet.AsReliable(strategy), DatabaseName, FeedOptionMaxItemCount);

        }

        static ConnectionPolicy CreateConnectionPolicy(ConnectionMode connectionMode, Protocol protocol)
        {
            return new ConnectionPolicy
            {
                ConnectionMode = connectionMode,
                ConnectionProtocol = protocol
            };
        }
        static DocumentClient CreateDocumentClient(string endpointUrl, string authorizationKey, ConnectionPolicy connectionPolicy)
                => new DocumentClient(new Uri(endpointUrl), authorizationKey, connectionPolicy);

        static FixedInterval GetRetryStrategy(string name, int retryCount, TimeSpan retryInterval, bool firstFastRetry)
                => new FixedInterval(name, retryCount, retryInterval, firstFastRetry);

    }
}

ここもドーンと書いてしまいました。

まず余談として、
DocumentDBの接続情報はApp.configから取得しますので、c#6.0から使えるusing staticも何気に使ってます。

using static System.Configuration.ConfigurationManager;

これで、アプリケーション構成ファイルから値を取得する際に毎回、

var somethingValue= ConfigurationManager.AppSettings["somethingKey"];
var somethingValue= AppSettings["somethingKey"];

と書けます。まぁ、たいしたことないんですが、新しいことは何かと使いたいものです。


さて本題に戻りますが、
フィールドメンバーにしている値が、DocumentDBに接続する際に設定する情報の一覧だと思ってください。実際の値は、App.configに格納していますが、直接値を書いているもの(ConnectionModeとProtocol)は、めんどくさいから書いただけです。

設定値についての不明な場合は、Azure本家や
Azure DocumentDB を使うときに知っておきたいいくつかのこと - BEACHSIDE BLOG
あたりを辿って情報を探してみましょう。

ここでの設定で、以前も紹介したパフォーマンスに関することもここでおおよそ網羅できます。

beachside.hatenablog.com

azure.microsoft.com

インデックスポリシーとかその他諸々漏れているものは、...........そのうち勉強します.....(汗)

コードについてざっくり説明ですが、
GetInstanceを呼べば、DocumentDBに接続するためのClientクラスが生成されます。リトライのストラテジーやFeedOptionもバシーっと設定していますので、安定のClientになりました(と思いたい)。

DocumentDBの構成情報を格納したApp.configの中はこんな感じに設定してます。
DdbConfig.endpointUrl、DdbConfig.authorizationKey、DdbConfig.databaseNameの値は、Azureポータルから自分の情報を取得して入れます。

リトライやFeedOptionに関する値は、私はこの値を使ってますが、自身の判断で必要な値を入力します。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
    </startup>
<appSettings>
  <add key="DdbConfig.endpointUri" value="https://yoururl!!!!!.documents.azure.com:443/"/>
  <add key="DdbConfig.authorizationKey" value="your authorizationKey!"/>
  <add key="DdbConfig.retryCount" value="10"/>
  <add key="DdbConfig.retryInterval" value="1"/>
  <add key="DdbConfig.feedOptionMaxItemCount" value="1000"/>
  <add key="DdbConfig.databaseName" value="Demo"/>
</appSettings>
</configuration>

これで大体基本的なところはできました。
データベースインスタンスを複数扱い、インスタンスによって設定値を変えるのであれば、各種定義用のクラスを作ってそれを受け取るコンストラクターを作っておけば大丈夫ですね(私が別のプログラムでそうしてるだけですが)。


(ってかバタバタ書くと説明書くのがめんどくさくなっちゃって......読み返すとわかりにくいなー...)


おなかがすいたので、また次回にします。