WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

Code First で外部キープロパティの定義

by WebSurfer 2016年9月11日 14:08

先に、Entity Framework Code First の機能を利用して SQL Server データベースに親子関係のあるテーブルを生成し、ASP.NET MVC4 で登録・編集・削除を行うという以下の 3 つの記事を書きました。

  1. MVC4 EF Code First(Entity Framework Code First の機能を利用して SQL Server 2008 Express に MVC4 アプリケーション用のテーブルを生成)
  2. 親子関係のあるデータ登録(Create アクションメソッド / ビューを追加して、上記 1 で作成したテーブルにデータを登録)
  3. 親子関係のあるデータの編集・削除(Edit および Delete アクションメソッド / ビューを追加して、上記 2 で登録したデータを編集・削除)

上の記事では、テーブル生成のベースとなる Child クラスに Parent クラスを参照するナビゲーションプロパティと外部キープロパティは定義していませんでした。(具体的には上記 1 の記事の Model のコードを見てください)

それに、Microsoft の文書「Code First の規約」に書いてある "依存オブジェクトを表す型には、ナビゲーションプロパティに加えて外部キープロパティを含めることをお勧めします" に従って、ナビゲーションプロパティと外部キープロパティを定義するとどのような影響があるかを書きます。

追加後のコードは以下の通りです。上で言う「依存オブジェクトを表す型」は Child クラスですので、それにナビゲーションプロパティと外部キープロパティを追加します。外部キープロパティは int 型とし、Children テーブルに生成される外部キーフィールドを NULL 不可に(連鎖削除を設定)します。

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

    [Required(ErrorMessage = "{0} は必須")]
    [StringLength(5, ErrorMessage = "{0} は {1} 文字以内")]
    [Display(Name = "Child Name")]
    public string Name { get; set; }

    // 外部キープロパティを追加
    // int 型なので外部キーフィールドは NULL 不可になる
    public int ParentId { get; set; }

    // ナビゲーションプロパティを追加
    public virtual Parent Parent { get; set; } 
}

その後、上記 1 の記事「MVC4 EF Code First」に書いた手順に従って、Controller と View を作って Controller のアクションメソッドを呼び出すと、以下とおり Parents テーブルと Children テーブルが自動的に生成されます。

生成されたテーブル

Child クラスへの int 型外部キープロパティ ParentId の追加により、Children テーブルの外部キーフィールドがプロパティ名と同名の ParentId となり NULL 不可に設定されたことが上記 1 の記事で作成したデータベースと異なる点です。

ASP.NET MVC4 アプリからこれらのテーブルへのデータの登録は、上記 2 の記事「親子関係のあるデータ登録」の手順どおりで可能です。

編集・削除は上記 3 の記事「親子関係のあるデータの編集・削除」とは若干異なってきます。注意すべき点を以下に箇条書きにします。

編集 (Edit)

  • Child クラスに ParentId プロパティが定義されているので ParentId の値もポストする必要がある。Id と同様に View に隠しフィールドを追加して対応する。そうしないと、モデルバインディングの際 ParentId プロパティにはゼロがバインドされ、db.Entry(postedParent).State を Modified に設定する時に参照整合性制約違反でエラーになる。
  • UpdateModel(db.Parents.Find(id)) の後 db.SaveChanges するのはエラーになる。Children テーブルの関連するレコードの外部キーフィールド ParentId を NULL に書き換え、ポストされたデータでレコードを新たに作り INSERT するという動きになるが、外部キーフィールド ParentId は NULL 不可なのでエラーになる。(先の記事の例のように外部キーフィールドが NULL 可であればエラーは出ない。外部キーフィールドが NULL の余計なレコードは残るが DB の整合性は保たれる)
  • db.Entry(親).State を Modified にしただけでは、先の記事の例と同様、親しか更新されない。コードを書いて子の State も Modified に設定する必要がある。(実は、連鎖削除だけでなく連鎖更新もされるのではと期待したがダメでした)
  • 親も子も無条件で State を Modified に設定すると、変更する必要のないレコードまで UPDATE されてしまう。先の記事の例ではそうコーディングしたが、考え直した方がよさそう。具体的な案としては、db.Parents.Find(id) で Parent オブジェクトを取得し、そのプロパティをポストされた値(アクションメソッドの引数 postedParent から取得)で一つ一つ書き換えるのがよさそう。そうすると、前の値と変更になった場合のみ自動的に Modified マークが付けられ、db.SaveChanges で更新される。(前の値と変わらなければ Unchanged マークのままなので更新されない) この方法を取れば、View の隠しフィールドで Id や ParentId をポストする必要もなくなる。

削除 (Delete)

  • Microsoft の文書「Code First の規約」によると、"依存エンティティの外部キーで null 値が許容されない場合、Code First はリレーションシップに連鎖削除を設定します" とのこと。外部キーフィールド ParentId が NULL 不可なので、フレームワークはリレーションシップに連鎖削除を設定しているはず。実際に削除を試してみると、親だけ Remove すれば連鎖的に子も削除されることを確認できた。(上記 3 の記事のように、子を Remove するのは不要)
  • 同じ Microsoft の文書には "依存エンティティの外部キーで null 値が許容される場合は、Code First はリレーションシップに連鎖削除を設定しないため、プリンシパルが削除されると外部キーが null に設定されます" と書いてあるが、上記 3 の記事(外部キーは NULL 可)で試した限りではそのようにはならない。子を持つ親を削除しようとすると FK 制約に引っかかって SqlException がスローされる。

Tags: ,

MVC

IE11 で[戻る]ボタンが使えない

by WebSurfer 2016年8月24日 10:37

ユーザーが Power Users グループに属していると IE11 の[戻る]ボタンが使えない(グレーアウトされる)ことがあるという話を書きます。

Power Users グループ

元は MSDN Forum の「IE11で戻るボタンが使えない」という表題のスレッドでの話です。

そのスレッドを読めば話はすぐわかるのですが、備忘録として自分のブログの記事にも書いておくことにしました。

簡単に話の内容を書くと・・・

IE11 にアップグレードしたら一部のドメインユーザーで IE11 の[戻る]ボタンが使えなくなる(グレーアウトされる)という問題が発生。

ググって調べてみると、ユーザーを Power Users グループから外したら問題が解決したという記事を発見。(ただし原因は不明)

MSDN Forum の質問者さんの方でも、ユーザーを Power Users グループから外すことで問題が解消した。

・・・ということです。

ネットの情報だけで自分で検証したわけではないし、そもそも何故 Power Users グループが影響するのか不明というのがアレですが、IE11 の[戻る]ボタンが使えないという問題に遭遇したら Power Users グループに属していないか調べてみるのがよさそうです。

Tags: ,

その他

VS2010 によるエラーチェック

by WebSurfer 2016年7月2日 15:48

Visual Studio によるエラーチェックは Web サイトプロジェクトと Web アプリケーションプロジェクトでは異なるという話を書きます。

VS2010 エディタのエラーチェック

自分の環境(Vista SP2 32-bit, Visual Studio 2010 Professional)で検証した結果をまとめると以下のようになりました:

  1. Web サイトプロジェクト
    設定したターゲットフレームワークに応じた言語バージョンに準拠(FW3.5 なら C# 3 または VB9.0、FW4 なら C# 4 または VB10.0)
  2. Web アプリケーションプロジェクト
    Visual Studio に採用された言語バージョンに準拠(VS2010 なら C# 4 または VB10.0)

これは、基本的にソースコードのままサーバーにデプロイしてサーバー側で動的にコンパイルを行う Web サイトプロジェクトと、Visual Studio で単一アセンブリ (.dll) にコンパイルして出来上がった .dll をサーバーにデプロイする Web アプリケーションプロジェクトとの違いからきていると思われます。

言語に C# を使って検証した場合の具体的な結果を以下に書いておきます。

(1) Web サイトプロジェクト

MSDN ライブラリの記事「What's New for Visual C#」によると、VS2010 の C# の言語バージョンは C# 4 で、Dynamic、名前付き引数と省略可能なパラメーター、ジェネリックの共変性/反変性が新機能としてサポートされたとのことです。

なので、名前付き引数を使ったメソッドをターゲットフレームワーク 3.5 の Web サイトプロジェクトのコードビハインドに実装して VS2010 によるエラーチェックがどうなるかを見てみました。

その結果が一番上の画像です。エディタレベルで VS2010 のエラーチェックに引っかかり、「3.0 C# 言語使用の一部ではないため、機能 '名前付き引数' を使用することはできません。」というエラーメッセージが出ています。

Visual Studio 上でビルドをかけると、[エラー一覧]にエラーが表示され(エディタレベルのエラーメッセージとは異なりますが)、ビルドに失敗します。もちろん実行はできません。

ブラウザのアドレスバーに直接 URL を入力して、ブラウザから問題のページを要求すると、サーバー側での動的コンパイルでコンパイルエラーとなり、以下の画像のサーバーエラーが応答として返ってきます。

動的コンパイルでのエラー

これは web.config で、サーバー側で動的コンパイルに使うコンパイラが以下のように設定されており、これに従って C:\Windows\Microsoft.NET\Framework\v3.5 にあるコンパイラ csc.exe(自分の環境では製品バージョン 3.5.307129.1・・・名前付き引数はサポートしていない)が使用されるためです。

<system.codedom>
  <compilers>
    <compiler 
      language="c#;cs;csharp" 
      extension=".cs" 
      warningLevel="4"
      type="Microsoft.CSharp.CSharpCodeProvider, System, 
            Version=2.0.0.0, 
            Culture=neutral, 
            PublicKeyToken=b77a5c561934e089">
      <providerOption name="CompilerVersion" value="v3.5"/>
      <providerOption name="WarnAsError" value="false"/>
    </compiler>
 </compilers>
</system.codedom>

同じコードをターゲットフレームワークが 4 の Web サイトプロジェクトに実装すれば、当然ながらエラーメッセージ等は一切出ず、ビルドも実行も問題ありません。

(2) Web アプリケーションプロジェクト

上記 (1) と同じコードをターゲットフレームワーク 3.5 の Web サイトプロジェクトのコードビハインドに実装してみます。動的コンパイルに使うコンパイラの設定(web.config の system.codedom 要素の設定)は上の (1) Web サイトプロジェクトの場合と同じです。

結果、上記 (1) Web サイトプロジェクトの時のようなエディタ上のエラーは出ません。ビルドも通りますし、実行上も問題ありません。

ということは、ターゲットフレームワークの設定には関係なく、VS2010 に採用された言語バージョン C# 4 をベースに VS2010 のエディタ上でエラーチェックが行われ、ビルドも言語バージョン C# 4 のコンパイラで行われているということになります。

では、動的コンパイルが行われる部分はどうでしょうか?(Web アプリケーションプロジェクトでも .aspx ファイルや App_Code のソースなどはデフォルトではサーバー側で動的コンパイルが行われます)

まず、.aspx ファイルに以下のようなコードブロック(<% から %> までのコード)を実装してみます。

<body>
  <form id="form1" runat="server">
  <div>
    <asp:Label ID="Label1" runat="server"></asp:Label>
    <br />
    <% =CalculateBMI(height: 64, weight: 123) %>
  </form>
</body>

そうすると、エディタレベルで一番上と同様なエラーが表示されます。ただし、コードブロックの部分は Visual Studio ではコンパイルされないためか、ビルドは通ります。実行したりブラウザから要求をかけた場合は動的コンパイルが行われ、コードブロックの部分でコンパイルエラーとなり、上の 2 つ目の画像と同様なサーバーエラーとなります。

次に、App_Code に以下のようなクラスファイルを実装してみます。クラスファイル Class1.cs のビルドアクションは「コンテンツ」「コンパイル」いずれの場合もエディタレベルではエラーは表示されません。ビルドも通ります。

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

namespace Framework35CSharp.App_Code
{
  public class Class1
  {
    public int CalculateBMI(int weight, int height)
    {
      return (weight * 703) / (height * height);
    }

    public string BMI64x123()
    {
      return CalculateBMI(height: 64, weight: 123).ToString();
    }
  }
}

2 重コンパイルの問題を避けるためにクラスファイル Class1.cs のビルドアクションは「コンテンツ」として実行してみます。するとサーバー側で動的コンパイルが行われ、名前付き引数を使っているところでコンパイルエラーとなり、上の 2 つ目の画像と同様なサーバーエラーとなります。ブラウザから要求をかけた場合も同様にサーバーエラーが返ってきます。(ビルドアクションを「コンパイル」にしても同様に動的コンパイルでエラーになります)

元は MSDN Forum の「ASPNET WEBサイト(VB)で、yieldが使用できない」という表題のスレッドでの話で、VB.NET の場合の検証結果はそちらに書きましたが若干様子が異なります。

また、そのフォーラムに VS2012 / 2013 では Web アプリケーションプロジェクトの App_Code フォルダでもエディタによるエラーチェックは働くという話があります。Visual Studio のバージョンによって異なるのかもしれません。(自分は検証できる環境を持ってないので未確認ですが)

Tags: ,

ASP.NET

About this blog

2010年5月にこのブログを立ち上げました。その後 ブログ2 を追加し、ここは ASP.NET 関係のトピックス、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  2017年3月  >>
2627281234
567891011
12131415161718
19202122232425
2627282930311
2345678

View posts in large calendar