--- tags: WPF, EntityFramework, C#, DataBinding, MSDN, lang: ja-JP --- # WPFとEF6のデータバインディング Data Binding with WPF > [time=Wed, Nov 20, 2019 3:49 PM] ###### tags: `MSDN` `WPF` `EntityFramework` `C#` `DataBinding` [TOC] ### 前書 MSDNに結構有用なデータは載っているが、訳文は機械翻訳で間違いだらけなので、ちょっと書き直した。全部完成してからGitHubにアップするかな… ### 関連リンク - [MSDN: WPFとのデータバインディング](https://docs.microsoft.com/ja-jp/ef/ef6/fundamentals/databinding/wpf) ## 本編紹介 このステップバイステップチュートリアルでは、POCOタイプを「マスター/詳細」形式でWPFコントロールにバインドする方法を示します。アプリケーションは、Entity Framework APIを使用して、データベースからのデータをオブジェクトに取り込み、変更を追跡し、データをデータベースに永続化します。 モデルは、一対多の関係に参加する2つのタイプを定義します:**Category**(プリンシパル\マスター)および**Product**(依存\詳細)。次に、Visual Studioツールを使用して、モデルで定義された型をWPFコントロールにバインドします。WPFデータバインディングフレームワークにより、関連オブジェクト間のナビゲーションが可能になります。マスタービューで行を選択すると、対応する子データで詳細ビューが更新されます。 このチュートリアルのスクリーンショットとコードリストはVisual Studio 2013から取得したものですが、Visual Studio 2012またはVisual Studio 2010でもこのチュートリアルを完了することができます。 ## 「Object」オプションを使用してWPFデータソースの作成 以前のバージョンのEntity Frameworkでは、EFデザイナーで作成されたモデルに基づいて新しいデータソースを作成するときに、**Database**オプションの使用を推奨していました。これは、デザイナーがObjectContextから派生したコンテキストと、EntityObjectから派生したエンティティクラスを生成するためです。Databaseオプションを使用すると、このAPIサーフェスと対話するための最適なコードを作成するのに役立ちます。 Visual Studio 2012およびVisual Studio 2013のEFデザイナーは、単純なPOCOエンティティクラスとともにDbContextから派生するコンテキストを生成します。Visual Studio 2010では、このチュートリアルで後ほど描述するように、DbContextを使用するコード生成テンプレートに交換することをお勧めします。 DbContextのAPIサーフェイスを使用する場合、このチュートリアルで示したように、新しいデータソースを生成する際には**Object**オプションを使うべきです。 必要に応じて、EF Designerで作成されたモデルの[ObjectContextベースのコード生成に戻す](https://docs.microsoft.com/ja-jp/ef/ef6/modeling/designer/codegen/legacy-objectcontext)ことができます。 ## 前提条件 このチュートリアルを完了する前提条件として、Visual Studio 2010以降のバージョンをインストールしたことです。 Visual Studio 2010を使う場合、NuGetもインストールする必要があります。より多い情報は[NuGetのインストール](https://docs.microsoft.com/nuget/install-nuget-client-tools)を参照してください。 ## アプリケーションの作成 - Visual Studioを開きます - **ファイル** -> **新規** -> **プロジェクト** - 左パネルで**Windowsデスクトップ**を選択し、右パネルで**WPFアプリケーション**を選択します - 名前に**WPFwithEFSample**を入力します - **OK**をクリック ## Entity FrameworkのNuGetパッケージのインストール - ソリューションエクスプローラーで、**WPFwithEFSample**プロジェクトを右クリックします。 - **NuGetパッケージの管理**を選択します。 - NuGetパッケージの管理ダイアログで、**参照**タブをクリックし、**EntityFramework**パッケージを選択します。 - **インストール**をクリックします。 > [!NOTE] > EntityFrameworkアセンブリに加えて、System.ComponentModel.DataAnnotationsへの参照も追加されます。 プロジェクトにSystem.Data.Entityへの参照がある場合、EntityFrameworkパッケージのインストール時に削除されます。 System.Data.Entityアセンブリは、Entity Framework 6アプリケーションでは使用されなくなりました。 ## モデルの定義 このチュートリアルでは、Code FirstまたはEFデザイナーを利用してモデルを実装することを選択できます。 次の2つのセクションのいずれかを完了します。 ### オプション1:Code Firstでモデル定義 このセクションではCode Firstの方法で、モデルを作成、及びデータベースへの連結を示します。Database Firstの方法で、EFデザイナーを利用してデータベースからモデルをリバースエンジニアリングする場合は、次のセクション(**オプション2:Database Firstでモデル定義**)にスキップします。 通常に、Code First開発を使用する場合、概念(ドメイン)モデルを定義する.NET Frameworkクラスを書くことから始めます。 - **WPFwithEFSample**プロジェクトに新しいクラスを追加する: - プロジェクト名に右クリック - **追加**を選択し、そして**新しい項目** - **クラス**を選択し、名前のところに**Product**を入力する - **Product**クラスの定義を次のコードで入れ替える: ```C# namespace WPFwithEFSample { public class Product { public int ProductId { get; set; } public string Name { get; set; } public int CategoryId { get; set; } public virtual Category Category { get; set; } } } ``` - **Category**クラスを追加して、次のコードで定義する: ```C# using System.Collections.ObjectModel; namespace WPFwithEFSample { public class Category { public Category() { this.Products = new ObservableCollection<Product>(); } public int CategoryId { get; set; } public string Name { get; set; } public virtual ObservableCollection<Product> Products { get; private set; } } } ``` **Category**クラスの**Products**プロパティと**Product**クラスの**Category**プロパティはナビゲーションプロパティです。 Entity Frameworkのナビゲーションプロパティは、2つのエンティティタイプ間の関係をナビゲートする方法を提供します。 エンティティの定義に加えて、DbContextから派生し、DbSet \<TEntity\>プロパティを公開するクラスを定義する必要があります。DbSet \<TEntity\>プロパティにより、モデルに含めるタイプをコンテキストに知らせます。 DbContext派生型のインスタンスは、実行中にエンティティオブジェクトを管理します。これには、データベースからのデータの入力、変更の追跡、データベースへのデータの永続化が含まれます。 - 新しい**ProductContext**クラスをプロジェクトに追加し、次の定義にします: ```C# using System.Data.Entity; namespace WPFwithEFSample { public class ProductContext : DbContext { public DbSet<Category> Categories { get; set; } public DbSet<Product> Products { get; set; } } } ``` - プロジェクトをコンパイルする。**ビルド**→**ソリューションのビルド(F7)** ### オプション2:Database Firstでモデル定義 このセクションでは、Database First(データベースファースト)の方法で、EFデザイナーを利用してデータベースからモデルをリバースエンジニアリングする方法を示します。 前のセクション(**オプション1:Code Firstでモデル定義**)を完了した場合は、このセクションをスキップして、**遅延ロード(Lazy Loading)** セクションへ進んでください。 #### 既存データベースの作成 通常の場合に、既存のデータベースを対象とする場合、データベースは既に作成されていますが、このチュートリアルでは、アクセスするデータベースを作成する必要があります。 Visual Studioと共にインストールされるデータベースサーバーは、インストールしたVisual Studioのバージョンによって異なります: - Visual Studio 2010を使用している場合は、SQL Expressデータベースを作成します。 - Visual Studio 2012を使用している場合は、LocalDBデータベースを作成します。 次に進んで、データベースの作成に着手しましょう。 - **表示(V)** ⇒ **サーバーエクスプローラー(V)** - **データ接続**に右クリック ⇒ **接続の追加** - サーバーエクスプローラーからデータベースに接続したことがない場合は、データソースとしてMicrosoft SQL Serverを選択する必要があります - インストールしたものに応じて、LocalDBまたはSQL Expressに接続し、データベース名として**Products**を入力します。 - **OK**を選択すると、新しいデータベースを作成するかどうか尋ねられます。**はい** を選択します。 - 新しいデータベースはいまサーバーエクスプローラーに表示されています。それに右クリックして、**新しいクエリ**を選択しまう。 - 次のSQLを新しいクエリにコピーし、クエリを右クリックして**実行**を選択します ```SQL CREATE TABLE [dbo].[Categories] ( [CategoryId] [int] NOT NULL IDENTITY, [Name] [nvarchar](max), CONSTRAINT [PK_dbo.Categories] PRIMARY KEY ([CategoryId]) ) CREATE TABLE [dbo].[Products] ( [ProductId] [int] NOT NULL IDENTITY, [Name] [nvarchar](max), [CategoryId] [int] NOT NULL, CONSTRAINT [PK_dbo.Products] PRIMARY KEY ([ProductId]) ) CREATE INDEX [IX_CategoryId] ON [dbo].[Products]([CategoryId]) ALTER TABLE [dbo].[Products] ADD CONSTRAINT [FK_dbo.Products_dbo.Categories_CategoryId] FOREIGN KEY ([CategoryId]) REFERENCES [dbo].[Categories] ([CategoryId]) ON DELETE CASCADE ``` #### モデルのリバースエンジニア Visual Studio機能の一つであるEFデザイナーを利用して、モデルを作成します。 - **プロジェクト** -\> **新しい項目の追加** - 左側のメニューから**Data**を選んで、右側で**ADO.NET Entity Data Model**を選択します - 名前に**ProductModel**を入力し、**追加**をクリックします - **Entity Data Modelウィザード**が立ち上がります。 - **データベースからEF Designer**を選択して、**次へ**をクリックします。 - 前のセッションで作成したデータベースの連結を選択して、接続文字列の名前として**ProductContext**を入力し、**次へ**をクリックします。 - 「**データベース オブジェクトと設定の選択**」の画面で、「**テーブル**」の前にあるチェックボックスにチェックを入れて全部のテーブルをインポートします。そして、下にあるオプションの「**生成されたオブジェクトの名前を複数化または単数化する**」もチェックを入れます。モデル名前空間のところに**ProductModel**になっているかどうかを確認して、最後に**完了**をクリックします。 リバースエンジニアリングプロセスが完了すると、新しいモデルがプロジェクトに追加され、EF Designerで表示できるように開かれます。データベースの接続の詳細を含むApp.configファイルもプロジェクトに追加されました。 #### Visual Studio 2010用の追加手順 Visual Studio 2010で作業する場合には、EFデザイナーを更新してEF6コード生成を使用する必要があります。 - EF Designerの中に空白の場所に右クリックして、**コード生成項目の追加**を選択します。 - **EF 6.x DbContext ジェネレーター**をメニューから選択し、名前に**ProductsModel**を入力し、**追加**をクリックします。 #### データバインディングのコード生成を更新する EFはT4テンプレートを利用して、モデルからコードを生成します。 これらテンプレートはVisual Studioに同梱されていて、またはVisual Studioギャラリーからダウンロードされて、汎用目的として使用されます。 つまり、これらのテンプレートから生成されたエンティティは、単純なICollection\<T\>プロパティを持っています。 ただし、WPFのデータバインディングを行う場合は、WPFがコレクションに加えられた変更を追跡できるように、コレクションプロパティに**ObservableCollection**を使用することが望ましいです。 そのために、ObservableCollectionを使用するようにテンプレートを変更します。 - **ソリューションエクスプローラー**を開き、**ProductModel.edmx**ファイルを探します。 - ProductModel.edmxの分岐内にある**ProductModel.tt**ファイルを探します。 - ProductModel.ttにダブルクリックして、エディタに開きます。 - 二箇所にある「**ICollection**」を「**ObservableCollection**」へ置き換えます。それぞれは約296行目と484行目あたりにあります。 - 最初に出た「**HashSet**」を見つけて「**ObservableCollection**」に置き換えます。 それは約50行目にあります。コードの後半にある2番目のHashSetを**置き換えないでください**。 - 一回だけある「**System.Collections.Generic**」を検索し、「**System.Collections.ObjectModel**」に置き換えます。 それは約424行目にあります。 - ProductModel.ttファイルを保存します。 これにより、エンティティのコードが再生成されます。 コードが自動的に再生成されない場合は、ProductModel.ttを右クリックして、「カスタムツールの実行」を選択します。 ProductModel.ttに属するCategory.csを開いて見ると、Productsコレクションは **ObservableCollection\<Product\>** 型になったことが分かります。 最後、プロジェクトをコンパイルする。 ## 遅延ロード Lazy Loading **Category**クラスの**Products**プロパティと**Product**クラスの**Category**プロパティはナビゲーションプロパティです。 Entity Frameworkのナビゲーションプロパティは、2つのエンティティタイプ間の関係をナビゲートする方法を提供します。 EFでは、次のようなオプションを提供します:ナビゲーションプロパティがアクセスされて初めて、関連するエンティティをデータベースから自動的にロードします。このタイプのロード(遅延ロードと呼ばれる)では、各ナビゲーションプロパティに最初にアクセスするときに、コンテンツがまだコンテキストにない場合、データベースに対して個別のクエリが実行されることに注意してください。 POCOエンティティタイプを使用する場合、EFは実行時に派生プロキシタイプのインスタンスを作成し、クラスの仮想プロパティをオーバーライドしてロードフックを追加することにより、遅延ロードを実現します。関連オブジェクトを遅延ロードで取得するには、ナビゲーションプロパティゲッターを**public**あるいは**virtual**(Visual Basicでは**Overridable**)として宣言する必要があり、クラスはsealed(Visual BasicではNotOverridable)でないことも必要条件です。Database Firstを使用すると、ナビゲーションプロパティが自動的にvirtualと定義されて、遅延ロードが可能になります。Code Firstセクションでナビゲーションプロパティをvirtualで定義したのも同じ理由です。 ## オブジェクトをコントローラとバイディング WPFアプリケーションのデータソースとしてモデルで定義されているクラスを追加します。 - ソリューションエクスプローラーの中にある**MainWindow.xaml**をダブルクリックして、メインフォームを開きます。 - メニューバーから、**プロジェクト**⇒**新しいデータソースの追加**を選択します。(VS2010では**データ**⇒**新しいデータソースの追加**) - データソースの種類の選択ダイアログで、**オブジェクト**を選択し、**次へ**をクリックします。 - データオブジェクトの選択ダイアログで、**WPFwithEFSample**を2段階展開し、**Category**を選択(チェック入れる)<br /> *我々は**Category**データソースの**Product**プロパティを利用するのでここでは**Product**データソースにチェック入れる必要はありません。* - **完了**をクリックします。 - データソースのウィンドウがMainWindow.xamlのウィンドウのととなりに開けられます。開けてない場合、**表示**⇒**その他のウィンドウ**⇒**データソース**を選択すると表示されます。 - データソースウインドウの右上にあるピンのアイコンをクリックし、ウインドウを常に表示するようにします。必要がある場合、更新ボタンを押してウインドウの内容を更新します。 - **Category**データソースを選択し、フォームにドラッグします。 データソースをフォームにドラッグしたときに、以下の動作が行われます: - **categoryViewSource**リソースと**categoryDataGrid**コントロールはXAMLに追加されます。 - 親であるGridエレメントのDataContextプロパティは「{StaticResource **categoryViewSource**}」と設定されて、**categoryViewSource**リソースは外側/親のGridエレメントのバインディングソースとして設定されます。内側のGridエレメントは親GridのDataContextの値を継承します。categoryDataGridのItemsSourceプロパティは「{Binding}」とセットされました。 ```C# <Window.Resources> <CollectionViewSource x:Key="categoryViewSource" d:DesignSource="{d:DesignInstance {x:Type local:Category}, CreateList=True}"/> </Window.Resources> <Grid DataContext="{StaticResource categoryViewSource}"> <DataGrid x:Name="categoryDataGrid" AutoGenerateColumns="False" EnableRowVirtualization="True" ItemsSource="{Binding}" Margin="13,13,43,191" RowDetailsVisibilityMode="VisibleWhenSelected"> <DataGrid.Columns> <DataGridTextColumn x:Name="categoryIdColumn" Binding="{Binding CategoryId}" Header="Category Id" Width="SizeToHeader"/> <DataGridTextColumn x:Name="nameColumn" Binding="{Binding Name}" Header="Name" Width="SizeToHeader"/> </DataGrid.Columns> </DataGrid> </Grid> ``` ## 詳細Gridを追加 Categoryを表示するGridはありました。さらに関連製品(Products)の詳細を表示するGridを追加しましょう。 - **Category**データソースの下から**Products**プロパティを選択し、フォームにドラッグします。 - **categoryProductsViewSource**リソースと**productDataGrid**がXAMLに追加される - リソースのバイディングパスはProductsに設定されます。 - WPFデータバインディングフレームワークは選択されたCategoryに関連するProductsのみproductDataGridに表示されるように働きます。 - ツールボックスから、**Button**をフォームにドラッグします。その名前を**buttonSave**に設定し、**Content**プロパティに**Save**と設定します。 フォームは次のようになります: ![the form look like](https://docs.microsoft.com/ja-jp/ef/ef6/media/designer.png "the form look like") ## データの相互作用を処理するコードを追加する メインウィンドウにイベントハンドラを追加する時が来ました。 - XAMLウィンドウに、Windowエレメントをクリックして、メインウインドウが選択されます。 - プロパティウィンドウの中に、右上にあるイベントボタンを選択して、そしてLoadedラベルの右にあるテキストボックスをダブルクリックします。 - SaveボタンにClickイベントを追加するために、デザイナーの中にあるSaveボタンにもダブルクリックします。 これでフォームに属するコードのほうに移動して、これらコードを編集することで、ProductContextを利用し、データアクセスを実現します。MainWindowのコードを次に示したように更新します。 このコードでProductContextの長期実行インスタンスを定義します。ProductContextオブジェクトはデータベースにクエリやデータ保存するときに利用されます。ProductContextインスタンスのDispose()はオーバーライドされたOnClosingメソッドから呼び出されます。詳細は次のコードを参照してください。 ```C# using System.Data.Entity; using System.Linq; using System.Windows; namespace WPFwithEFSample { public partial class MainWindow : Window { private ProductContext _context = new ProductContext(); public MainWindow() { InitializeComponent(); } private void Window_Loaded(object sender, RoutedEventArgs e) { System.Windows.Data.CollectionViewSource categoryViewSource = ((System.Windows.Data.CollectionViewSource)(this.FindResource("categoryViewSource"))); // LoadはIQueryableの拡張メソッドで、 // System.Data.Entityネームスペースに定義されています。 // そのメソッドでクエリの結果を列挙して、 // ToListと似ていますが、リストは作成されません。 // Linqを使ってエンティティを呼び出すときに、 // エンティティオブジェクトを作成し、それらエンティティをコンテキストに追加します。 _context.Categories.Load(); // データのロードが完了してから、DbSet<T>.Localプロパティを呼び出し、 // DbSet<T>をバイディングソースとして使います. categoryViewSource.Source = _context.Categories.Local; } private void buttonSave_Click(object sender, RoutedEventArgs e) { // 関連エンティティコレクションからオブジェクトを削除するときに、 // (今回の例はProducts), EFはその該当の子エンティティも削除するとはマークされない。 // 代わりに、親オブジェクトのレファレンスをnullに設定することで、親と子の関連性を削除します。 // ゆえに、手動でCategoryレファレンスがnullに設定されたproductを削除します。 // 次のコードで、ProductsのLocalコレクションに対して、オブジェクトのLINQを掛けます。 // ToListの呼出は必要で、そうしないと、コレクションはRemoveが呼び出される時点で変更されてしまいます。 // その他の場合では、ToListを先に呼ばなくても、オブジェクトのLINQを直接に使用することは可能です。 foreach (var product in _context.Products.Local.ToList()) { if (product.Category == null) { _context.Products.Remove(product); } } _context.SaveChanges(); // グリッドを更新して、データベースの変更を反映させます。 this.categoryDataGrid.Items.Refresh(); this.productsDataGrid.Items.Refresh(); } protected override void OnClosing(System.ComponentModel.CancelEventArgs e) { base.OnClosing(e); this._context.Dispose(); } } } ``` ## WPFアプリケーションのテスト - アプリケーションをコンパイルして実行します。Code First手法で進んだ場合、WPFwithEFSample.ProductContextデータベースが作成されます。 - 上のグリッドにカテゴリ名を入力して、下のグリッドに製品名を入力します。*主キーはデータベースのほうで生成するので、ID欄に何も入れないでください。* - **Save**ボタンを押して、データをデータベースに保存します。 DbContextの **SaveChanges()** が呼び出されたら、IDはデータベースの生成された値で書き直されます。 **SaveChanges()** の後に **Refresh()** を実行したから、**DataGrid** コントロールは新しい値を表示するようにしました。 ## 追加リソース WPFのデータバインディングについて、より深く探りたい場合、WPFドキュメントの[このトピック](https://docs.microsoft.com/ja-jp/dotnet/desktop-wpf/data/data-binding-overview#binding-to-collections)を参照してください。