T-SQL - DROP IF EXISTS

先週SQL Server 2016の提供が開始されました。

新機能を調べだしたところですが、まずはとりあえずDROP IF EXISTSを試してみました。

開発中、試行錯誤してたりとかでテーブル構造がなかなか固まらず、テーブルを作っては消してまた作るということを繰り返すことがありました。 私はGUIでテーブルを作らずクエリで作る方で、さらにGUIでテーブルを消すこともめんどくさくて、次のようなクエリをよく書いていました。

-- テーブルが存在していればテーブルを消す
if object_id(N't') is not null
    drop table t;

-- テーブルを作る
create table t(c int);

このテーブルが存在していれば消すということが、 SQL Server 2016からはすっきり書けるようになりました。それがDROP IF EXISTS。

-- テーブルが存在していればテーブルを消す
drop table if exists t;

ぶっちゃけ地味ですがわかりやすいですね。DROP文全般で使えるようですし、ALTER TABLEでカラムを削除するときも使えるみたいです。

参考

次はJSON関係を試してみようかなーと思います。

FlagsAttributeを指定したenumの文字列

ものすごく小ネタですが、FlagsAttributeを指定したenumの文字列表現はどうなるんだろ?とふと気になって試してみました。

// トッピング
[Flags]
public enum Toppings {
    None = 0x00,   // なし
    Nitamago = 0x01,   // 煮卵
    CharSiu = 0x02,    // チャーシュー
    Menma = 0x04,  // メンマ
    Moyashi = 0x08,    // もやし
}

出力してみます。フラグを組み合わせた場合はカンマ区切りの文字列となるようです。文字列の並びはenumの値順ですかね。

Console.WriteLine(Toppings.CharSiu);
// CharSiu

Console.WriteLine(Toppings.CharSiu | Toppings.Menma);
// CharSiu, Menma

Console.WriteLine(Toppings.CharSiu | Toppings.Nitamago | Toppings.Menma);
// Nitamago, CharSiu, Menma

Console.WriteLine(Toppings.None);
// None

もちろんカンマ区切りの文字列をパースできると。

var toppings = default(Toppings);
if (Enum.TryParse<Toppings>("CharSiu, Moyashi, Nitamago", out toppings)) {
    Console.WriteLine(toppings);
    // Nitamago, CharSiu, Moyashi
}

どうでもいいことですが、あぶりメンマが好きです。

ASP.NET MVC - FilterAttribute.OrderとFilterScopeによるフィルタの実行順を確認する

FilterAttribute.OrderとFilterScopeによるフィルタの実行順を確認します。

同じフィルタが複数ある場合に

  1. FilterAttributeのOrderプロパティの昇順(小さい値から大きい値の順序)
  2. FilterScopeの昇順

でフィルタがソートされて実行される、というあたりを確認します。

FilterAttribute.Order

Orderプロパティは想像しやすいかなと思います。

なので略。

FilterScope

FilterScopeはMVC内部で使われているもので、使う側が指定するものではないようです。

ざっくり言うとフィルタの指定した場所によって決まるものという解釈でいいのかなと思います。

  • FilterScope.First・・・コントローラ自体のフィルタ
  • FilterScope.Global・・・グローバルフィルタに登録したフィルタ
  • FilterScope.Controller・・・コントローラのクラスに指定したFilterAttribute
  • FilterScope.Action・・・アクションメソッドに指定したFilterAttribute
  • FilterScope.Last・・・定義されているだけで使われていないような・・・?

といった感じです。昇順もこの順番です。

FilterScope.Firstを少し補足します。

コントローラは各フィルタのインターフェイスを実装しています。メソッドをオーバーライドすることによってフィルタの処理を書くことができます。このときコントローラ自体のスコープはFilterScope.Firstとなります。またOrderはInt32.MinValueとして扱われているようです。つまりコントローラ自体のフィルタ(オーバーライドしたメソッド)は必ず最初に実行されることになります。

サンプルで確認

ここからはアクションフィルタに絞ってサンプルコードで確認してみます。

まずはアクションフィルタを実装したフィルタ属性を用意します。この属性はトレース出力だけしています。

// アクションフィルタだけを実装したフィルタ属性
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class TestFilterAttribute : FilterAttribute, IActionFilter {
    public string Label { get; set; } // 確認用に出力する文字列

    public void OnActionExecuting(ActionExecutingContext filterContext) {
        // Orderとラベルをトレース
        Trace.WriteLine($"{nameof(TestFilterAttribute)}({Order}, \"{Label}\").{nameof(OnActionExecuting)}");
    }

    public void OnActionExecuted(ActionExecutedContext filterContext) {
        Trace.WriteLine($"{nameof(TestFilterAttribute)}({Order}, \"{Label}\").{nameof(OnActionExecuted)}");
    }
}

次に用意した属性をグローバルフィルタに設定します。Orderを省略した場合は-1になります。

// グローバルフィルタとして属性追加(ラベル:"Global")
// Orderを指定しない場合は-1
GlobalFilters.Filters.Add(new TestFilterAttribute { Label = "Global" });
GlobalFilters.Filters.Add(new TestFilterAttribute { Order = 1, Label = "Global" });
GlobalFilters.Filters.Add(new TestFilterAttribute { Order = 2, Label = "Global" });

さらに用意した属性をコントローラのクラスとメソッドにも指定します。またコントローラ自体のアクションフィルタもオーバーライドしてトレースするようにしておきます。

// クラスに属性(ラベル:"Controller")
[TestFilter(Label = "Controller")]
[TestFilter(Order = 1, Label = "Controller")]
[TestFilter(Order = 2, Label = "Controller")]
public class DefaultController : Controller {
    // コントローラ自体のアクションフィルタ
    protected override void OnActionExecuting(ActionExecutingContext filterContext) {
        Trace.WriteLine($"{nameof(DefaultController)}.{nameof(OnActionExecuting)}");
    }
    protected override void OnActionExecuted(ActionExecutedContext filterContext) {
        Trace.WriteLine($"{nameof(DefaultController)}.{nameof(OnActionExecuted)}");
    }

    // メソッドに属性(ラベル:"Action")
    [TestFilter(Label = "Action")]
    [TestFilter(Order = 1, Label = "Action")]
    [TestFilter(Order = 2, Label = "Action")]
    public ActionResult Index() {
        Trace.WriteLine($"{nameof(DefaultController)}.{nameof(Index)}");
        return new EmptyResult();
    }
}

そしてデバッグ実行すると出力結果は次のように。わかりやすいように改行しつつコメントも足しています。

// コントローラ自体のメソッドが真っ先に呼ばれる
DefaultController.OnActionExecuting

// Order = -1(Orderを指定しない)のアクションフィルタ
// Global、Controller、Actionの順で実行される
TestFilterAttribute(-1, "Global").OnActionExecuting
TestFilterAttribute(-1, "Controller").OnActionExecuting
TestFilterAttribute(-1, "Action").OnActionExecuting

// Order = 1のアクションフィルタ
TestFilterAttribute(1, "Global").OnActionExecuting
TestFilterAttribute(1, "Controller").OnActionExecuting
TestFilterAttribute(1, "Action").OnActionExecuting

// Order = 2のアクションフィルタ
TestFilterAttribute(2, "Global").OnActionExecuting
TestFilterAttribute(2, "Controller").OnActionExecuting
TestFilterAttribute(2, "Action").OnActionExecuting

// ここでアクションメソッドの呼び出し
DefaultController.Index

// OnActionExecutedは逆順
TestFilterAttribute(2, "Action").OnActionExecuted
TestFilterAttribute(2, "Controller").OnActionExecuted
TestFilterAttribute(2, "Global").OnActionExecuted

TestFilterAttribute(1, "Action").OnActionExecuted
TestFilterAttribute(1, "Controller").OnActionExecuted
TestFilterAttribute(1, "Global").OnActionExecuted

TestFilterAttribute(-1, "Action").OnActionExecuted
TestFilterAttribute(-1, "Controller").OnActionExecuted
TestFilterAttribute(-1, "Global").OnActionExecuted

DefaultController.OnActionExecuted

Orderの昇順、Orderが同じ場合はグローバルフィルタ、クラスのフィルタ属性、メソッドのフィルタ属性の順(FilterScopeの昇順)で実行されることを確認できました。さらにOnActionExecutingは上記の実行順で実行され、OnActionExecutedはその逆順で実行されることも確認できました。

ちなみにOrderもFilterScopeも同じ場合の実行順は未定義とのことです。

種類、順序、スコープが同じフィルターの実行順序は未定義です。

おしまい。

ASP.NET MVC - input要素のplaceholder属性に使うHtmlHelperの拡張メソッドを作ってみた

気持ち今さら感はありますが、input要素のplaceholder属性にテキストを出力するためのHtmlHelperの拡張メソッドを作ってみました。正直なところやってみて作りました!というほどではなかったんですが、まあ試してみたかったんです、ということで。

モデルのプロパティに対する表示名は、DisplayAttributeのNameプロパティとDisplayNameForメソッドを使えばいい感じに出力できます。そんなノリでできないかなと思って調べてみたところ、DisplayAttributeのPromtプロパティがそれっぽいかな?使っていいかな?と。MVCのソースも調べてみるとこのプロパティの値はModelMetadataのWatermarkプロパティから取得できそうかなと。

ということで作ってみた拡張メソッドがこちらです。

public static class PlaceholderExtesions {
    // placeholder属性用の文字列を取得
    public static MvcHtmlString PlaceholderFor<TModel, TValue>(
        this HtmlHelper<TModel> htmlHelper,
        Expression<Func<TModel, TValue>> expression) {

        // モデルのメタデータを取得
        var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);

        // メタデータのウォーターマークを使う
        // html属性として使うと想定してエンコード
        return new MvcHtmlString(HttpUtility.HtmlAttributeEncode(metadata.Watermark));
    }
}

それでは使ってみます。まずはモデルを用意します。

// モデル
public class TestFormModel {
    // Promptにplaceholder属性に出力したい文字列を指定
    [Display(Name = "メールアドレス", Prompt = "メールアドレスを入力してください")]
    public string Mail { get; set; }
}

作ったHtmlHelperの拡張メソッドはこんな感じで使います。長いので適当に改行しています。

<input type="text"
    name="@Html.NameFor(model => model.Mail)"
    value="@Html.ValueFor(model => model.Mail)"
    placeholder="@Html.PlaceholderFor(model => model.Mail)" />

するとこんなhtmlが出力されると。

<input type="text" name="Mail" value="" placeholder="メールアドレスを入力してください" />

いい感じ。

LINQ - 空のシーケンスでMin/Max/Averageを使ったときのメモ

LINQで空のシーケンスに対してMin/Max/Averageを使うとInvalidOperationExceptionが発生する場合があります。言われるとそっかと思うんですが、SQLの感覚もあってかうっかりやっちゃうかなあと思ったのでちょっとメモしておきます。

たとえば、intの空のシーケンスに対してMinメソッドを使ってみます。この場合はInvalidOperationExceptionが発生します。int?の空のシーケンスだとnullが返ってきます。

// intの空のシーケンスの場合はInvalidOperationException
try {
    Enumerable.Empty<int>().Min();
} catch (InvalidOperationException exception) {
    Console.WriteLine($"{exception.Message}");
    //シーケンスに要素が含まれていません
}

// int?の空のシーケンスの場合はnull
var min = Enumerable.Empty<int?>().Min();
if (min == null) {
    Console.WriteLine($"{nameof(min)}はnull");
    //minはnull
}

selector関数を使ったオーバーロードも確認してみます。selector関数の戻り値がnull許容型かどうかでnullが返ってくる場合と例外が発生する場合があります。

// selector関数の戻り値が非nullの場合はInvalidOperationException
try {
    Enumerable.Empty<Tuple<DateTime>>().Min(tuple => tuple.Item1);
} catch (InvalidOperationException exception) {
    Console.WriteLine($"{exception.Message}");
    //シーケンスに要素が含まれていません
}

// selector関数の戻り値がnull許容型の場合はnull
var min = Enumerable.Empty<Tuple<DateTime?>>().Min(tuple => tuple.Item1);
if (min == null) {
    Console.WriteLine($"{nameof(min)}はnull");
    //minはnull
}

null許容型ではないシーケンスにMin/Max/Averageするときはシーケンスが空かどうかチェックしましょうってことですね。はい、気をつけます。