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