WPF とのデータバインド

重要

このドキュメントは、.NET Framework 上の WPF に対してのみ有効です

このドキュメントでは、.NET Framework 上の WPF のデータバインドについて説明します。 新しい .NET Core プロジェクトでは、Entity Framework 6 ではなく EF Core を使うことをお勧めします。 EF Core でのデータバインドについては、「WPF の概要」を参照してください。

このステップ バイ ステップ チュートリアルでは、"master-detail" フォームで POCO 型を WPF コントロールにバインドする方法について説明します。 アプリケーションでは、Entity Framework API を使用して、データベースのデータをオブジェクトに設定し、変更を追跡し、データベースにデータを保持します。

このモデルでは、一対多のリレーションシップに関係する 2 つの型 Category (principal\master) と Product (dependent\detail) が定義されています。 次に、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 デザイナーにより、DbContext から派生したコンテキストと、シンプルな POCO エンティティ クラスが生成されます。 Visual Studio 2010 の場合は、このチュートリアルで後述するように、DbContext を使うコード生成テンプレートに変更することをお勧めします。

DbContext API サーフェスを使う場合、このチュートリアルで示すように、新しいデータ ソースを作成するときに Object オプションを使う必要があります。

必要に応じて、EF デザイナーで作成したモデルの場合に ObjectContext ベースのコード生成に戻すことができます。

前提条件

このチュートリアルを実行するには、Visual Studio 2013、Visual Studio 2012 または Visual Studio 2010 がインストールされている必要があります。

Visual Studio 2010 を使っている場合は、NuGet もインストールする必要があります。 詳細については、NuGet のインストールを参照してください。  

アプリケーションを作成する

  • Visual Studio を開きます
  • [ファイル] -> [新規] -> [プロジェクト]
  • 左側のペインで [Windows] を、右側のペインで [WPFApplication] を選びます
  • 名前に「WPFwithEFSample」と入力します
  • [OK] を選択します。

Entity Framework NuGet パッケージをインストールする

  • ソリューション エクスプローラーで WinFormswithEFSample プロジェクトを右クリックします
  • [NuGet パッケージの管理] を選択します
  • [NuGet パッケージの管理] ダイアログで [オンライン] タブを選択し、[EntityFramework] パッケージを選択します。
  • [インストール]をクリックします。

    Note

    EntityFramework アセンブリに加えて、System.ComponentModel.DataAnnotations への参照も追加されます。 プロジェクトに System.Data.Entity への参照が含まれている場合は、EntityFramework パッケージのインストール時に削除されます。 Entity Framework 6 アプリケーションでは、System.Data.Entity アセンブリは使用されなくなりました。

モデルを定義する

このチュートリアルでは、Code First または EF デザイナーを使ってモデルを実装することを選ぶことができます。 次の 2 つのセクションのいずれかを実行します。

オプション 1: Code First を使ってモデルを定義する

このセクションでは、Code First を使ってモデルとそれに関連付けられたデータベースを作成する方法について説明します。 Database First を使って、EF デザイナーでデータベースからモデルをリバース エンジニアリングする場合は、次のセクション (オプション 2: Database First を使ってモデルを定義する) に進んでください

通常、Code First の開発を使用するときはまず、概念 (ドメイン) モデルを定義する .NET Framework のクラスを記述します。

  • 新しいクラスを WPFwithEFSample に追加します。
    • プロジェクト名を右クリックします。
    • [追加][新しい項目] の順に選びます
    • [クラス] を選び、クラス名に「Product」と入力します
  • Product クラスの定義を次のコードに置き換えます。
    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 クラスを追加します。
    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 クラスを、プロジェクトに追加します。
    using System.Data.Entity;

    namespace WPFwithEFSample
    {
        public class ProductContext : DbContext
        {
            public DbSet<Category> Categories { get; set; }
            public DbSet<Product> Products { get; set; }
        }
    }

プロジェクトをコンパイルします。

オプション 2: Database First を使ってモデルを定義する

このセクションでは、Database First を使って、EF デザイナーでデータベースからモデルをリバース エンジニアリングする方法について説明します。 前のセクション (オプション 1: Code First を使ってモデルを定義する) を完了している場合は、このセクションをスキップし、遅延読み込みセクションに直接移動します。

既存のデータベースを作成する

通常、既存のデータベースを対象とする場合、データベースは既に作成されていますが、このチュートリアルでは、アクセスするデータベースを作成する必要があります。

Visual Studio と共にインストールされるデータベース サーバーは、インストールされている Visual Studio のバージョンによって異なります。

  • Visual Studio 2010 を使用している場合は、SQL Express データベースを作成します。
  • Visual Studio 2012 を使っている場合は、LocalDB データベースを作成します。

それではデータベースを生成しましょう。

  • [表示] -> [サーバー エクスプローラー]

  • [データ接続] を右クリックし、[接続の追加] を選択します。

  • これまでサーバー エクスプローラーからデータベースに接続したことがない場合は、データ ソースとして Microsoft SQL Server を選択する必要があります。

    Change Data Source

  • インストールされているものに応じて、LocalDB または SQL Express のいずれかに接続し、データベース名に「Products」と入力します。

    Add Connection LocalDB

    Add Connection Express

  • [OK] を選択すると、新しいデータベースを作成するかどうかを確認するメッセージが表示されます。[はい] を選択します。

    Create Database

  • サーバー エクスプローラーに新しいデータベースが表示されたら、それを右クリックし、[新しいクエリ] を選択します。

  • 次の 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 の一部として含まれている Entity Framework Designer を使用してモデルを作成します。

  • [プロジェクト] -> [新しい項目の追加...] を選択します。

  • 左側のメニューから [データ] を選び、[ADO.NET Entity Data Model] を選びます

  • 名前に「ProductModel」と入力し、[OK] をクリックします

  • これにより、Entity Data Model ウィザードが起動します。

  • [データベースから生成] を選択し、[次へ] をクリックします。

    Choose Model Contents

  • 最初のセクションで作成したデータベースへの接続を選び、接続文字列の名前に「ProductContext」と入力して、[次へ] をクリックします

    Choose Your Connection

  • [テーブル] の横のチェック ボックスをオンにして、すべてのテーブルをインポートし、[完了] をクリックします

    Choose Your Objects

リバース エンジニアリング プロセスが完了すると、新しいモデルがプロジェクトに追加され、Entity Framework Designer で表示できるように開かれます。 データベースの接続の詳細と共に、App.config ファイルもプロジェクトに追加されています。

Visual Studio 2010 での追加の手順

Visual Studio 2010 で作業している場合は、EF6 コード生成を使うように EF デザイナーを更新する必要があります。

  • EF Designer でモデルの空いている場所を右クリックし、[コード生成項目の追加] を選択します。
  • 左側のメニューから [オンライン テンプレート] を選び、DbContext を検索します。
  • [C# 用 EF 6.x DbContext ジェネレーター] を選び、名前に「ProductsModel」と入力して、[追加] をクリックします。

データ バインディングのコード生成の更新

EF により、T4 テンプレートを使ってモデルからコードが生成されます。 Visual Studio に含まれるテンプレートまたは Visual Studio ギャラリーからダウンロードしたテンプレートは、汎用的に使用することを目的としています。 つまり、これらのテンプレートから生成されたエンティティには、単純な ICollection<T> プロパティがあります。 ただし、WPF を使ってデータバインドを実行する場合は、コレクションのプロパティに ObservableCollection を使い、WPF によってコレクションの変更を追跡できるようにすることをお勧めします。 この目的のためには、ObservableCollection を使うようにテンプレートを変更します。

  • ソリューション エクスプローラーを開き、ProductModel.edmx ファイルを見つけます

  • ProductModel.edmx ファイルに入れ子にする ProductModel.tt ファイルを見つけます

    WPF Product Model Template

  • ProductModel.tt ファイルをダブルクリックして Visual Studio のエディターで開きます

  • "ICollection" の 2 つの出現箇所を検索し、"ObservableCollection" に置き換えます。 これらは、296 行目付近と 484 行目付近にあります。

  • "HashSet" の最初の出現箇所を検索し、"ObservableCollection" に置き換えます。 出現箇所は、50 行目付近にあります。 コードの後半にある HashSet の 2 番目の出現箇所を置き換えないでください

  • "System.Collections.Generic" の唯一の出現箇所を検索し、"System.Collections.ObjectModel" に置き換えます。 これは、424 行目付近にあります。

  • ProductModel.tt ファイルを保存します。 これにより、エンティティのコードが再生成されます。 コードが自動的に再生成されない場合は、ProductModel.tt を右クリックし、[カスタム ツールの実行] を選択します。

Category.cs ファイル (ProductModel.tt の下に入れ子になっています) を開くと、Products コレクションの型が ObservableCollection<Product> であることがわかります。

プロジェクトをコンパイルします。

遅延読み込み

Category クラスの Products プロパティと、Product クラスの Category プロパティは、ナビゲーション プロパティです。 Entity Framework では、ナビゲーション プロパティによって、2 つのエンティティ型間のリレーションシップをナビゲートする手段が提供されます。

EF を使うと、ナビゲーション プロパティに初めてアクセスしたときに、関連エンティティをデータベースから自動的に読み込むことができます。 この種類の読み込み (遅延読み込みと呼ばれます) では、各ナビゲーション プロパティに初めてアクセスしたときに、コンテンツがコンテキスト内に存在しない場合、データベースに対して別のクエリが実行されることに注意してください。

POCO エンティティ型を使用すると、EF では、実行中に派生プロキシ型のインスタンスを作成してから、クラス内の virtual プロパティをオーバーライドして読み込みフックを追加することにより、遅延読み込みが実現されます。 関連オブジェクトの遅延読み込みを行うには、ナビゲーション プロパティ ゲッターを public および virtual (Visual Basic では Overridable) として宣言する必要があり、クラスを sealed (Visual Basic では NotOverridable) にしないようにする必要があります。 Database First を使うと、遅延読み込みを有効にするために、ナビゲーション プロパティは自動的に virtual にされます。 Code First セクションでは、同じ理由でナビゲーション プロパティを virtual にすることを選びました。

オブジェクトをコントロールにバインドする

モデルで定義されているクラスを、この WPF アプリケーションのデータ ソースとして追加します。

  • ソリューション エクスプローラーで MainWindow.xaml をダブルクリックして、メイン フォームを開きます

  • メイン メニューから、[プロジェクト] -> [新しいデータ ソースの追加] を選びます (Visual Studio 2010 では、[データ] -> [新しいデータ ソースの追加] を選ぶ必要があります)

  • [データ ソースの種類を選択] ウィンドウで、[オブジェクト] を選んで [次へ] をクリックします

  • [データ オブジェクトの選択] ダイアログで WPFwithEFSample を 2 回展開し、Category を選びます
    Product データ ソースは Category データ ソースの Product のプロパティから取得するので、選ぶ必要はありません

    Select Data Objects

  • [完了] をクリックします。

  • MainWindow.xaml ウィンドウの横に [データ ソース] ウィンドウが表示されます [データ ソース] ウィンドウが表示されない場合は、[表示] -> [他のウィンドウ] -> [データ ソース] を選びます

  • [データ ソース] ウィンドウが自動的に非表示にならないように、ピン アイコンを押します。 ウィンドウが既に表示されている場合は、更新ボタンを押す必要がある場合があります。

    Data Sources

  • Category データ ソースを選び、フォームにドラッグします。

このソースをドラッグすると、次のようになります。

  • XAML に categoryViewSource リソースと categoryDataGrid コントロールが追加されました
  • 親 Grid 要素の DataContext プロパティが "{StaticResource categoryViewSource }" に設定されました。 この categoryViewSource リソースは、外側または親の Grid 要素のバインド ソースとして機能します。 内側の Grid 要素は、親の Grid から DataContext 値を継承します (categoryDataGrid の ItemsSource プロパティは "{Binding}" に設定されます)
    <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>

詳細グリッドの追加

Categories を表示するグリッドができたので、関連する Products を表示する詳細グリッドを追加しましょう。

  • Category データ ソースの下にある Products プロパティを選び、フォームにドラッグします。
    • categoryProductsViewSource リソースと productDataGrid グリッドが XAML に追加されます
    • このリソースのバインド パスは Products に設定されます
    • WPF データバインド フレームワークにより、選ばれた Category に関連する Products のみが productDataGrid に表示されます
  • ツールボックスから [ボタン] をフォームにドラッグします。 Name プロパティを buttonSave に、Content プロパティを Save に設定します。

フォームは次のようになります。

Designer Form

データのやり取りを処理するコードを追加する

次に、いくつかのイベント ハンドラーをメイン ウィンドウに追加します。

  • XAML ウィンドウで、<Window 要素をクリックして、メイン ウィンドウを選びます

  • [プロパティ] ウィンドウで右上の [イベント] をクリックし、[Loaded] ラベルの右にあるテキスト ボックスをダブルクリックします

    Main Window Properties

  • また、デザイナーで [保存] ボタンをダブルクリックして、[保存] ボタンの Click イベントを追加します。

これにより、フォームのコード ビハインドが表示されます。ProductContext を使ってデータ アクセスを実行するようにコードを編集します。 次に示すように、MainWindow のコードを更新します。

このコードでは、ProductContext の実行時間の長いインスタンスが宣言されています。 データをクエリしてデータベースに保存するには、ProductContext オブジェクトを使います。 その後、ProductContext インスタンスの Dispose() が、オーバーライドされた OnClosing メソッドから呼び出されます。 コードのコメントは、コードの動作に関する詳細を提供します。

    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 is an extension method on IQueryable,
                // defined in the System.Data.Entity namespace.
                // This method enumerates the results of the query,
                // similar to ToList but without creating a list.
                // When used with Linq to Entities this method
                // creates entity objects and adds them to the context.
                _context.Categories.Load();

                // After the data is loaded call the DbSet<T>.Local property
                // to use the DbSet<T> as a binding source.
                categoryViewSource.Source = _context.Categories.Local;
            }

            private void buttonSave_Click(object sender, RoutedEventArgs e)
            {
                // When you delete an object from the related entities collection
                // (in this case Products), the Entity Framework doesn’t mark
                // these child entities as deleted.
                // Instead, it removes the relationship between the parent and the child
                // by setting the parent reference to null.
                // So we manually have to delete the products
                // that have a Category reference set to null.

                // The following code uses LINQ to Objects
                // against the Local collection of Products.
                // The ToList call is required because otherwise the collection will be modified
                // by the Remove call while it is being enumerated.
                // In most other situations you can use LINQ to Objects directly
                // against the Local property without using ToList first.
                foreach (var product in _context.Products.Local.ToList())
                {
                    if (product.Category == null)
                    {
                        _context.Products.Remove(product);
                    }
                }

                _context.SaveChanges();
                // Refresh the grids so the database generated values show up.
                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 列には何も入力しないでください"

    Main Window with new categories and products

  • [保存] ボタンを押してデータをデータベースに保存します

DbContext の SaveChanges() を呼び出した後、ID にはデータベースで生成された値が入力されます。 SaveChanges() の後に Refresh() を呼び出したので、DataGrid コントロールも新しい値で更新されます。

Main Window with IDs populated

その他のリソース

WPF を使ったコレクションへのデータ バインドの詳細については、WPF ドキュメントのこのトピックを参照してください。