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

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

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

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

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

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

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

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

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

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

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

    Примечание.

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

Реализация IListSource для коллекций

Свойства коллекции должны реализовать интерфейс IListSource, чтобы включить двусторонняя привязка данных с сортировкой при использовании Windows Forms. Для этого мы добавим функцию IListSource для расширения ObservableCollection.

  • Добавьте класс ObservableListSource в проект:
    • Щелкните правой кнопкой мыши имя проекта
    • Выбор " Добавить" —> новый элемент
    • Выберите класс и введите ObservableListSource для имени класса
  • Замените код, созданный по умолчанию, следующим кодом:

Этот класс включает двусторонняя привязка данных, а также сортировку. Класс является производным от ObservableCollection<T> и добавляет явную реализацию IListSource. Метод GetList() IListSource реализуется для возврата реализации IBindingList, которая остается в синхронизации с ObservableCollection. Реализация IBindingList, созданная ToBindingList, поддерживает сортировку. Метод расширения ToBindingList определен в сборке EntityFramework.

    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Diagnostics.CodeAnalysis;
    using System.Data.Entity;

    namespace WinFormswithEFSample
    {
        public class ObservableListSource<T> : ObservableCollection<T>, IListSource
            where T : class
        {
            private IBindingList _bindingList;

            bool IListSource.ContainsListCollection { get { return false; } }

            IList IListSource.GetList()
            {
                return _bindingList ?? (_bindingList = this.ToBindingList());
            }
        }
    }

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

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

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

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

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

  • Добавление нового класса Product в проект
  • Замените код, созданный по умолчанию, следующим кодом:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;

    namespace WinFormswithEFSample
    {
        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;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;

    namespace WinFormswithEFSample
    {
        public class Category
        {
            private readonly ObservableListSource<Product> _products =
                    new ObservableListSource<Product>();

            public int CategoryId { get; set; }
            public string Name { get; set; }
            public virtual ObservableListSource<Product> Products { get { return _products; } }
        }
    }

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

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

  • Добавьте в проект новый класс ProductContext .
  • Замените код, созданный по умолчанию, следующим кодом:
    using System;
    using System.Collections.Generic;
    using System.Data.Entity;
    using System.Linq;
    using System.Text;

    namespace WinFormswithEFSample
    {
        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> . Однако при привязке данных желательно иметь свойства коллекции, реализующие IListSource. Поэтому мы создали выше класс ObservableListSource, и теперь мы изменим шаблоны, чтобы использовать этот класс.

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

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

    Product Model Template

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    Data Source

  • Нажмите кнопку "Готово". Если окно "Источники данных" не отображается, выберите "Вид" —> другие источники данных Windows->

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

    Data Source 2

  • В Обозреватель решений дважды щелкните файл Form1.cs, чтобы открыть основную форму в конструкторе.

  • Выберите источник данных категории и перетащите его в форму. По умолчанию в конструктор добавляются новые элементы управления DataGridView (categoryDataGridView) и панели инструментов навигации. Эти элементы управления привязаны к компонентам BindingSource (categoryBindingSource) и Binding Navigator (categoryBindingNavigator), созданным также.

  • Измените столбцы в categoryDataGridView. Мы хотим задать для столбца CategoryId значение "Только для чтения". Значение свойства CategoryId создается базой данных после сохранения данных.

    • Щелкните правой кнопкой мыши элемент управления DataGridView и выберите пункт "Изменить столбцы".
    • Выберите столбец CategoryId и задайте значение ReadOnly true
    • Нажмите кнопку ОК
  • Выберите продукты из источника данных категории и перетащите его в форму. ProductDataGridView и productBindingSource добавляются в форму.

  • Измените столбцы в productDataGridView. Мы хотим скрыть столбцы CategoryId и Category и задать ProductId только для чтения. Значение свойства ProductId создается базой данных после сохранения данных.

    • Щелкните правой кнопкой мыши элемент управления DataGridView и выберите "Изменить столбцы...".
    • Выберите столбец ProductId и задайте значение ReadOnly true.
    • Выберите столбец CategoryId и нажмите кнопку "Удалить". Выполните то же самое с столбцом "Категория ".
    • Нажмите кнопку ОК.

    До сих пор мы связываем элементы управления DataGridView с компонентами BindingSource в конструкторе. В следующем разделе мы добавим код в код позади, чтобы задать categoryBindingSource.DataSource коллекцию сущностей, которые в настоящее время отслеживаются DbContext. Когда мы перетаскивали продукты из категории, WinForms позаботились о настройке свойства ProductsBindingSource.DataSource в категорииBindingSource и productsBindingSource.DataMember. Из-за этой привязки в productDataGridView будут отображаться только продукты, принадлежащие выбранной в данный момент категории.

  • Включите кнопку "Сохранить" на панели инструментов навигации, нажав правую кнопку мыши и выбрав "Включено".

    Form 1 Designer

  • Добавьте обработчик событий для кнопки сохранения, дважды щелкнув кнопку. Это добавит обработчик событий и приведет вас к коду формы. Код обработчика событий categoryBindingNavigatorSaveItem_Click будет добавлен в следующем разделе.

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

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

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

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows.Forms;
    using System.Data.Entity;

    namespace WinFormswithEFSample
    {
        public partial class Form1 : Form
        {
            ProductContext _context;
            public Form1()
            {
                InitializeComponent();
            }

            protected override void OnLoad(EventArgs e)
            {
                base.OnLoad(e);
                _context = new ProductContext();

                // Call the Load method to get the data for the given DbSet
                // from the database.
                // The data is materialized as entities. The entities are managed by
                // the DbContext instance.
                _context.Categories.Load();

                // Bind the categoryBindingSource.DataSource to
                // all the Unchanged, Modified and Added Category objects that
                // are currently tracked by the DbContext.
                // Note that we need to call ToBindingList() on the
                // ObservableCollection<TEntity> returned by
                // the DbSet.Local property to get the BindingList<T>
                // in order to facilitate two-way binding in WinForms.
                this.categoryBindingSource.DataSource =
                    _context.Categories.Local.ToBindingList();
            }

            private void categoryBindingNavigatorSaveItem_Click(object sender, EventArgs e)
            {
                this.Validate();

                // Currently, the Entity Framework doesn’t mark the entities
                // that are removed from a navigation property (in our example the Products)
                // as deleted in the context.
                // The following code uses LINQ to Objects against the Local collection
                // to find all products and marks any that do not have
                // a Category reference as deleted.
                // 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 do 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);
                    }
                }

                // Save the changes to the database.
                this._context.SaveChanges();

                // Refresh the controls to show the values         
                // that were generated by the database.
                this.categoryDataGridView.Refresh();
                this.productsDataGridView.Refresh();
            }

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

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

  • Скомпилируйте и запустите приложение, и вы можете протестировать функциональные возможности.

    Form 1 Before Save

  • После сохранения созданных магазином ключей отображаются на экране.

    Form 1 After Save

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

    Server Object Explorer