次の方法で共有


WPF とのデータバインド

Important

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

このドキュメントでは、.NET Framework での WPF のデータ バインドについて説明します。 新しい .NET プロジェクトでは、Entity Framework 6 の代わりに EF Core を使用することをお勧めします。 EF Core でのデータ バインドに関するドキュメントは、 WPF の概要に関するページにあります。

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

このモデルでは、一対多リレーションシップに参加する 2 つの型を定義します。 カテゴリ (プリンシパル\マスター) と Product (dependent\detail) です。 次に、Visual Studio ツールを使用して、モデルで定義されている型を WPF コントロールにバインドします。 WPF データ バインディング フレームワークを使用すると、関連オブジェクト間のナビゲーションが可能になります。マスター ビューで行を選択すると、詳細ビューが対応する子データで更新されます。

このチュートリアルのスクリーン ショットとコード一覧は Visual Studio 2013 から取得しますが、このチュートリアルは Visual Studio 2012 または Visual Studio 2010 で完了できます。

WPF データ ソースの作成に 'Object' オプションを使用する

以前のバージョンの Entity Framework では、EF Designer で作成されたモデルに基づいて新しいデータ ソースを作成するときに 、データベース オプションを使用することをお勧めします。 これは、デザイナーが ObjectContext から派生したコンテキストと EntityObject から派生したエンティティ クラスを生成するためです。 [データベース] オプションを使用すると、この API サーフェスと対話するための最適なコードを記述するのに役立ちます。

Visual Studio 2012 および Visual Studio 2013 の EF デザイナーは、単純な POCO エンティティ クラスと共に DbContext から派生するコンテキストを生成します。 Visual Studio 2010 では、このチュートリアルで後述するように、DbContext を使用するコード生成テンプレートにスワップすることをお勧めします。

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

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

Pre-Requisites

このチュートリアルを完了するには、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 パッケージのインストール時に削除されます。 System.Data.Entity アセンブリは、Entity Framework 6 アプリケーションでは使用されなくなりました。

モデルを定義する

このチュートリアルでは、Code First または EF Designer を使用してモデルを実装することを選択できます。 次の 2 つのセクションのいずれかを完了します。

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

このセクションでは、Code 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 を使用して、EF デザイナーを使用してデータベースからモデルをリバース エンジニアリングする方法について説明します。 前のセクション (オプション 1: Code First を使用してモデルを定義する) を完了した場合は、このセクションをスキップして、 遅延読み込み セクションに進みます。

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

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

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

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

先に進み、データベースを生成しましょう。

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

  • [ データ接続] -> [接続の追加]を右クリックします。..

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

    データ ソースの変更

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

    LocalDB接続の追加

    Connection Express の追加

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

    データベースの作成

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

  • 次の 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 を使用して、モデルを作成します。

  • Project -> 新しい項目の追加...

  • 左側のメニューから [データ] を選択し、エンティティ データ モデル ADO.NET します

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

  • これにより、エンティティ データ モデル ウィザードが起動します。

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

    モデル コンテンツの選択

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

    接続の選択

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

    オブジェクトを選択する

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

Visual Studio 2010 の追加手順

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

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

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

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

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

  • ProductModel.edmx ファイルにネストされている ProductModel.tt ファイルを検索する

    WPF 製品モデル テンプレート

  • 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 は実行時に派生プロキシ型のインスタンスを作成し、クラス内の仮想プロパティをオーバーライドして読み込みフックを追加することで遅延読み込みを実現します。 関連オブジェクトの遅延読み込みを取得するには、ナビゲーション プロパティゲッターを パブリック および 仮想 として宣言する必要があります (Visual Basic ではオーバーライド可能 )、クラスを シール することはできません (Visual Basic では NotOverridable )。 Database First ナビゲーション プロパティを使用すると、遅延読み込みを有効にするために自動的に仮想プロパティが作成されます。 コードファーストセクションでは、同じ理由でナビゲーションプロパティを仮想にすることを選択しました。

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

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

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

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

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

  • [データ オブジェクトの選択] ダイアログで、WPFwithEFSample を 2 回展開し、[カテゴリ] を選択します
    製品データ ソースを選択する必要はありません。カテゴリ データ ソースの Product のプロパティを通じて取得されるためです。

    データ オブジェクトの選択

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

  • [データ ソース] ウィンドウが MainWindow.xaml ウィンドウの横で開かれる [データ ソース] ウィンドウが表示されない場合は、[表示] -> [その他の Windows > データ ソース] を選択します

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

    データ ソース

  • カテゴリ データ ソースを選択し、フォーム上にドラッグします。

このソースをドラッグすると、次のことが発生しました。

  • categoryViewSource リソースと categoryDataGrid コントロールが XAML に追加されました
  • 親 Grid 要素の DataContext プロパティが "{StaticResource categoryViewSource }" に設定されました。  categoryViewSource リソースは、outer\parent 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>

詳細グリッドの追加

カテゴリを表示するグリッドが用意されたので、関連する製品を表示する詳細グリッドを追加しましょう。

  • カテゴリ データ ソースの下から Products プロパティを選択し、フォーム上にドラッグします。
    • categoryProductsViewSource リソースと productDataGrid グリッドが XAML に追加されます
    • このリソースのバインド パスが Products に設定されている
    • WPF データ バインディング フレームワークにより、選択したカテゴリに関連する製品のみが productDataGrid に表示されます
  • ツールボックスから、 ボタン をフォームにドラッグします。 [名前] プロパティを buttonSave に設定し、[コンテンツ] プロパティを [保存] に設定します

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

デザイナー フォーム

データ操作を処理するコードを追加する

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

  • XAML ウィンドウで、<Window 要素をクリックすると、メイン ウィンドウが選択されます。

  • [ プロパティ ] ウィンドウで右上にある [イベント ] を選択し、[ 読み込まれた ] ラベルの右側にあるテキスト ボックスをダブルクリックします。

    メイン ウィンドウのプロパティ

  • デザイナーで [保存] ボタンをダブルクリックして、「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 列には何も入力しないでください

    新しいカテゴリと製品を含むメイン ウィンドウ

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

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

ID が入力されたメインウィンドウ

その他のリソース

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