Приступая к работе с Windows Forms

В этом пошаговом руководстве показано, как создать простое приложение Windows Forms (WinForms), поддерживаемое базой данных SQLite. Приложение использует Entity Framework Core (EF Core) для загрузки данных из базы данных, отслеживания изменений, внесенных в эти данные, и сохранения этих изменений обратно в базу данных.

Снимки экрана и описания кода в этом пошаговом руководстве взяты из Visual Studio 2022 17.3.0.

Совет

Вы можете скачать используемый в этой статье пример из репозитория GitHub.

Необходимые компоненты

Для выполнения этого пошагового руководства необходимо установить Visual Studio 2022 17.3 или более поздней версии с рабочей нагрузкой рабочего стола .NET. Дополнительные сведения об установке новейшей версии Visual Studio см. в статье Установка Visual Studio.

Создание приложения

  1. Запустите Visual Studio

  2. На начальном экране выберите Создать проект.

  3. Выберите приложение Windows Forms и нажмите кнопку "Далее".

    Create a new Windows Forms project

  4. На следующем экране укажите имя проекта, например GetStartedWinForms и нажмите кнопку "Далее".

  5. На следующем экране выберите используемую версию .NET. Это пошаговое руководство было создано с помощью .NET 7, но оно также должно работать с более поздними версиями.

  6. Выберите Создать.

Установка пакетов NuGet EF Core

  1. Щелкните правой кнопкой мыши решение и выберите Управление пакетами NuGet для решения...

    Manage NuGet Packages for Solution

  2. Перейдите на вкладку "Обзор" и найдите "Microsoft.EntityFrameworkCore.Sqlite".

  3. Выберите пакет Microsoft.EntityFrameworkCore.Sqlite.

  4. Проверьте проект GetStartedWinForms в правой области.

  5. Выберите последнюю версию. Чтобы использовать предварительную версию, убедитесь, что поле "Включить предварительную версию" проверка.

  6. Щелкните Установить.

    Install the Microsoft.EntityFrameworkCore.Sqlite package

Примечание.

Microsoft.EntityFrameworkCore.Sqlite — это пакет поставщика базы данных для использования EF Core с базой данных SQLite. Аналогичные пакеты доступны для других систем баз данных. Установка пакета поставщика базы данных автоматически приводит все зависимости, необходимые для использования EF Core с этой системой базы данных. Сюда входит базовый пакет Microsoft.EntityFrameworkCore .

Определение модели

В этом пошаговом руководстве мы реализуем модель с помощью Code First. Таким образом, EF Core создаст таблицы и схему базы данных на основе определенных вами классов C#. См. статью "Управление схемами баз данных" , чтобы узнать, как использовать существующую базу данных.

  1. Щелкните проект правой кнопкой мыши и нажмите кнопку "Добавить", а затем "Класс" , чтобы добавить новый класс.

    Add new class

  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();
    }
    

Свойство ProductsCategory класса и Category свойства класса Product называются навигациями. В EF Core навигации определяют связь между двумя типами сущностей. В этом случае навигация Product.Category ссылается на категорию, к которой принадлежит данный продукт. Аналогичным образом навигация Category.Products по коллекции содержит все продукты для данной категории.

Совет

При использовании Windows Forms , ObservableCollectionListSourceкоторый реализует IListSource, можно использовать для навигации по коллекции. Это не обязательно, но улучшает двусторонняя привязка данных.

Определение 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. Измените имя основной формы на Form1MainForm.

    Rename Form1 to MainForm

  2. И измените заголовок на "Продукты и категории".

    Title MainForm as

  3. С помощью панели элементов добавьте два DataGridView элемента управления, расположенные рядом друг с другом.

    Add DataGridView

  4. В свойствах для первого DataGridViewизмените имяdataGridViewCategoriesна .

  5. В свойствах второго DataGridViewизмените имяdataGridViewProductsна .

  6. Кроме того, с помощью панели элементов добавьте Button элемент управления.

  7. Назовите кнопку buttonSave и присвойте ей текст "Сохранить". Форма должна выглядеть следующим образом:

    Form layout

Привязка данных

Следующим шагом является подключение Product и Category типы из модели к DataGridView элементам управления. Это привязывает данные, загруженные EF Core, к элементам управления, таким образом, что сущности, отслеживаемые EF Core, синхронизируются с данными, отображаемыми в элементах управления.

  1. Щелкните глиф действия конструктора на первомDataGridView. Это крошечная кнопка в правом верхнем углу элемента управления.

    The Designer Action Glyph

  2. Откроется список действий, из которого можно получить доступ к раскрывающемся списку для выбранного источника данных. Мы еще не создали источник данных, поэтому перейдите к нижнему краю и нажмите кнопку "Добавить новый источник данных объекта...".

    Add new Object Data Source

  3. Выберите категорию , чтобы создать источник данных объекта для категорий и нажмите кнопку "ОК".

    Choose Category data source type

    Совет

    Если здесь нет типов источников данных, убедитесь, что Product.csон добавлен в проект и ProductsContext.cs решение создано. Category.cs

  4. Теперь раскрывающийся список "Выбор источника данных" содержит только что созданный источник данных объекта. Разверните другие источники данных, а затем выберите "Источники данных проекта" и выберите категорию.

    Choose Category data source

    Второй DataGridView будет привязан к продуктам. Однако вместо привязки к типу верхнего уровня Product она будет привязана к Products навигации из Category привязки первой DataGridView. Это означает, что при выборе категории в первом представлении продукты для этой категории будут автоматически использоваться во втором представлении.

  5. Используя глиф действия конструктора во второмDataGridView, выберите "Выбрать источник данных", а затем разверните categoryBindingSource и выберите Products.

    Choose Products data source

Настройка отображаемых элементов

По умолчанию столбец создается в DataGridView каждом свойстве привязанных типов. Кроме того, значения для каждого из этих свойств можно изменить пользователем. Однако некоторые значения, такие как значения первичного ключа, концептуально доступны только для чтения, и поэтому не следует изменять. Кроме того, некоторые свойства, такие как CategoryId свойство внешнего ключа и Category навигация, не полезны для пользователя, и поэтому их следует скрыть.

Совет

Обычно скрытие свойств первичного ключа в реальном приложении. Они остаются видимыми здесь, чтобы легко увидеть, что EF Core делает за кулисами.

  1. Щелкните правой кнопкой мыши первый DataGridView и выберите пункт "Изменить столбцы...".

    Edit DataGridView columns

  2. CategoryId Сделайте столбец, представляющий первичный ключ, только для чтения и нажмите кнопку "ОК".

    Make CategoryId column read-only

  3. Щелкните правой кнопкой мыши второй DataGridView и выберите пункт "Изменить столбцы...". ProductId Сделайте столбец доступным только для чтения и удалите CategoryCategoryId столбцы, а затем нажмите кнопку "ОК".

    Make ProductId column read-only and remove CategoryId and Category columns

Подключение Подключение в EF Core

Теперь приложению требуется небольшой объем кода для подключения EF Core к элементам управления, привязанным к данным.

  1. MainForm Откройте код, щелкнув файл правой кнопкой мыши и выбрав "Просмотреть код".

    View Code

  2. Добавьте частное поле для хранения 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 , который будет использоваться для загрузки и отслеживания изменений в продуктах и категориях, отображаемых приложением.
  • EnsureCreated вызывается для DbContext создания базы данных SQLite, если она еще не существует. Это быстрый способ создания базы данных при создании прототипов или тестировании приложений. Однако если модель изменяется, необходимо удалить базу данных, чтобы ее можно было создать еще раз. (Строка EnsureDeleted может быть незакомментирована, чтобы легко удалить и повторно создать базу данных при запуске приложения.) Вместо этого можно использовать миграции EF Core для изменения и обновления схемы базы данных без потери данных.
  • EnsureCreated также заполняет новую базу данных данными, определенными в методе ProductsContext.OnModelCreating .
  • Метод Load расширения используется для загрузки всех категорий из базы данных в DbContextбазу данных. Теперь эти сущности будут отслеживаться DbContextпользователем, которые будут обнаруживать любые изменения, внесенные при изменении категорий пользователем.
  • Свойство categoryBindingSource.DataSource инициализировано в категории, отслеживаемые данными DbContext. Это делается путем вызова Local.ToBindingList()CategoriesDbSet свойства. Local предоставляет доступ к локальному представлению отслеживаемых категорий с событиями, которые подключены к локальным данным, чтобы обеспечить синхронизацию локальных данных с отображаемыми данными и наоборот. ToBindingList() предоставляет эти данные в виде IBindingListпривязки данных Windows Forms.

Метод OnClosing вызывается при закрытии формы. В настоящее время удаляется, DbContext что гарантирует, что все ресурсы базы данных будут освобождены, и dbContext поле имеет значение NULL, чтобы его нельзя было использовать снова.

Заполнение представления "Продукты"

Если приложение запущено на этом этапе, оно должно выглядеть примерно так:

Fist run of the application

Обратите внимание, что категории были загружены из базы данных, но таблица продуктов остается пустой. Кроме того, кнопка "Сохранить " не работает.

Чтобы заполнить таблицу продуктов, EF Core необходимо загрузить продукты из базы данных для выбранной категории. Чтобы достичь этого, выполните указанные ниже действия.

  1. В конструкторе основной формы выберите DataGridView категории.

  2. В разделе "Свойства" DataGridViewвыберите события (кнопка молнии) и дважды щелкните событие SelectionChanged.

    Add the SelectionChanged event

    Это создаст заглушку в основном коде формы для события, которое будет запущено всякий раз, когда изменяется выбор категории.

  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();
        }
    }
}

В этом коде, если существует активный (непустой) DbContext сеанс, мы получаем Category экземпляр, привязанный к выбранной строке текущей DataViewGridстроки. (Это может быть null , если выбрана окончательная строка в представлении, которая используется для создания новых категорий.) Если выбрана категория, необходимо загрузить продукты, DbContext связанные с этой категорией. Способы выполнения этих целей:

  • Получение экземпляра EntityEntryCategory (dbContext.Entry(category))
  • Предоставление EF Core знать, что мы хотим работать с Products навигацией по коллекции этого Category (.Collection(e => e.Products))
  • И, наконец, сообщите EF Core, что мы хотим загрузить коллекцию продуктов из базы данных (.Load();)

Совет

При Load вызове EF Core будет получать доступ только к базе данных для загрузки продуктов, если они еще не загружены.

Если приложение запущено снова, оно должно загружать соответствующие продукты всякий раз, когда выбрана категория:

Products are loaded

Сохранение изменений

Наконец, кнопка "Сохранить" может быть подключена к EF Core, чтобы все изменения, внесенные в продукты и категории, сохранялись в базе данных.

  1. В конструкторе основной формы нажмите кнопку "Сохранить ".

  2. В разделе "Свойства" Buttonвыберите события (кнопка молнии) и дважды щелкните событие Click.

    Add the Click event for Save

  3. Введите код для события:

private void buttonSave_Click(object sender, EventArgs e)
{
    this.dbContext!.SaveChanges();

    this.dataGridViewCategories.Refresh();
    this.dataGridViewProducts.Refresh();
}

Этот код вызывает SaveChangesDbContextобъект, который сохраняет все изменения, внесенные в базу данных SQLite. Если изменения не были внесены, это no-op, и вызов базы данных не выполняется. После сохранения 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 .

Перед сохранением

The running application before clicking Save

После сохранения

The running application after clicking Save

Обратите внимание, что значения первичного ключа для добавленной категории и продуктов заполняются при нажатии кнопки "Сохранить ".

Подробнее