Windows フォームについて
このステップバイステップ チュートリアルでは、SQLite データベースを利用する単純な Windows フォーム (WinForms) アプリケーションを構築する方法について説明します。 このアプリケーションでは、Entity Framework Core (EF Core) を使ってデータベースからデータを読み込み、そのデータに加えられた変更を追跡し、それらの変更をデータベースに保持します。
このチュートリアルのスクリーンショットとコード リストは、Visual Studio 2022 17.3.0 から取得されています。
ヒント
この記事のサンプルは GitHub で確認できます。
前提条件
このチュートリアルを完了するには、Visual Studio 2022 17.3 以降がインストールされ、.NET デスクトップ ワークロードが選ばれている必要があります。 Visual Studio の最新バージョンのインストールの詳細については、「Visual Studio のインストール」を参照してください。
アプリケーションを作成する
Visual Studio を開きます
スタート ウィンドウで、[新しいプロジェクトの作成] を選択します。
[Windows フォーム アプリケーション] を選び、[次へ] を選びます。
次の画面で、プロジェクトの名前を指定し (例: GetStartedWinForms)、[次へ] を選びます。
次の画面で、使う .NET バージョンを選びます。 このチュートリアルは .NET 7 を使って作成されましたが、それ以降のバージョンでも機能するはずです。
[作成] を選択します。
EF Core の NuGet パッケージをインストールする
ソリューションを右クリックして、[ソリューションの NuGet パッケージの管理...] を選択します。
[参照] タブを選び、「Microsoft.EntityFrameworkCore.Sqlite」を検索します。
Microsoft.EntityFrameworkCore.Sqlite パッケージを選択します。
右ペインのプロジェクト GetStartedWinForms を確認します。
最新バージョンを選択します。 プレリリース バージョンを使う場合は、必ず [プレリリースを含める] をオンにします。
[インストール]をクリックします。
Note
Microsoft.EntityFrameworkCore.Sqlite は、SQLite データベースで EF Core を使うための "データベース プロバイダー" パッケージです。 他のデータベース システムでも同様のパッケージを使用できます。 データベース プロバイダー パッケージをインストールすると、そのデータベース システムで EF Core を使うために必要なすべての依存関係が自動的に導入されます。 これには Microsoft.EntityFrameworkCore ベース パッケージが含まれます。
モデルを定義する
このチュートリアルでは、"Code First" を使ってモデルを実装します。 つまり、定義した C# クラスに基づいて、EF Core によりデータベース テーブルとスキーマが作成されます。 代わりに既存のデータベースを使う方法については、データベース スキーマの管理に関する記事を参照してください。
プロジェクトを右クリックして [追加] を選び、[クラス] を選んで新しいクラスを追加します。
ファイル名
Product.cs
を使い、クラスのコードを次の内容に置き換えます。using System.ComponentModel; namespace GetStartedWinForms; public class Product { public int ProductId { get; set; } public string? Name { get; set; } public int CategoryId { get; set; } public virtual Category Category { get; set; } = null!; }
次のコードを使った
Category.cs
の作成を繰り返します。using Microsoft.EntityFrameworkCore.ChangeTracking; namespace GetStartedWinForms; public class Category { public int CategoryId { get; set; } public string? Name { get; set; } public virtual ObservableCollectionListSource<Product> Products { get; } = new(); }
Category
クラスの Products
プロパティと、Product
クラスの Category
プロパティは、"ナビゲーション" と呼ばれます。 EF Core では、ナビゲーションによって 2 つのエンティティ型間のリレーションシップを定義します。 この場合、Product.Category
ナビゲーションは、指定した製品が属するカテゴリを参照します。 同様に、Category.Products
コレクション ナビゲーションには、指定したカテゴリのすべての製品が含まれています。
ヒント
Windows フォームを使う場合、IListSource
を実装する ObservableCollectionListSource
はコレクション ナビゲーションに使用できます。 これは必須ではありませんが、双方向のデータ バインディング エクスペリエンスが向上します。
DbContext を定義する
EF Core では、DbContext
から派生したクラスは、モデル内のエンティティ型を構成し、データベースと対話するためのセッションとして機能するために使われます。 最も単純なケースでは、DbContext
クラスです。
- モデル内のエンティティ型ごとに
DbSet
プロパティを含みます。 OnConfiguring
メソッドをオーバーライドして、使うデータベース プロバイダーと接続文字列を構成します。 詳細については DbContext の構成に関する記事を参照してください。
この場合、DbContext クラスも OnModelCreating
メソッドをオーバーライドし、アプリケーションに一部のサンプル データを提供します。
次のようなコードを使って新しい ProductsContext.cs
クラスをプロジェクトに追加します。
using Microsoft.EntityFrameworkCore;
namespace GetStartedWinForms;
public class ProductsContext : 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");
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Category>().HasData(
new Category { CategoryId = 1, Name = "Cheese" },
new Category { CategoryId = 2, Name = "Meat" },
new Category { CategoryId = 3, Name = "Fish" },
new Category { CategoryId = 4, Name = "Bread" });
modelBuilder.Entity<Product>().HasData(
new Product { ProductId = 1, CategoryId = 1, Name = "Cheddar" },
new Product { ProductId = 2, CategoryId = 1, Name = "Brie" },
new Product { ProductId = 3, CategoryId = 1, Name = "Stilton" },
new Product { ProductId = 4, CategoryId = 1, Name = "Cheshire" },
new Product { ProductId = 5, CategoryId = 1, Name = "Swiss" },
new Product { ProductId = 6, CategoryId = 1, Name = "Gruyere" },
new Product { ProductId = 7, CategoryId = 1, Name = "Colby" },
new Product { ProductId = 8, CategoryId = 1, Name = "Mozzela" },
new Product { ProductId = 9, CategoryId = 1, Name = "Ricotta" },
new Product { ProductId = 10, CategoryId = 1, Name = "Parmesan" },
new Product { ProductId = 11, CategoryId = 2, Name = "Ham" },
new Product { ProductId = 12, CategoryId = 2, Name = "Beef" },
new Product { ProductId = 13, CategoryId = 2, Name = "Chicken" },
new Product { ProductId = 14, CategoryId = 2, Name = "Turkey" },
new Product { ProductId = 15, CategoryId = 2, Name = "Prosciutto" },
new Product { ProductId = 16, CategoryId = 2, Name = "Bacon" },
new Product { ProductId = 17, CategoryId = 2, Name = "Mutton" },
new Product { ProductId = 18, CategoryId = 2, Name = "Pastrami" },
new Product { ProductId = 19, CategoryId = 2, Name = "Hazlet" },
new Product { ProductId = 20, CategoryId = 2, Name = "Salami" },
new Product { ProductId = 21, CategoryId = 3, Name = "Salmon" },
new Product { ProductId = 22, CategoryId = 3, Name = "Tuna" },
new Product { ProductId = 23, CategoryId = 3, Name = "Mackerel" },
new Product { ProductId = 24, CategoryId = 4, Name = "Rye" },
new Product { ProductId = 25, CategoryId = 4, Name = "Wheat" },
new Product { ProductId = 26, CategoryId = 4, Name = "Brioche" },
new Product { ProductId = 27, CategoryId = 4, Name = "Naan" },
new Product { ProductId = 28, CategoryId = 4, Name = "Focaccia" },
new Product { ProductId = 29, CategoryId = 4, Name = "Malted" },
new Product { ProductId = 30, CategoryId = 4, Name = "Sourdough" },
new Product { ProductId = 31, CategoryId = 4, Name = "Corn" },
new Product { ProductId = 32, CategoryId = 4, Name = "White" },
new Product { ProductId = 33, CategoryId = 4, Name = "Soda" });
}
}
この時点で、必ずソリューションをビルドしてください。
フォームへのコントロールの追加
このアプリケーションには、カテゴリの一覧と製品の一覧が表示されます。 最初の一覧でカテゴリが選ばれると、2 つ目の一覧はそのカテゴリの製品を表示するように変更されます。 これらの一覧を変更して製品とカテゴリの追加、削除、または編集を行うことができます。また、[保存] ボタンをクリックすることで、SQLite データベースにこれらに変更を保存できます。
メイン フォームの名前を
Form1
からMainForm
に変更します。次に、タイトルを「製品とカテゴリ」に変更します。
Toolbox を使って、2 つの
DataGridView
コントロールを追加し、並べて配置します。最初の
DataGridView
の [プロパティ] で、[名前] をdataGridViewCategories
に変更します。2 つ目の
DataGridView
の [プロパティ] で、[名前] をdataGridViewProducts
に変更します。また、Toolbox を使って、
Button
コントロールを追加します。ボタンに
buttonSave
という名前を付け、「保存」というテキストを指定します。 フォームは次のようになります。
データ バインディング
次の手順は、モデルから Product
と Category
の型を DataGridView
コントロールに接続することです。 そうすると、EF Core によって読み込まれたデータはコントロールにバインドされ、EF Core によって追跡されるエンティティは、コントロールに表示されるものと同期が保たれるようになります。
1 つ目の
DataGridView
に対してデザイナー アクション グリフをクリックします。 これは、コントロールの右上隅にある小さなボタンです。これで [アクション] 一覧が開きます。そこから [データ ソースの選択] ドロップダウンにアクセスできます。 まだデータ ソースを作成していないので、一番下に移動し、[新しいオブジェクト データ ソースの追加] を選びます。
[カテゴリ] を選んで、カテゴリのオブジェクト データ ソースを作成し、[OK] をクリックします。
ヒント
ここにデータ ソースの種類が表示されない場合は、
Product.cs
、Category.cs
、ProductsContext.cs
がプロジェクトに追加されていることと "とソリューションがビルドされていること" を確認します。これで、[データ ソースの選択] ドロップダウンに、先ほど作成したオブジェクト データ ソースが表示されるようになります。 [その他のデータ ソース] を展開してから、[プロジェクト データ ソース] を選び、[カテゴリ] を選びます。
2 つ目の
DataGridView
は製品にバインドされます。 ただし、最上位レベルのProduct
型にバインドされるのではなく、1 つ目のDataGridView
のCategory
バインドからProducts
ナビゲーションにバインドされます。 これは、1 つ目のビューでカテゴリが選ばれると、そのカテゴリの製品が自動的に 2 つ目のビューで使われることを意味します。2 つ目の
DataGridView
に対してデザイナー アクション グリフを使って、[データ ソースの選択] を選び、categoryBindingSource
を展開し、Products
を選びます。
表示内容を構成する
既定では、バインドされた型を持つプロパティごとに、DataGridView
に列が作成されます。 また、これらの各プロパティの値はユーザーが編集できます。 ただし、主キー値などの一部の値は概念的に読み取り専用であるため、編集しないでください。 また、CategoryId
外部キー プロパティや Category
ナビゲーションなど、一部のプロパティはユーザーにとって役に立たないので、非表示にする必要があります。
ヒント
実際のアプリケーションでは、主キーのプロパティを非表示にするのが一般的です。 背後で EF Core が行っている処理を簡単に確認できるように、ここでは表示したままにしています。
1 つ目の
DataGridView
を右クリックして、[列の編集] を選びます。主キーを表す
CategoryId
列を読み取り専用にし、[OK] をクリックします。2 つ目の
DataGridView
を右クリックし、[列の編集] を選びます。ProductId
列を読み取り専用にし、CategoryId
とCategory
の列を削除して、[OK] をクリックします。
EF Core への接続
このアプリケーションで EF Core をデータ バインド コントロールに接続するには、コードが少し必要です。
ファイルを右クリックし、[コードの表示] を選んで、
MainForm
コードを開きます。セッションのために
DbContext
を保持するプライベート フィールドを追加し、OnLoad
とOnClosing
のメソッドのオーバーライドを追加します。 コードは、次のようになります。
using Microsoft.EntityFrameworkCore;
using System.ComponentModel;
namespace GetStartedWinForms
{
public partial class MainForm : Form
{
private ProductsContext? dbContext;
public MainForm()
{
InitializeComponent();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
this.dbContext = new ProductsContext();
// Uncomment the line below to start fresh with a new database.
// this.dbContext.Database.EnsureDeleted();
this.dbContext.Database.EnsureCreated();
this.dbContext.Categories.Load();
this.categoryBindingSource.DataSource = dbContext.Categories.Local.ToBindingList();
}
protected override void OnClosing(CancelEventArgs e)
{
base.OnClosing(e);
this.dbContext?.Dispose();
this.dbContext = null;
}
}
}
OnLoad
メソッドは、フォームが読み込まれるときに呼び出されます。 この時点で
ProductsContext
のインスタンスが作成されます。これは、アプリケーションに表示される製品とカテゴリの変更を読み込み、追跡するために使われます。DbContext
に対してEnsureCreated
が呼び出され、SQLite データベースがまだ存在しない場合に作成されます。 アプリケーションのプロトタイプを作成したりテストしたりするときに、この方法でデータベースを簡単に作成できます。 ただし、モデルが変更された場合は、データベースを削除して、再度作成できるようにする必要があります (アプリケーションの実行時にデータベースを簡単に削除して再作成するために、EnsureDeleted
行のコメントアウトを解除することができます)。代わりに [EF Core Migrations] (EF Core の移行) を使うと、データを失うことなく、データベース スキーマの変更と更新を行うことができます。- また、
EnsureCreated
はProductsContext.OnModelCreating
メソッドで定義されたデータを新しいデータベースに設定します。 Load
拡張メソッドは、データベースからDbContext
にすべてのカテゴリを読み込むために使われます。 これらのエンティティはDbContext
によって追跡されるようになります。その結果、ユーザーがカテゴリを編集したときに加えたすべての変更が検出されます。categoryBindingSource.DataSource
プロパティはDbContext
によって追跡されるカテゴリに初期化されます。 これは、Categories
DbSet
プロパティに対してLocal.ToBindingList()
を呼び出すことで行われます。Local
によって、追跡されるカテゴリのローカル ビューへのアクセス権が付与され、イベントがフックされ、ローカル データから表示データとその逆方向の同期状態が確実に保たれます。ToBindingList()
はこのデータをIBindingList
として公開します。これは、Windows フォーム データ バインディングによって認識されます。
OnClosing
メソッドは、フォームが閉じられたときに呼び出されます。 この時点で DbContext
は破棄されます。これにより、すべてのデータベース リソースは確実に解放され、dbContext
フィールドは NULL に設定され、再使用できなくなります。
製品ビューの設定
この時点でアプリケーションを起動すると、次のような外観になります。
カテゴリはデータベースから読み込まれましたが、products テーブルは空のままであることに注意してください。 また、[保存] ボタンは機能しません。
products テーブルに設定するために、EF Core は選んだカテゴリの製品をデータベースから読み込む必要があります。 具体的な手順は次のとおりです。
メイン フォームのデザイナーで、カテゴリに
DataGridView
を選びます。DataGridView
の [プロパティ] で、イベント (稲妻ボタン) を選び、SelectionChanged イベントをダブルクリックします。これにより、カテゴリの選択が変わるたびに発生するイベントのスタブがメイン フォーム コードに作成されます。
イベントのコードを入力します。
private void dataGridViewCategories_SelectionChanged(object sender, EventArgs e)
{
if (this.dbContext != null)
{
var category = (Category)this.dataGridViewCategories.CurrentRow.DataBoundItem;
if (category != null)
{
this.dbContext.Entry(category).Collection(e => e.Products).Load();
}
}
}
このコードでは、アクティブな (非 NULL の) DbContext
セッションがある場合、DataViewGrid
の現在選ばれている行にバインドされている Category
インスタンスを取得します (これは、ビューの最後の行が選ばれている場合、新しいカテゴリを作成するために使われる null
である可能性があります)。選ばれたカテゴリがある場合、DbContext
はそのカテゴリに関連付けられた製品を読み込むよう指示されます。 このためには、次のことを行います。
Category
インスタンスのEntityEntry
の取得 (dbContext.Entry(category)
)- その
Category
のProducts
コレクション ナビゲーションで操作することを EF Core に知らせる (.Collection(e => e.Products)
) - 最後に、データベースから製品のそのコレクションを読み込むことを EF Core に伝える (
.Load();
)
ヒント
Load
が呼び出されると、EF Core は、まだ読み込まれていない場合にのみ、データベースにアクセスして製品を読み込みます。
次にアプリケーションを再実行する場合は、カテゴリが選ばれるたびに適切な製品を読み込む必要があります。
変更を保存しています
最後に、[保存] ボタンを EF Core に接続すると、製品とカテゴリに加えられたすべての変更がデータベースに保存されるようになります。
メイン フォームのデザイナーで、[保存] ボタンを選びます。
Button
の [プロパティ] で、イベント (稲妻ボタン) を選び、Click イベントをダブルクリックします。イベントのコードを入力します。
private void buttonSave_Click(object sender, EventArgs e)
{
this.dbContext!.SaveChanges();
this.dataGridViewCategories.Refresh();
this.dataGridViewProducts.Refresh();
}
このコードでは、DbContext
に対して SaveChanges
を呼び出し、SQLite データベースに加えられたすべての変更が保存されます。 変更がなければ、これは操作なしであり、データベースの呼び出しは行われません。 保存後、DataGridView
コントロールは更新されます。 これは、データベースから新しい製品とカテゴリ用に生成された主キー値を EF Core が読み取るためです。 Refresh
を呼び出すと、これらの生成された値で表示は更新されます。
最終的なアプリケーション
メイン フォームの完全なコードは次のとおりです。
using Microsoft.EntityFrameworkCore;
using System.ComponentModel;
namespace GetStartedWinForms
{
public partial class MainForm : Form
{
private ProductsContext? dbContext;
public MainForm()
{
InitializeComponent();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
this.dbContext = new ProductsContext();
// Uncomment the line below to start fresh with a new database.
// this.dbContext.Database.EnsureDeleted();
this.dbContext.Database.EnsureCreated();
this.dbContext.Categories.Load();
this.categoryBindingSource.DataSource = dbContext.Categories.Local.ToBindingList();
}
protected override void OnClosing(CancelEventArgs e)
{
base.OnClosing(e);
this.dbContext?.Dispose();
this.dbContext = null;
}
private void dataGridViewCategories_SelectionChanged(object sender, EventArgs e)
{
if (this.dbContext != null)
{
var category = (Category)this.dataGridViewCategories.CurrentRow.DataBoundItem;
if (category != null)
{
this.dbContext.Entry(category).Collection(e => e.Products).Load();
}
}
}
private void buttonSave_Click(object sender, EventArgs e)
{
this.dbContext!.SaveChanges();
this.dataGridViewCategories.Refresh();
this.dataGridViewProducts.Refresh();
}
}
}
これでアプリケーションを実行できるようになりました。製品とカテゴリを追加、削除、編集することができます。 アプリケーションを閉じる前に [保存] ボタンをクリックすると、加えられた変更内容はデータベースに格納され、アプリケーションの再起動時に再度読み込まれることに注意してください。 [保存] をクリックしない場合、アプリケーションの再起動時に変更内容は失われます。
ヒント
コントロールの下部にある空の行を使って、新しいカテゴリまたは製品を DataViewControl
に追加できます。 行を削除するには、行を選んで Del キーを押します。
保存前
保存後
[保存] をクリックすると、追加されたカテゴリと製品の主キー値が設定されることに注意してください。
詳細情報
.NET