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