Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В этом пошаговом руководстве показано, как привязать типы POCO к элементам управления Window Forms (WinForms) в форме master-detail. Приложение использует Entity Framework для заполнения объектов данными из базы данных, отслеживания изменений и сохранения данных в базе данных.
Модель определяет два типа, которые участвуют в отношениях "один ко многим": категория (главный\основной) и продукт (зависимый\подробность). Затем средства 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 с сортировкой. Для этого мы собираемся расширить ObservableCollection, добавив функциональность IListSource.
- Добавьте класс 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: Определение модели с использованием подхода Database First), если вы предпочитаете использовать Database 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
В этом разделе показано, как использовать подход Database First для обратного проектирования модели из базы данных с помощью дизайнера EF. Если вы завершили предыдущий раздел (вариант 1. Определение модели с помощью кода first), пропустите этот раздел и перейдите прямо к разделу " Отложенная загрузка ".
Создание существующей базы данных
Обычно при выборе существующей базы данных она уже будет создана, но для этого пошагового руководства необходимо создать базу данных для доступа.
Сервер базы данных, установленный с Visual Studio, отличается в зависимости от установленной версии Visual Studio:
- Если вы используете Visual Studio 2010, вы создадите базу данных SQL Express.
- Если вы используете Visual Studio 2012, вы создадите базу данных LocalDB .
Давайте пойдем вперед и создадим базу данных.
Вид —> Обозреватель серверов
Щелкните правой кнопкой мыши Подключения к данным — Добавить подключение...
Если вы ранее не подключались к базе данных из обозревателя серверов, вам нужно будет выбрать Microsoft SQL Server в качестве источника данных.
Подключитесь к LocalDB или SQL Express, в зависимости от того, какой вы установили, и введите "Продукты " в качестве имени базы данных.
Нажмите кнопку "ОК ", и вам будет предложено создать новую базу данных, нажмите кнопку "Да"
Новая база данных появится в обозревателе серверов, щелкните его правой кнопкой мыши и выберите новый запрос
Скопируйте следующий 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 в качестве имени и нажмите кнопку "ОК"
Откроется Мастер создания модели данных сущностей
Выберите "Создать из базы данных" и нажмите кнопку "Далее"
Выберите подключение к базе данных, созданной в первом разделе, введите ProductContext в качестве имени строки подключения и нажмите кнопку "Далее".
Установите флажок рядом с "Таблицы", чтобы импортировать все таблицы и нажмите кнопку "Готово"
После завершения процесса реверс-инжиниринга новая модель добавляется в ваш проект и открывается в конструкторе 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
Дважды щелкните файл 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). При использовании подхода Database First навигационные свойства автоматически делаются виртуальными, чтобы включить отложенную загрузку. В разделе Code First мы решили сделать свойства навигации виртуальными по той же причине
Привязка объекта к элементам управления
Добавьте классы, определенные в модели в качестве источников данных для этого приложения WinForms.
В главном меню выберите "Проект "> Добавить новый источник данных ... (в Visual Studio 2010 необходимо выбрать данные —> добавить новый источник данных...)
В окне "Выбор типа источника данных" выберите "Объект" и нажмите кнопку "Далее"
В диалоговом окне "Выбор объектов данных" разверните WinFormswithEFSample два раза и выберите Категория. Не нужно выбирать источник данных Product, так как мы вернемся к нему через свойство Product в источнике данных Категория.
Нажмите кнопку "Готово". Если окно "Источники данных" не отображается, выберите "Вид" —> другие источники данных Windows->
Нажмите значок булавки, чтобы окно источников данных не скрывалось автоматически. Возможно, потребуется нажать кнопку обновления, если окно уже видно.
В обозревателе решений дважды щелкните файл 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 и задайте значение ReadOnlytrue.
- Выберите столбец CategoryId и нажмите кнопку "Удалить ". Выполните то же самое с столбцом "Категория ".
- Нажмите кнопку ОК.
До сих пор мы связываем элементы управления DataGridView с компонентами BindingSource в конструкторе. В следующем разделе мы добавим код во внутренний код, чтобы установить categoryBindingSource.DataSource на коллекцию сущностей, которые в настоящее время отслеживаются с помощью DbContext. Когда мы перетаскивали продукты из категории, WinForms позаботился о настройке свойства productsBindingSource.DataSource к categoryBindingSource и свойства productsBindingSource.DataMember к "Products". Из-за этой привязки в productDataGridView будут отображаться только продукты, принадлежащие выбранной в данный момент категории.
Включите кнопку "Сохранить " на панели инструментов навигации, нажав правую кнопку мыши и выбрав "Включено".
Добавьте обработчик событий для кнопки сохранения, дважды щелкнув кнопку. Это добавит функцию-обработчик событий и откроет код, связанный с формой. Код обработчика событий 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
Скомпилируйте и запустите приложение, и вы можете протестировать функциональные возможности.
После сохранения ключи, сгенерированные магазином, отображаются на экране.
Если вы использовали Code First, то вы также увидите, что для вас создается база данных WinFormswithEFSample.ProductContext.