В этом пошаговом руководстве показано, как привязать типы POCO к элементам управления WPF в форме "Основное — сведения". Приложение использует API-интерфейсы Entity Framework для заполнения объектов данными из базы данных, трассировки изменений и сохранения данных в базе данных.
Модель определяет два типа, которые участвуют в связи "один ко многим": Категория (субъект\главный) и Продукт (зависимый\детали). Платформа привязки данных WPF обеспечивает навигацию между связанными объектами: выбор строк в главном представлении приводит к обновлению представления сведений с соответствующими дочерними данными.
Снимки экрана и листинги кода в этом пошаговом руководстве взяты из Visual Studio 2019 16.6.5.
Совет
Вы можете скачать используемый в этой статье пример из репозитория GitHub.
Предварительные требования
Для выполнения этого пошагового руководства необходимо установить Visual Studio 2019 16.3 или более поздней версии и выбрать при этом рабочую нагрузку для классических приложений .NET. Дополнительные сведения об установке новейшей версии Visual Studio см. в статье Установка Visual Studio.
Создание приложения
Запустите Visual Studio
На начальном экране выберите Создать проект.
Выполните поиск по запросу "WPF", выберите в результатах Приложение WPF (.NET Core) и нажмите кнопку Далее.
На следующем экране присвойте проекту имя, например GetStartedWPF, и щелкните Создать.
Установка пакета NuGet для Entity Framework
Щелкните правой кнопкой мыши решение и выберите Управление пакетами NuGet для решения...
Установите флажок рядом с проектом в области справа и нажмите кнопку Установить.
Повторите шаги для поиска entityframeworkcore.proxies и установите Microsoft.EntityFrameworkCore.Proxies.
Примечание
При установке пакета SQLite автоматически извлекается связанный базовый пакет Microsoft.EntityFrameworkCore. Пакет Microsoft.EntityFrameworkCore.Proxies обеспечивает поддержку "отложенной загрузки" данных. Это означает, что при наличии сущностей с дочерними объектами в процессе начальной нагрузки извлекаются только родительские сущности. Прокси обнаруживают попытку доступа к дочерним сущностям и автоматически загружает их по требованию.
Определение модели
В этом пошаговом руководстве вы реализуете модель с использованием подхода, в котором акцент делается на коде. Это означает, что 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>();
}
}
Свойство Products класса Category и свойство Category класса Product — это свойства навигации. В Entity Framework свойства навигации обеспечивают возможность навигации по связям между двумя типами сущностей.
В дополнение к определению сущностей необходимо определить класс, производный от 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 сообщает EF Core, какие сущности C# следует сопоставить с базой данных.
DbContext для EF Core можно настроить множеством способов. Их можно прочитать в: настройка DbContext.
Чтобы задать файл данных SQLite, в этом примере используется переопределение OnConfiguring.
Вызов UseLazyLoadingProxies указывает EF Core, что необходимо реализовать отложенную загрузку, благодаря которой дочерние сущности автоматически загружаются при доступе из родительской сущности.
Нажмите клавиши CTRL+SHIFT+B или последовательно выберите Сборка> Собрать решение для компиляции проекта.
Совет
Сведения о том, как сохранить базу данных и модели EF Core в синхронизации: управление схемами баз данных.
Отложенная загрузка
Свойство Products класса Category и свойство Category класса Product — это свойства навигации. В Entity Framework Core свойства навигации обеспечивают возможность навигации по связям между двумя типами сущностей.
EF Core обеспечивает возможность автоматической загрузки связанных сущностей из базы данных при первом доступе к свойству навигации. В случае использования такого типа загрузки (называемой отложенной) следует помнить о следующем: если содержимое еще отсутствует в контексте, при первом обращении к каждому свойству навигации будет выполняться отдельный запрос базы данных.
При использовании типов сущностей POCO решение EF Core обеспечивает отложенную загрузку, создавая во время выполнения экземпляры производных прокси-типов, а затем переопределяя виртуальные свойства в классах для добавления обработчика загрузки. Чтобы получить отложенную загрузку связанных объектов, необходимо объявить методы получения свойств навигации как общедоступные и виртуальные (переопределяемые в Visual Basic). При этом класс не должен быть запечатанным (непереопределяемым в Visual Basic). При использовании Database First свойства навигации автоматически становятся виртуальными для включения возможности отложенной загрузки.
Привязка объекта к элементам управления
Добавьте классы, определенные в модели в качестве источников данных для этого приложения WPF.
Дважды щелкните файл MainWindow.xaml в Обозревателе решений, чтобы открыть главную форму.
Перейдите на вкладку XAML, чтобы изменить код XAML.
Сразу после открывающего тега Window добавьте следующие источники для подключения к сущностям EF Core.
Обратите внимание, что для свойства CategoryId задано значение ReadOnly, поскольку оно назначается базой данных и не может быть изменено.
Добавление таблицы сведений
Теперь, когда существует таблица для отображения категорий, можно добавить таблицу сведений для отображения продуктов. Добавьте его внутри элемента Grid после элемента категорий DataGrid.
Представление конструктора должно выглядеть следующим образом:
Добавление кода, обрабатывающего взаимодействие с данными
Теперь следует добавить в главное окно несколько обработчиков событий.
В окне XAML щелкните элемент <Window>, чтобы выбрать главное окно.
В окне Свойства в правом верхнем углу выберите События, а затем дважды щелкните текстовое поле справа от метки Загружен.
В результате вы перейдете к коду программной части формы, который теперь можно изменить, чтобы использовать ProductContext для доступа к данным. Обновите код, как показано ниже.
Код объявляет длительный экземпляр ProductContext. Объект ProductContext используется для запроса и сохранения данных в базе данных. Затем из переопределенного метода OnClosing вызывается метод Dispose() для экземпляра ProductContext. Комментарии к коду объясняют, что делает каждый шаг.
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);
}
}
}
Примечание
Для создания базы данных при первом запуске в коде используется вызов метода EnsureCreated(). Это приемлемо для демонстраций, но в рабочих приложениях следует изучить возможности миграций для управления схемой. Код также выполняется синхронно, так как он использует локальную базу данных SQLite. Для сценариев в рабочей среде, которые обычно предполагают использование удаленного сервера, рекомендуется применять асинхронные версии методов Load и SaveChanges.
Тестирование приложения WPF
Скомпилируйте и запустите приложение, нажав клавишу F5 или выбрав Отладка> Начать отладку. База данных должна быть создана автоматически и сохранена в файле с именем products.db. Введите имя категории и нажмите клавишу ВВОД, а затем добавьте продукты в нижнюю таблицу. Нажмите кнопку "Сохранить" и посмотрите, обновилась ли таблица с учетом указанных идентификаторов базы данных. Выделите строку и нажмите Удалить, чтобы ее удалить. Сущность будет удалена при нажатии кнопки Сохранить.
Уведомление об изменении свойства
Этот пример предполагает выполнение четырех шагов для синхронизации сущностей с пользовательским интерфейсом.
При начальном вызове _context.Categories.Load() загружаются данные категорий.
Прокси отложенной загрузки загружают данные зависимых продуктов.
Встроенная функция отслеживания изменений EF Core вносит необходимые изменения в сущности, в том числе вставки и удаления, при вызове метода _context.SaveChanges().
Вызовы DataGridView.Items.Refresh() приводят к принудительной перезагрузке с вновь созданными идентификаторами.
Это подходит для нашего примера для начала работы, в других сценариях может потребоваться дополнительный код. Элементы управления WPF преобразовывают для просмотра пользовательский интерфейс, считывая поля и свойства сущностей. При изменении значения в пользовательском интерфейсе это значение передается в сущность. При изменении значения свойства непосредственно в сущности, например при его загрузке из базы данных, WPF не отражает эти изменения сразу же в пользовательском интерфейсе. Подсистема преобразования для просмотра должна быть уведомлена об изменениях. В данном проекте это выполняется путем вызова Refresh() вручную. Такое уведомление можно легко автоматизировать, реализовав интерфейс INotifyPropertyChanged. Компоненты WPF будут автоматически обнаруживать интерфейс и регистрироваться для получения событий изменения. За создание таких событий отвечает сущность.
Совет
Дополнительные сведения об обработке изменений см. в статье о реализации уведомления об изменении свойств.
Источник этого содержимого можно найти на GitHub, где также можно создавать и просматривать проблемы и запросы на вытягивание. Дополнительные сведения см. в нашем руководстве для участников.
Отзыв о .NET
.NET — это проект с открытым исходным кодом. Выберите ссылку, чтобы оставить отзыв:
Создайте пользовательский интерфейс с привязкой данных. Пользовательский интерфейс автоматически обновляется на основе последних данных, а данные обновляются в ответ на изменения в пользовательском интерфейсе.