共用方式為


開始使用 Windows Forms

本逐步解說示範如何建置 SQLite 資料庫支援的簡單 Windows Forms (WinForms) 應用程式。 應用程式會使用 Entity Framework Core (EF Core) 從資料庫載入數據、追蹤對該數據的變更,並將這些變更保存回資料庫。

本逐步解說中的螢幕快照和程式代碼清單取自 Visual Studio 2022 17.3.0。

提示

您可以檢視本文中的 GitHut 範例

必要條件

您必須安裝 Visual Studio 2022 17.3 或更新版本,並 選取 .NET 桌面工作負載 來完成本逐步解說。 如需安裝最新版 Visual Studio 的詳細資訊,請參閱 安裝 Visual Studio

建立應用程式

  1. 開啟 Visual Studio

  2. 在開始視窗中,選擇 [建立新專案]

  3. 選擇 [Windows Forms 應用程式 ],然後選擇 [ 下一步]。

    建立新的 Windows Forms 專案

  4. 在下一個畫面中,為專案指定名稱,例如 GetStartedWinForms,然後選擇 [下一步]。

  5. 在下一個畫面中,選擇要使用的 .NET 版本。 本逐步解說是使用 .NET 7 所建立,但它也應該使用更新版本。

  6. 選擇 [建立] 。

安裝EF Core NuGet 套件

  1. 以滑鼠右鍵按兩下解決方案,然後選擇 [ 管理方案的 NuGet 套件...]。

    管理方案的 NuGet 套件

  2. 選擇 [流覽] 索引標籤,然後搜尋 “Microsoft.EntityFrameworkCore.Sqlite”。

  3. 選取 Microsoft.EntityFrameworkCore.Sqlite 套件。

  4. 檢查右窗格中的專案 GetStartedWinForms

  5. 選擇最新版本。 若要使用發行前版本,請確定 已核取 [包含發行前版本 ] 方塊。

  6. 按一下 [安裝]

    安裝 Microsoft.EntityFrameworkCore.Sqlite 套件

注意

Microsoft.EntityFrameworkCore.Sqlite 是搭配 SQLite 資料庫使用 EF Core 的「資料庫提供者」套件。 其他資料庫系統也提供類似的套件。 安裝資料庫提供者套件會自動帶入與該資料庫系統搭配使用EF Core所需的所有相依性。 這包括 Microsoft.EntityFrameworkCore 基底套件。

定義模型

在本逐步解說中,我們將使用「程式代碼優先」來實作模型。 這表示 EF Core 會根據您定義的 C# 類別來建立資料庫數據表和架構。 請參閱 管理資料庫架構 ,以瞭解如何改用現有的資料庫。

  1. 以滑鼠右鍵按兩下項目,然後選擇 [新增],然後選擇 [類別... ] 以新增類別。

    新增類別

  2. 使用檔案名 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!;
    }
    
  3. 使用下列程式代碼重複建立 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();
    }
    

類別 Products 上的 Category 屬性和 Category 類別上的 Product 屬性稱為「導覽」。 在EF Core 中,導覽會定義兩個實體類型之間的關聯性。 在此情況下,導覽會 Product.Category 參考指定產品所屬的類別。 同樣地, Category.Products 集合導覽會包含指定類別的所有產品。

提示

使用 Windows Forms 時,實作 IListSourceObservableCollectionListSource,可用於集合導覽。 這並非必要,但會改善雙向數據系結體驗。

定義 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" });
    }
}

請務必 此時建置解決方案

將控制項新增至表單

應用程式會顯示類別清單和產品清單。 在第一個清單中選取類別時,第二個清單會變更以顯示該類別的產品。 您可以修改這些清單以新增、移除或編輯產品和類別,這些變更可以按下 [儲存] 按鈕儲存至 SQLite 資料庫。

  1. 將主要表單的名稱從 Form1 變更為 MainForm

    將 Form1 重新命名為 MainForm

  2. 並將標題變更為「產品和類別」。

    Title MainForm as “Products and Categories”

  3. 使用 [ 工具箱] 新增兩 DataGridView 個控件,並彼此排列。

    新增 DataGridView

  4. 在第一個 DataGridView的 [屬性] 中,將 [名稱] 變更為 dataGridViewCategories

  5. 在第二個 DataGridView的 [屬性] 中,將 [名稱] 變更為 dataGridViewProducts

  6. 此外,使用 [ 工具箱] 新增 Button 控件。

  7. 將按鈕 buttonSave 命名為 ,並將文字命名為 「Save」。 表單看起來應該像這樣:

    表單配置

資料繫結

下一個步驟是將和 Category 型別從模型連接到 Product DataGridView 控件。 這會將 EF Core 載入的數據系結至控件,讓 EF Core 追蹤的實體與控件中顯示的實體保持同步。

  1. 按兩下第一個 上的設計工具動作圖像DataGridView 這是控制件右上角的小型按鈕。

    設計工具動作圖像

  2. 這會開啟 [動作清單],您可以從中存取 [選擇數據源] 的下拉式清單。 我們尚未建立數據源,因此請移至底部,然後選擇 [ 新增對象數據源...]。

    新增物件數據源

  3. 選擇 [類別 ] 以建立類別的對象數據源,然後按兩下 [ 確定]。

    選擇類別數據源類型

    提示

    如果這裡未顯示任何數據來源類型,請確定 Product.cs已將 和 Category.cs ProductsContext.cs 新增至專案,並已建置方案。

  4. 現在,[ 選擇數據源 ] 下拉式清單包含我們剛才建立的對象數據源。 展開 [其他數據源],然後展開 [項目數據源],然後選擇 [類別]。

    選擇類別數據源

    第二 DataGridView 個將系結至產品。 不過,與其系結至最上層Product類型,而是系結至Products第一個 DataGridView系結的Category導覽。 這表示在第一個檢視中選取類別時,該類別的產品會自動在第二個檢視中使用。

  5. 在第二個DataGridView上使用設計工具動作圖像,選擇 [選擇資料源],然後展開 categoryBindingSource 並選擇 Products

    選擇產品數據源

設定顯示的內容

根據預設,數據行會在 中 DataGridView 針對系結類型的每個屬性建立。 此外,使用者也可以編輯這些屬性的值。 不過,某些值,例如主鍵值,在概念上是只讀的,因此不應該編輯。 此外,某些屬性,例如 CategoryId 外鍵屬性和 Category 瀏覽對使用者而言並無用處,因此應該隱藏。

提示

在實際應用程式中隱藏主鍵屬性很常見。 您可以在這裡看到它們,讓您輕鬆查看 EF Core 在幕後執行的動作。

  1. 以滑鼠右鍵按兩下第一個 DataGridView ,然後選擇 [ 編輯資料行...]。

    編輯 DataGridView 資料行

  2. CategoryId將代表主鍵的數據行設為唯讀,然後按兩下 [確定]。

    將 CategoryId 資料行設為唯讀

  3. 以滑鼠右鍵按兩下第二個 DataGridView ,然後選擇 [ 編輯資料行...]。將數據 ProductId 行設為只讀,然後移除 CategoryIdCategory 數據行,然後按兩下 [ 確定]。

    將 ProductId 資料行設為唯讀,並移除 CategoryId 和 Category 數據行

聯機到 EF Core

應用程式現在需要少量的程序代碼,才能將EF Core 連接到數據綁定控件。

  1. 以滑鼠右鍵按下檔案並選擇 [檢視程序代碼],以開啟程序MainForm代碼

    檢視程式碼

  2. 新增私人欄位來保存DbContext工作階段的 ,並新增和 OnClosing 方法的OnLoad覆寫。 程式碼看起來應該類似:

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 實例,將用來載入和追蹤應用程式所顯示產品和類別的變更。
  • EnsureCreated 如果尚未存在,則會在上 DbContext 呼叫 來建立 SQLite 資料庫。 這是在建立原型或測試應用程式時建立資料庫的快速方式。 不過,如果模型變更,則必須刪除資料庫,才能再次建立資料庫。 (行 EnsureDeleted 可以取消批注,以在應用程式執行時輕鬆刪除及重新建立資料庫。您可以改為使用 EF Core 移轉 來修改和更新資料庫架構,而不會遺失任何數據。
  • EnsureCreated 也會使用方法中 ProductsContext.OnModelCreating 定義的數據填入新的資料庫。
  • 擴充Load方法可用來將資料庫中的所有類別載入 。DbContext 這些實體現在會由 DbContext追蹤,這會偵測使用者編輯類別時所做的任何變更。
  • 屬性 categoryBindingSource.DataSource 會初始化為 所 DbContext追蹤的類別。 這是藉由呼叫 Local.ToBindingList() Categories DbSet 屬性來完成。 Local 可讓您存取追蹤類別的本機檢視,並連結事件以確保本機數據與顯示的數據保持同步,反之亦然。 ToBindingList() 會將此數據公開為 IBindingList,這是 Windows Forms 數據系結所瞭解。

關閉 OnClosing 表單時會呼叫 方法。 此時, DbContext 會處置 ,以確保釋放任何資料庫資源,並將 dbContext 字段設定為 null,使其無法再次使用。

填入 [產品] 檢視

如果應用程式此時已啟動,則看起來應該像這樣:

應用程式的 Fist 執行

請注意,類別已從資料庫載入,但 products 數據表會保持空白。 此外,[ 儲存] 按鈕無法運作。

若要填入產品數據表,EF Core 必須從資料庫載入所選類別的產品。 若要達成此目的:

  1. 在主要表單的設計工具中,選取 DataGridView 類別的 。

  2. 在 的 [屬性DataGridView] 中,選擇事件 (閃電按鈕),然後按兩下 SelectionChanged 事件。

    新增 SelectionChanged 事件

    這會在主要表單程式代碼中建立存根,以便每當類別選取範圍變更時引發事件。

  3. 填入事件的程式代碼:

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 工作階段,則我們會取得 Category 系結至 目前選取之數據列的 DataViewGrid實例。 (如果 null 選取檢視中的最後一個數據列,則可能是用來建立新類別。如果有選取的類別,則會 DbContext 指示 載入與該類別相關聯的產品。 這是透過下列方式完成:

  • EntityEntry取得 實體的 CategorydbContext.Entry(category)
  • 讓 EF Core 知道我們想要在該 的集合導覽CategoryProducts操作 (.Collection(e => e.Products)
  • 最後告訴EF Core,我們想要從資料庫載入該集合產品 (.Load();

提示

呼叫 時 Load ,EF Core 只會在尚未載入產品時存取資料庫以載入產品。

如果應用程式現在再次執行,則每當選取類別時,它應該載入適當的產品:

已載入產品

正在儲存變更

最後,[ 儲存 ] 按鈕可以連線到 EF Core,以便對產品和類別所做的任何變更儲存至資料庫。

  1. 在主表單的設計工具中,選取 [ 儲存] 按鈕。

  2. 在 的 [屬性Button] 中,選擇事件 (閃電按鈕),然後按兩下 Click 事件。

    新增 Save 的 Click 事件

  3. 填入事件的程式代碼:

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 鍵來刪除資料列。

儲存之前

按兩下 [儲存] 之前執行中的應用程式

儲存之後

按兩下 [儲存] 之後執行中的應用程式

請注意,按兩下 [儲存] 時,會填入已新增類別和產品的主鍵值。

深入了解