次の方法で共有


WinForms を使用したデータ バインド

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

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

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

前提条件

このチュートリアルを完了するには、Visual Studio 2013、Visual Studio 2012、または Visual Studio 2010 をインストールする必要があります。

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

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

  • Visual Studio を開く
  • ファイル -> 新規 -> プロジェクト……
  • 左側のウィンドウで Windows を選択し、右側のウィンドウで Windows FormsApplication を選択します
  • 名前として 「WinFormswithEFSample 」と入力します
  • [OK] を選択します。

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

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

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

コレクションの IListSource の実装

Windows フォームの使用時に並べ替えによる双方向データ バインディングを有効にするには、コレクション プロパティで IListSource インターフェイスを実装する必要があります。 これを行うには、ObservableCollection を拡張して IListSource 機能を追加します。

  • ObservableListSource クラスをプロジェクトに追加します。
    • プロジェクト名を右クリックします。
    • 「新しい項目を追加」を選択する
    • [クラス] を選択し、クラス名として「ObservableListSource」と入力します
  • 既定で生成されたコードを次のコードに置き換えます。

このクラスを使用すると、双方向のデータ バインディングと並べ替えが可能になります。 クラスは ObservableCollection<T> から派生し、IListSource の明示的な実装を追加します。 IListSource の GetList() メソッドは、ObservableCollection と同期したままの IBindingList 実装を返すために実装されます。 ToBindingList によって生成される IBindingList 実装では、並べ替えがサポートされています。 ToBindingList 拡張メソッドは EntityFramework アセンブリで定義されています。

    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Diagnostics.CodeAnalysis;
    using System.Data.Entity;

    namespace WinFormswithEFSample
    {
        public class ObservableListSource<T> : ObservableCollection<T>, IListSource
            where T : class
        {
            private IBindingList _bindingList;

            bool IListSource.ContainsListCollection { get { return false; } }

            IList IListSource.GetList()
            {
                return _bindingList ?? (_bindingList = this.ToBindingList());
            }
        }
    }

モデルを定義する

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

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

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

Code First 開発を使用する場合は、通常、概念 (ドメイン) モデルを定義する .NET Framework クラスを記述することから始めます。

  • 新しい Product クラスをプロジェクトに追加する
  • 既定で生成されたコードを次のコードに置き換えます。
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;

    namespace WinFormswithEFSample
    {
        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;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;

    namespace WinFormswithEFSample
    {
        public class Category
        {
            private readonly ObservableListSource<Product> _products =
                    new ObservableListSource<Product>();

            public int CategoryId { get; set; }
            public string Name { get; set; }
            public virtual ObservableListSource<Product> Products { get { return _products; } }
        }
    }

エンティティの定義に加えて、 DbContext から派生し、 DbSet<TEntity> プロパティを公開するクラスを定義する必要があります。 DbSet プロパティを使用すると、モデルに含める型がコンテキストに認識されます。 DbContext 型と DbSet 型は EntityFramework アセンブリで定義されます。

DbContext 派生型のインスタンスは、実行時にエンティティ オブジェクトを管理します。これには、データベースからのデータを含むオブジェクトの設定、変更の追跡、データベースへのデータの永続化が含まれます。

  • 新しい ProductContext クラスをプロジェクトに追加します。
  • 既定で生成されたコードを次のコードに置き換えます。
    using System;
    using System.Collections.Generic;
    using System.Data.Entity;
    using System.Linq;
    using System.Text;

    namespace WinFormswithEFSample
    {
        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 の接続を追加

    接続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> プロパティがあります。 ただし、データ バインディングを実行する場合は、IListSource を実装するコレクション プロパティが必要です。 このため、上で ObservableListSource クラスを作成し、このクラスを利用するようにテンプレートを変更します。

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

  • ProductModel.edmx ファイルの下に入れ子になる ProductModel.tt ファイルを見つける

    製品モデル テンプレート

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

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

  • "HashSet" の最初の出現箇所を検索して "ObservableListSource" に置き換えます。 この発生は約 50 行目にあります。 コードの後半で見つかった HashSet の 2 番目の出現箇所を置き換えないでください

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

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

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

遅延読み込み

Category クラスのProducts プロパティと Product クラスの Category プロパティはナビゲーション プロパティです。 Entity Framework のナビゲーション プロパティは、2 つのエンティティ型間のリレーションシップをナビゲートする方法を提供します。

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

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

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

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

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

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

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

    データ ソース

  • [完了] をクリックします。[データ ソース] ウィンドウが表示されない場合は、[表示] -> [その他の Windows > データ ソース] を選択します

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

    データ ソース 2

  • ソリューション エクスプローラーで、 Form1.cs ファイルをダブルクリックして、デザイナーでメイン フォームを開きます。

  • カテゴリ データ ソースを選択し、フォーム上にドラッグします。 既定では、新しい DataGridView (categoryDataGridView) コントロールとナビゲーション ツール バー コントロールがデザイナーに追加されます。 これらのコントロールは、作成された BindingSource (categoryBindingSource) およびバインド ナビゲーター (categoryBindingNavigator) コンポーネントにもバインドされます。

  • categoryDataGridView の列を編集します。 CategoryId 列を読み取り専用に設定します。 CategoryId プロパティの値は、データを保存した後にデータベースによって生成されます。

    • DataGridView コントロールを右クリックし、[列の編集]を選択します。..
    • CategoryId 列を選択し、ReadOnly を True に設定します。
    • [OK] を押す
  • カテゴリ データ ソースの下から [製品] を選択し、フォーム上にドラッグします。 productDataGridView と productBindingSource がフォームに追加されます。

  • productDataGridView の列を編集します。 CategoryId 列と Category 列を非表示にし、ProductId を読み取り専用に設定します。 ProductId プロパティの値は、データを保存した後にデータベースによって生成されます。

    • DataGridView コントロールを右クリックし、[ 列の編集...] を選択します。
    • ProductId 列を選択し、ReadOnlyTrue に設定します。
    • CategoryId 列を選択し、[削除] ボタンを押します。 [カテゴリ] 列でも同じ操作を行います。
    • OK をクリックします。

    ここまでは、DataGridView コントロールをデザイナーの BindingSource コンポーネントに関連付けしました。 次のセクションでは、分離コードにコードを追加して、dbContext によって現在追跡されているエンティティのコレクションに categoryBindingSource.DataSource を設定します。 カテゴリの下から製品をドラッグ アンド ドロップすると、WinForms は productsBindingSource.DataSource プロパティを categoryBindingSource に設定し、productsBindingSource.DataMember プロパティを Products に設定しました。 このバインドにより、現在選択されているカテゴリに属している製品のみが productDataGridView に表示されます。

  • ナビゲーション ツール バーの [保存 ] ボタンを有効にするには、マウスの右ボタンをクリックし、[ 有効] を選択します。

    フォーム 1 デザイナー

  • ボタンをダブルクリックして、保存ボタンのイベント ハンドラーを追加します。 これにより、イベント ハンドラーが追加され、フォームの分離コードが表示されます。 categoryBindingNavigatorSaveItem_Click イベント ハンドラーのコードは、次のセクションで追加されます。

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

次に、ProductContext を使用してデータ アクセスを実行するコードを追加します。 次に示すように、メイン フォーム ウィンドウのコードを更新します。

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

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows.Forms;
    using System.Data.Entity;

    namespace WinFormswithEFSample
    {
        public partial class Form1 : Form
        {
            ProductContext _context;
            public Form1()
            {
                InitializeComponent();
            }

            protected override void OnLoad(EventArgs e)
            {
                base.OnLoad(e);
                _context = new ProductContext();

                // Call the Load method to get the data for the given DbSet
                // from the database.
                // The data is materialized as entities. The entities are managed by
                // the DbContext instance.
                _context.Categories.Load();

                // Bind the categoryBindingSource.DataSource to
                // all the Unchanged, Modified and Added Category objects that
                // are currently tracked by the DbContext.
                // Note that we need to call ToBindingList() on the
                // ObservableCollection<TEntity> returned by
                // the DbSet.Local property to get the BindingList<T>
                // in order to facilitate two-way binding in WinForms.
                this.categoryBindingSource.DataSource =
                    _context.Categories.Local.ToBindingList();
            }

            private void categoryBindingNavigatorSaveItem_Click(object sender, EventArgs e)
            {
                this.Validate();

                // Currently, the Entity Framework doesn’t mark the entities
                // that are removed from a navigation property (in our example the Products)
                // as deleted in the context.
                // The following code uses LINQ to Objects against the Local collection
                // to find all products and marks any that do not have
                // a Category reference as deleted.
                // 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 do 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);
                    }
                }

                // Save the changes to the database.
                this._context.SaveChanges();

                // Refresh the controls to show the values         
                // that were generated by the database.
                this.categoryDataGridView.Refresh();
                this.productsDataGridView.Refresh();
            }

            protected override void OnClosing(CancelEventArgs e)
            {
                base.OnClosing(e);
                this._context.Dispose();
            }
        }
    }

Windows フォーム アプリケーションをテストする

  • アプリケーションをコンパイルして実行すると、機能をテストできます。

    保存前のフォーム 1

  • ストアで生成されたキーを保存すると、画面に表示されます。

    保存後のフォーム 1

  • Code First を使用した場合は、 WinFormswithEFSample.ProductContext データベースが自動的に作成されていることも確認できます。

    サーバー オブジェクト エクスプローラー