Поделиться через


Привязка данных с помощью WPF

Важно!

Этот документ действителен только для WPF в платформа .NET Framework

В этом документе описывается привязка данных для WPF в платформа .NET Framework. Для новых проектов .NET Core рекомендуется использовать EF Core вместо Entity Framework 6. Документация по привязке данных в EF Core приведена здесь: начало работы с WPF.

В этом пошаговом руководстве показано, как привязать типы POCO к элементам управления WPF в форме master-detail. Приложение использует API-интерфейсы Entity Framework для заполнения объектов данными из базы данных, трассировки изменений и сохранения данных в базе данных.

Модель определяет два типа, которые участвуют в отношениях "один ко многим": категория (субъект\master) и Product (зависимые\детали). Затем средства Visual Studio используются для привязки типов, определенных в модели, к элементам управления WPF. Платформа привязки данных WPF обеспечивает навигацию между связанными объектами: выбор строк в главном представлении приводит к обновлению представления сведений с соответствующими дочерними данными.

Снимки экрана и описания кода в этом пошаговом руководстве взяты из Visual Studio 2013, но вы можете выполнить это пошаговое руководство с помощью Visual Studio 2012 или Visual Studio 2010.

Использование параметра Object для создания источников данных WPF

В предыдущей версии Entity Framework мы использовали параметр "База данных " при создании нового источника данных на основе модели, созданной с помощью конструктора EF. Это связано с тем, что конструктор создаст контекст, производный от ObjectContext и классов сущностей, производных от EntityObject. Использование параметра "База данных" поможет вам создать лучший код для взаимодействия с этой поверхностью API.

Конструкторы EF для Visual Studio 2012 и Visual Studio 2013 создают контекст, производный от DbContext вместе с простыми классами сущностей POCO. В Visual Studio 2010 рекомендуется переключение на шаблон создания кода, использующий DbContext, как описано далее в этом пошаговом руководстве.

При использовании поверхности API DbContext следует использовать параметр Object при создании нового источника данных, как показано в этом пошаговом руководстве.

При необходимости можно отменить изменения в создание кода на основе ObjectContext для моделей, созданных с помощью конструктора EF.

Предварительные требования

Для выполнения этого пошагового руководства необходимо установить Visual Studio 2013, Visual Studio 2012 или Visual Studio 2010.

Если вы используете Visual Studio 2010, также необходимо установить NuGet. Дополнительные сведения см. в разделе "Установка NuGet".  

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

  • Запустите Visual Studio
  • Файл —> Новый —> Project....
  • Выберите Windows на левой панели и WPFApplication в правой области
  • Введите WPFwithEFSample в качестве имени
  • Выберите ОК

Установка пакета NuGet Entity Framework

  • В Обозреватель решений щелкните правой кнопкой мыши проект WinFormswithEFSample
  • Выберите " Управление пакетами NuGet...
  • В диалоговом окне "Управление пакетами NuGet" выберите вкладку "Интернет" и выберите пакет EntityFramework
  • Щелкните Установить.

    Примечание.

    Помимо сборки EntityFramework также добавляется ссылка на System.ComponentModel.DataAnnotations. Если у проекта есть ссылка на System.Data.Entity, она будет удалена при установке пакета EntityFramework. Сборка System.Data.Entity больше не используется для приложений Entity Framework 6.

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

В этом пошаговом руководстве вы можете реализовать модель с помощью code First или EF Designer. Выполните один из двух следующих разделов.

Вариант 1. Определение модели с помощью кода в первую очередь

В этом разделе показано, как создать модель и связанную с ней базу данных с помощью Code First. Перейдите к следующему разделу (вариант 2. Определение модели с помощью базы данных first) если вы предпочитаете использовать базу данных first для обратной инженерии модели из базы данных с помощью конструктора EF

При использовании разработки Code First вы обычно начинаете с написания платформа .NET Framework классов, определяющих концептуальную модель (домен).

  • Добавьте новый класс в WPFwithEFSample:
    • Щелкните правой кнопкой мыши имя проекта
    • Выберите " Добавить", а затем "Создать элемент"
    • Выберите класс и введите Product для имени класса
  • Замените определение класса Product следующим кодом:
    namespace WPFwithEFSample
    {
        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 со следующим определением:
    using System.Collections.ObjectModel;

    namespace WPFwithEFSample
    {
        public class Category
        {
            public Category()
            {
                this.Products = new ObservableCollection<Product>();
            }

            public int CategoryId { get; set; }
            public string Name { get; set; }

            public virtual ObservableCollection<Product> Products { get; private set; }
        }
    }

Свойство Products класса Category и свойство Category класса Product — это свойства навигации. В Entity Framework свойства навигации обеспечивают возможность навигации по связям между двумя типами сущностей.

В дополнение к определению сущностей необходимо определить класс, производный от DbContext и предоставляющий свойства DbSet<TEntity>. Свойства DbSet<TEntity> позволяют контексту определить, какие типы необходимо включить в модель.

Экземпляр производного типа DbContext управляет объектами сущностей во время выполнения, в частности заполняет объекты данными из базы данных, отслеживает изменения и сохраняет данные в базе данных.

  • Добавьте новый класс ProductContext в проект со следующим определением:
    using System.Data.Entity;

    namespace WPFwithEFSample
    {
        public class ProductContext : DbContext
        {
            public DbSet<Category> Categories { get; set; }
            public DbSet<Product> Products { get; set; }
        }
    }

Скомпилируйте проект.

Вариант 2. Определение модели с помощью базы данных First

В этом разделе показано, как использовать базу данных First для обратного инженера модели из базы данных с помощью конструктора EF. Если вы завершили предыдущий раздел (вариант 1. Определение модели с помощью кода first), пропустите этот раздел и перейдите прямо к разделу "Отложенная загрузка ".

Создание существующей базы данных

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

Сервер базы данных, установленный с Visual Studio, отличается в зависимости от установленной версии Visual Studio:

  • Если вы используете Visual Studio 2010, вы создадите базу данных SQL Express.
  • Если вы используете Visual Studio 2012, вы создадите базу данных LocalDB .

Давайте пойдем вперед и создадим базу данных.

  • Представление —> Обозреватель сервера

  • Щелкните правой кнопкой мыши данные Подключение ions—> добавьте Подключение ion...

  • Если вы не подключились к базе данных из сервера Обозреватель, прежде чем выбрать Microsoft SQL Server в качестве источника данных

    Change Data Source

  • Подключение в LocalDB или SQL Express, в зависимости от того, какой вы установили, и введите Продукты в качестве имени базы данных

    Add Connection LocalDB

    Add Connection Express

  • Нажмите кнопку "ОК ", и вам будет предложено создать новую базу данных, нажмите кнопку "Да"

    Create Database

  • Новая база данных появится на сервере Обозреватель, щелкните его правой кнопкой мыши и выберите новый запрос

  • Скопируйте следующий SQL в новый запрос, а затем щелкните правой кнопкой мыши запрос и выберите "Выполнить".

    CREATE TABLE [dbo].[Categories] (
        [CategoryId] [int] NOT NULL IDENTITY,
        [Name] [nvarchar](max),
        CONSTRAINT [PK_dbo.Categories] PRIMARY KEY ([CategoryId])
    )

    CREATE TABLE [dbo].[Products] (
        [ProductId] [int] NOT NULL IDENTITY,
        [Name] [nvarchar](max),
        [CategoryId] [int] NOT NULL,
        CONSTRAINT [PK_dbo.Products] PRIMARY KEY ([ProductId])
    )

    CREATE INDEX [IX_CategoryId] ON [dbo].[Products]([CategoryId])

    ALTER TABLE [dbo].[Products] ADD CONSTRAINT [FK_dbo.Products_dbo.Categories_CategoryId] FOREIGN KEY ([CategoryId]) REFERENCES [dbo].[Categories] ([CategoryId]) ON DELETE CASCADE

Модель обратного инженера

Мы будем использовать конструктор Entity Framework, который входит в состав Visual Studio, чтобы создать нашу модель.

  • Проект —> добавление нового элемента...

  • Выберите данные из меню слева, а затем ADO.NET модель данных сущности

  • Введите ProductModel в качестве имени и нажмите кнопку "ОК"

  • Откроется мастер модели данных сущностей

  • Выберите " Создать из базы данных " и нажмите кнопку "Далее"

    Choose Model Contents

  • Выберите подключение к базе данных, созданной в первом разделе, введите ProductContext в качестве имени строка подключения и нажмите кнопку "Далее".

    Choose Your Connection

  • Щелкните поле проверка box рядом с "Таблицы", чтобы импортировать все таблицы и нажмите кнопку "Готово"

    Choose Your Objects

После завершения процесса обратного инженера в проект добавляется новая модель и открывается для просмотра в конструкторе Entity Framework. Файл app.config также был добавлен в проект с сведениями о подключении для базы данных.

Дополнительные шаги в Visual Studio 2010

Если вы работаете в Visual Studio 2010, необходимо обновить конструктор EF для использования создания кода EF6.

  • Щелкните правой кнопкой мыши пустое место модели в конструкторе EF и выберите пункт "Добавить элемент создания кода".
  • Выберите онлайн-шаблоны в меню слева и найдите DbContext
  • Выберите генератор DBContext ef 6.x для C#, введите ProductsModel в качестве имени и нажмите кнопку "Добавить".

Обновление создания кода для привязки данных

EF создает код из модели с помощью шаблонов T4. Шаблоны, поставляемые с Visual Studio или скачанные из коллекции Visual Studio, предназначены для общего использования. Это означает, что сущности, созданные из этих шаблонов, имеют простые свойства ICollection<T> . Однако при привязке данных с помощью WPF желательно использовать ObservableCollection для свойств коллекции, чтобы WPF мог отслеживать изменения, внесенные в коллекции. Для этого мы изменим шаблоны для использования ObservableCollection.

  • Откройте Обозреватель решений и найдите файл ProductModel.edmx

  • Найдите файл ProductModel.tt, который будет вложен в файл ProductModel.edmx

    WPF Product Model Template

  • Дважды щелкните файл ProductModel.tt, чтобы открыть его в редакторе Visual Studio

  • Найдите и замените два вхождения "ICollection" на "ObservableCollection". Они расположены примерно в строках 296 и 484.

  • Найдите и замените первое вхождение HashSet на "ObservableCollection". Это вхождение находится примерно в строке 50. Не заменяйте второе вхождение HashSet позже в коде.

  • Найдите и замените единственное вхождение System.Collections.Generic на System.Collections.ObjectModel. Это находится примерно в строке 424.

  • Сохраните файл ProductModel.tt. Это должно привести к повторному создании кода сущностей. Если код не создается автоматически, щелкните правой кнопкой мыши ProductModel.tt и выберите "Запустить настраиваемое средство".

Если открыть файл Category.cs (вложенный в ProductModel.tt), то вы увидите, что коллекция Products имеет тип ObservableCollection<Product>.

Скомпилируйте проект.

Отложенная загрузка

Свойство Products класса Category и свойство Category класса Product — это свойства навигации. В Entity Framework свойства навигации обеспечивают возможность навигации по связям между двумя типами сущностей.

EF позволяет автоматически загружать связанные сущности из базы данных при первом доступе к свойству навигации. В случае использования такого типа загрузки (называемой отложенной) следует помнить о следующем: если содержимое еще отсутствует в контексте, при первом обращении к каждому свойству навигации будет выполняться отдельный запрос базы данных.

При использовании типов сущностей POCO EF достигает отложенной загрузки путем создания экземпляров производных типов прокси-серверов во время выполнения, а затем переопределения виртуальных свойств в классах для добавления перехватчика загрузки. Чтобы получить отложенную загрузку связанных объектов, необходимо объявить методы получения свойств навигации как общедоступные и виртуальные (переопределяемые в Visual Basic). При этом класс не должен быть запечатанным (непереопределяемым в Visual Basic). При использовании свойств навигации "Первая база данных" автоматически создаются виртуальными, чтобы включить отложенную загрузку. В разделе Code First мы решили сделать свойства навигации виртуальными по той же причине.

Привязка объекта к элементам управления

Добавьте классы, определенные в модели в качестве источников данных для этого приложения WPF.

  • Дважды щелкните файл MainWindow.xaml в Обозревателе решений, чтобы открыть главную форму.

  • В главном меню выберите "Проект "> Добавить новый источник данных ... (в Visual Studio 2010 необходимо выбрать данные —> добавить новый источник данных...)

  • В поле "Выбор типа источника данных" выберите "Объект " и нажмите кнопку "Далее"

  • В диалоговом окне "Выбор объектов данных" разверните wpFwithEFSample два раза и выберите категорию
    Нет необходимости выбрать источник данных Product, так как мы вернемся к нему через свойство Product в источнике данных категории

    Select Data Objects

  • Нажмите кнопку Готово.

  • Окно "Источники данных" открывается рядом с окном MainWindow.xaml, если окно "Источники данных" не отображается, выберите "Вид "> Другие источники данных Windows-> Источники данных"

  • Нажмите значок закрепления, поэтому окно источников данных не скрывается автоматически. Возможно, потребуется нажать кнопку обновления, если окно уже видно.

    Data Sources

  • Выберите источник данных категории и перетащите его в форму.

При перетаскивании этого источника произошло следующее:

  • Ресурс categoryViewSource и элемент управления categoryDataGrid были добавлены в XAML
  • Для свойства DataContext в родительском элементе Grid задано значение "{StaticResource categoryViewSource }". Ресурс categoryViewSource служит источником привязки для внешнего элемента Сетки\parent Grid. Затем внутренние элементы Grid наследуют значение DataContext из родительской сетки (свойство ItemsSource объекта CategoryDataGrid имеет значение "{Binding}")
    <Window.Resources>
        <CollectionViewSource x:Key="categoryViewSource"
                                d:DesignSource="{d:DesignInstance {x:Type local:Category}, CreateList=True}"/>
    </Window.Resources>
    <Grid DataContext="{StaticResource categoryViewSource}">
        <DataGrid x:Name="categoryDataGrid" AutoGenerateColumns="False" EnableRowVirtualization="True"
                    ItemsSource="{Binding}" Margin="13,13,43,191"
                    RowDetailsVisibilityMode="VisibleWhenSelected">
            <DataGrid.Columns>
                <DataGridTextColumn x:Name="categoryIdColumn" Binding="{Binding CategoryId}"
                                    Header="Category Id" Width="SizeToHeader"/>
                <DataGridTextColumn x:Name="nameColumn" Binding="{Binding Name}"
                                    Header="Name" Width="SizeToHeader"/>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>

Добавление таблицы сведений

Теперь, когда у нас есть сетка для отображения категорий, давайте добавим сетку сведений для отображения связанных продуктов.

  • Выберите свойство Products из источника данных категории и перетащите его в форму.
    • Ресурс categoryProductsViewSource и сетка productDataGrid добавляются в XAML
    • Путь привязки для этого ресурса имеет значение Products
    • Платформа привязки данных WPF гарантирует, что в productDataGrid отображаются только продукты, связанные с выбранной категорией.
  • Из панели элементов перетащите кнопку в форму. Задайте свойству Name значение buttonSave и свойству Content значение Save.

Форма должна выглядеть примерно так:

Designer Form

Добавление кода, обрабатывающего взаимодействие с данными

Теперь следует добавить в главное окно несколько обработчиков событий.

  • В окне XAML щелкните <элемент Window , выберите главное окно.

  • В окне "Свойства" выберите события в правом верхнем углу, а затем дважды щелкните текстовое поле справа от загруженной метки

    Main Window Properties

  • Кроме того, добавьте событие click для кнопки "Сохранить", дважды нажав кнопку "Сохранить " в конструкторе.

При этом вы перейдете к коду для формы, теперь мы изменим код, чтобы использовать ProductContext для выполнения доступа к данным. Обновите код для MainWindow, как показано ниже.

Код объявляет длительный экземпляр ProductContext. Объект ProductContext используется для запроса и сохранения данных в базе данных. Затем Метод Dispose() экземпляра ProductContext вызывается из переопределенного метода OnClosing . Примечания кода содержат сведения о том, что делает код.

    using System.Data.Entity;
    using System.Linq;
    using System.Windows;

    namespace WPFwithEFSample
    {
        public partial class MainWindow : Window
        {
            private ProductContext _context = new ProductContext();
            public MainWindow()
            {
                InitializeComponent();
            }

            private void Window_Loaded(object sender, RoutedEventArgs e)
            {
                System.Windows.Data.CollectionViewSource categoryViewSource =
                    ((System.Windows.Data.CollectionViewSource)(this.FindResource("categoryViewSource")));

                // Load is an extension method on IQueryable,
                // defined in the System.Data.Entity namespace.
                // This method enumerates the results of the query,
                // similar to ToList but without creating a list.
                // When used with Linq to Entities this method
                // creates entity objects and adds them to the context.
                _context.Categories.Load();

                // After the data is loaded call the DbSet<T>.Local property
                // to use the DbSet<T> as a binding source.
                categoryViewSource.Source = _context.Categories.Local;
            }

            private void buttonSave_Click(object sender, RoutedEventArgs e)
            {
                // When you delete an object from the related entities collection
                // (in this case Products), the Entity Framework doesn’t mark
                // these child entities as deleted.
                // Instead, it removes the relationship between the parent and the child
                // by setting the parent reference to null.
                // So we manually have to delete the products
                // that have a Category reference set to null.

                // The following code uses LINQ to Objects
                // against the Local collection of Products.
                // The ToList call is required because otherwise the collection will be modified
                // by the Remove call while it is being enumerated.
                // In most other situations you can use LINQ to Objects directly
                // against the Local property without using ToList first.
                foreach (var product in _context.Products.Local.ToList())
                {
                    if (product.Category == null)
                    {
                        _context.Products.Remove(product);
                    }
                }

                _context.SaveChanges();
                // Refresh the grids so the database generated values show up.
                this.categoryDataGrid.Items.Refresh();
                this.productsDataGrid.Items.Refresh();
            }

            protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
            {
                base.OnClosing(e);
                this._context.Dispose();
            }
        }

    }

Тестирование приложения WPF

  • Скомпилируйте и запустите приложение. При использовании кода сначала вы увидите, что для вас создается база данных WPFwithEFSample.ProductContext .

  • Введите имя категории в верхней сетке и названия продуктов в нижней сетке Не введите ничего в столбцах идентификатора, так как первичный ключ создается базой данных.

    Main Window with new categories and products

  • Нажмите кнопку "Сохранить", чтобы сохранить данные в базе данных

После вызова DbContext SaveChanges()идентификаторы заполняются созданными значениями базы данных. Так как после SaveChanges() элементы управления DataGrid обновляются с новыми значениями.

Main Window with IDs populated

Дополнительные ресурсы

Дополнительные сведения о привязке данных к коллекциям с помощью WPF см . в этой статье в документации ПО WPF.