このステップ バイ ステップのチュートリアルでは、POCO 型を WPF コントロールに "メイン詳細" フォームでバインドする方法を示します。 アプリケーションは Entity Framework API を使用して、データベースからのデータをオブジェクトに設定し、変更を追跡し、データをデータベースに保持します。
このモデルでは、一対多リレーションシップに参加する 2 つの型 ( Category (principal\main) と Product (dependent\detail) が定義されています。 WPF データ バインディング フレームワークを使用すると、関連オブジェクト間のナビゲーションが可能になります。マスター ビューで行を選択すると、詳細ビューが対応する子データで更新されます。
このチュートリアルのスクリーン ショットとコード一覧は、Visual Studio 2019 16.6.5 から取得します。
Tip
この記事の サンプルは、GitHubで確認できます。
Pre-Requisites
このチュートリアルを完了するには、 .NET デスクトップ ワークロード を選択して Visual Studio 2019 16.3 以降をインストールする必要があります。 最新バージョンの Visual Studio のインストールの詳細については、「 Visual Studio のインストール」を参照してください。
アプリケーションを作成する
- Visual Studio を開く
- スタート ウィンドウで、[ 新しいプロジェクトの作成] を選択します。
- "WPF" を検索し、[ WPF アプリ (.NET)] を選択し、[ 次へ] を選択します。
- 次の画面で、プロジェクトに GetStartedWPF などの名前を付け、[作成] を選択します 。
Entity Framework NuGet パッケージをインストールする
ソリューションを右クリックし、[ソリューション の NuGet パッケージの管理]を選択します。..
検索ボックスに、「
entityframeworkcore.sqlite
」と入力します。Microsoft.EntityFrameworkCore.Sqlite パッケージを選択します。
右側のウィンドウでプロジェクトを確認し、[インストール] をクリックします
手順を繰り返して
entityframeworkcore.proxies
を検索し、 Microsoft.EntityFrameworkCore.Proxies をインストールします。
Note
Sqlite パッケージをインストールすると、関連する Microsoft.EntityFrameworkCore ベース パッケージが自動的にプルダウンされます。 Microsoft.EntityFrameworkCore.Proxies パッケージは、"遅延読み込み" データのサポートを提供します。 エンティティに子エンティティがある場合、最初の読み込みでは親エンティティのみが取得されます。 プロキシは、子エンティティにアクセスしようとしたときに検出し、必要に応じて自動的に読み込みます。
モデルを定義する
このチュートリアルでは、"code first" を使用してモデルを実装します。つまり、EF Core では、定義した C# クラスに基づいてデータベース テーブルとスキーマが作成されます。
新しいクラスを追加します。 Product.cs
名前を付け、次のように設定します。
Product.cs
namespace GetStartedWPF
{
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.cs
という名前のクラスを追加し、次のコードを設定します。
Category.cs
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace GetStartedWPF
{
public class Category
{
public int CategoryId { get; set; }
public string Name { get; set; }
public virtual ICollection<Product>
Products
{ get; private set; } =
new ObservableCollection<Product>();
}
}
Category クラスのProducts プロパティと Product クラスの Category プロパティはナビゲーション プロパティです。 Entity Framework のナビゲーション プロパティは、2 つのエンティティ型間のリレーションシップをナビゲートする方法を提供します。
エンティティの定義に加えて、DbContext から派生し、DbSet<TEntity> プロパティを公開するクラスを定義する必要があります。 DbSet<TEntity> プロパティを使用すると、モデルに含める型がコンテキストに認識されます。
DbContext 派生型のインスタンスは、実行時にエンティティ オブジェクトを管理します。これには、データベースからのデータを含むオブジェクトの設定、変更の追跡、データベースへのデータの永続化が含まれます。
次の定義を使用して、新しい ProductContext.cs
クラスをプロジェクトに追加します。
ProductContext.cs
using Microsoft.EntityFrameworkCore;
namespace GetStartedWPF
{
public class ProductContext : DbContext
{
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
protected override void OnConfiguring(
DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite(
"Data Source=products.db");
optionsBuilder.UseLazyLoadingProxies();
}
}
}
DbSet
は、データベースにマップする必要がある C# エンティティを EF Core に通知します。- EF Core
DbContext
を構成するには、さまざまな方法があります。 詳細については 、DbContext の構成に関するページを参照してください。 - この例では、
OnConfiguring
オーバーライドを使用して Sqlite データ ファイルを指定します。 UseLazyLoadingProxies
呼び出しは EF Core に遅延読み込みを実装するように指示するため、親からアクセスすると子エンティティが自動的に読み込まれます。
CTRL+SHIFT+B を押すか[Build > Build Solution](ソリューションのビルド)に移動してプロジェクトをコンパイルします。
Tip
データベースと EF Core モデルを同期させるさまざまな方法について学びます: データベース スキーマの管理。
Lazy Loading
Category クラスのProducts プロパティと Product クラスの Category プロパティはナビゲーション プロパティです。 Entity Framework Core のナビゲーション プロパティは、2 つのエンティティ型間のリレーションシップをナビゲートする方法を提供します。
EF Core では、ナビゲーション プロパティに初めてアクセスするときに、データベースから関連エンティティを自動的に読み込むオプションが提供されます。 この種類の読み込み (遅延読み込みと呼ばれます) では、各ナビゲーション プロパティに初めてアクセスするときに、コンテンツがまだコンテキストにない場合は、データベースに対して個別のクエリが実行されることに注意してください。
"Plain Old C# Object" (POCO) エンティティ型を使用する場合、EF Core は実行時に派生プロキシ型のインスタンスを作成し、クラス内の仮想プロパティをオーバーライドして読み込みフックを追加することで遅延読み込みを実現します。 関連オブジェクトの遅延読み込みを取得するには、ナビゲーション プロパティゲッターを パブリック および 仮想 として宣言する必要があります (Visual Basic ではオーバーライド可能 )、クラスを シール することはできません (Visual Basic では NotOverridable )。 Database First を使用すると、遅延読み込みを有効にするためにナビゲーション プロパティが自動的に仮想になります。
オブジェクトをコントロールにバインドする
この WPF アプリケーションのデータ ソースとしてモデルで定義されているクラスを追加します。
ソリューション エクスプローラーで MainWindow.xaml をダブルクリックしてメイン フォームを開く
[XAML] タブを選択して XAML を編集します。
開始
Window
タグの直後に、次のソースを追加して EF Core エンティティに接続します。<Window x:Class="GetStartedWPF.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:GetStartedWPF" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800" Loaded="Window_Loaded"> <Window.Resources> <CollectionViewSource x:Key="categoryViewSource"/> <CollectionViewSource x:Key="categoryProductsViewSource" Source="{Binding Products, Source={StaticResource categoryViewSource}}"/> </Window.Resources>
これにより、"親" カテゴリのソースと、"詳細" 製品の 2 番目のソースが設定されます。
次に、開始
Grid
タグの後に、次のマークアップを XAML に追加します。<DataGrid x:Name="categoryDataGrid" AutoGenerateColumns="False" EnableRowVirtualization="True" ItemsSource="{Binding Source={StaticResource categoryViewSource}}" Margin="13,13,43,229" RowDetailsVisibilityMode="VisibleWhenSelected"> <DataGrid.Columns> <DataGridTextColumn Binding="{Binding CategoryId}" Header="Category Id" Width="SizeToHeader" IsReadOnly="True"/> <DataGridTextColumn Binding="{Binding Name}" Header="Name" Width="*"/> </DataGrid.Columns> </DataGrid>
CategoryId
はデータベースによって割り当てられ、変更できないため、ReadOnly
に設定されていることに注意してください。
詳細グリッドの追加
カテゴリを表示するグリッドが存在するため、詳細グリッドを追加して製品を表示できるようになりました。 カテゴリGrid
要素の後に、DataGrid
要素内にこれを追加します。
MainWindow.xaml
<DataGrid x:Name="productsDataGrid" AutoGenerateColumns="False"
EnableRowVirtualization="True"
ItemsSource="{Binding Source={StaticResource categoryProductsViewSource}}"
Margin="13,205,43,108" RowDetailsVisibilityMode="VisibleWhenSelected"
RenderTransformOrigin="0.488,0.251">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding CategoryId}"
Header="Category Id" Width="SizeToHeader"
IsReadOnly="True"/>
<DataGridTextColumn Binding="{Binding ProductId}" Header="Product Id"
Width="SizeToHeader" IsReadOnly="True"/>
<DataGridTextColumn Binding="{Binding Name}" Header="Name" Width="*"/>
</DataGrid.Columns>
</DataGrid>
最後に、Save
ボタンを追加して、Button_Click
にクリック イベントを結び付けます。
<Button Content="Save" HorizontalAlignment="Center" Margin="0,240,0,0"
Click="Button_Click" Height="20" Width="123"/>
デザイン ビューは次のようになります。
データ操作を処理するコードを追加する
ここでは、メイン ウィンドウにいくつかのイベント ハンドラーを追加します。
XAML ウィンドウで、 <Window> 要素をクリックして、メイン ウィンドウを選択します。
[ プロパティ ] ウィンドウで、右上にある [イベント ] を選択し、[ 読み込まれた ] ラベルの右側にあるテキスト ボックスをダブルクリックします。
これにより、フォームの裏側のコードが表示されます。次に、ProductContext
を使用してデータアクセスを実行するようにコードを編集します。 次に示すようにコードを更新します。
このコードは、 ProductContext
の実行時間の長いインスタンスを宣言します。 ProductContext
オブジェクトは、クエリを実行してデータベースにデータを保存するために使用されます。 その後、Dispose()
インスタンスのProductContext
メソッドは、オーバーライドされたOnClosing
メソッドから呼び出されます。 コード コメントでは、各手順の内容が説明されています。
MainWindow.xaml.cs
using Microsoft.EntityFrameworkCore;
using System.ComponentModel;
using System.Windows;
using System.Windows.Data;
namespace GetStartedWPF
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private readonly ProductContext _context =
new ProductContext();
private CollectionViewSource categoryViewSource;
public MainWindow()
{
InitializeComponent();
categoryViewSource =
(CollectionViewSource)FindResource(nameof(categoryViewSource));
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// this is for demo purposes only, to make it easier
// to get up and running
_context.Database.EnsureCreated();
// load the entities into EF Core
_context.Categories.Load();
// bind to the source
categoryViewSource.Source =
_context.Categories.Local.ToObservableCollection();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
// all changes are automatically tracked, including
// deletes!
_context.SaveChanges();
// this forces the grid to refresh to latest values
categoryDataGrid.Items.Refresh();
productsDataGrid.Items.Refresh();
}
protected override void OnClosing(CancelEventArgs e)
{
// clean up database connections
_context.Dispose();
base.OnClosing(e);
}
}
}
Note
このコードでは、 EnsureCreated()
呼び出しを使用して、最初の実行時にデータベースをビルドします。 これはデモでもかまいませんが、運用環境のアプリでは、スキーマを管理するための 移行 を確認する必要があります。 ローカル SQLite データベースを使用するため、コードも同期的に実行されます。 通常、リモート サーバーが関係する運用シナリオでは、 Load
メソッドと SaveChanges
メソッドの非同期バージョンの使用を検討してください。
WPF アプリケーションをテストする
F5 キーを押すか、[デバッグ] >[デバッグの開始] を選択して、アプリケーションをコンパイルして実行します。 データベースは、 products.db
という名前のファイルを使用して自動的に作成されます。 カテゴリ名を入力して Enter キーを押し、下のグリッドに製品を追加します。 [保存] をクリックし、データベース指定の ID でグリッドの更新を確認します。 行を強調表示し、[ 削除] をクリックして行を削除します。 [ 保存] をクリックすると、エンティティが削除されます。
プロパティ変更通知
この例では、エンティティを UI と同期する 4 つの手順に依存しています。
- 最初の呼び出し
_context.Categories.Load()
は、カテゴリ データを読み込みます。 - 遅延読み込みプロキシは、依存製品データを読み込みます。
- EF Core の組み込みの変更追跡では、
_context.SaveChanges()
が呼び出されたときに、挿入や削除など、エンティティに必要な変更が行われます。 DataGridView.Items.Refresh()
の呼び出しでは、新しく生成された ID を使用して強制的に再読み込みを行います。
これは入門サンプルで機能しますが、他のシナリオでは追加のコードが必要になる場合があります。 WPF コントロールは、エンティティのフィールドとプロパティを読み取って UI をレンダリングします。 ユーザー インターフェイス (UI) で値を編集すると、その値がエンティティに渡されます。 エンティティでプロパティの値を直接変更した場合 (データベースからの読み込みなど)、WPF は UI の変更をすぐに反映しません。 レンダリング エンジンには、変更を通知する必要があります。 プロジェクトは、 Refresh()
を手動で呼び出すことによってこれを行いました。 この通知を自動化する簡単な方法は、 INotifyPropertyChanged インターフェイスを実装することです。 WPF コンポーネントは、インターフェイスを自動的に検出し、変更イベントに登録します。 エンティティは、これらのイベントを発生させる役割を担います。
Tip
変更を処理する方法の詳細については、「 プロパティ変更通知を実装する方法」を参照してください。
Next Steps
DbContext の構成の詳細を確認します。
.NET