Все необходимые для выполнения данной работы программы можно найти в прилагаемом каталоге.

Разбирайте код и экспериментируйте. Все отлажено и все работает! В непонятных местах кода задавайте вопросы или читайте книги - всего не опишешь. Программирование - это не развлечение, это работа, причем тяжелая. А иначе не научиться и не успеть. Вот, можете повеселиться...
НаноСидоркин, посмотри, какие красивые упражнения! Неужели и тебе будет скучно? Тогда мне действительно пора на пензию.
Данная лабораторная работа основана на мне-е-е и Большом Биле!
Любая БД является хранилищем данных, а ADO.NET является инструментом манипулирования содержимым БД: обеспечивает согласованное извлечение, редактирование, вставку, обновление и удаление данных с минимальными возможностями их потери или искажения.
Вставку, удаление можно делать построчно командами SQL на уровне БД. Редактирование тоже можно делать построчно, блокируя выбранные записи открытым соединением. Тогда проверка ограничений будет выполняться на уровне БД и мы вынуждены будем обрабатывать исключения самой БД.
Но более правильным будет обработка загруженных данных и проверка их целостности на уровне приложения в отсоединенном режиме с последующим сохранением сразу всех изменений в БД. В последнем случае соединение с БД произойдет только дважды: при загрузке данных в приложение и выгрузки их в БД. А вся проверка целостности данных может выполняться в приложении средствами ADO.NET. Такая стратегия называется оптимистическим параллелизмом. Она требует своих дополнительных заморочек, но обеспечивает одновременную работу с БД большого количества клиентов. В Web-программировании без нее просто не обойтись.
Файловая БД имеет свою структуру, определяемую в ее заголовке. Там содержится общая информация служебного характера, описывающая БД в целом. Обычно эту общую информацию называют схемой. В данном упражнении мы научимся извлекать из БД наиболее важные сведения, касающиеся самой БД или отдельных ее составляющих, например, таблиц или столбцов. И эти данные будем называть метаданными ( данные о данных ).
Чтобы сделать описание более кратким (и облегчить жизнь тем, кому скучно), будем стараться избегать этапа визуального проектирования, а все действия по формированию пользовательского интерфейса выполним в коде. В таком случае мне останется только привести отлаженный листинг и конечный снимок окна результата (а вам - лихо, 'по-программистски', скопировать, запустить и повеселиться...).
Создайте новое решение
с именем ADO и добавьте в него новый проект с именем WinForms1
Заполните файл WinForms1.cs следующим
кодомusing System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
// Дополнительные пространства имен
using System.Data.OleDb;
using System.Data.Common;
namespace WinForms1
{
public partial class Form1 : Form
{
// Объявили поля-ссылки для видимости в методах
TabControl tabControl;
DataTable schemaTable;
ComboBox cbCollections, cbTables;
DataGridView dataGridView1, dataGridView2;
// Конструктор формы
public Form1()
{
InitializeComponent();
this.Text = "Упражнение 1. Извлечение метаданных";
this.WindowState = FormWindowState.Maximized;
CreateControl();// Динамически создать пользовательский интерфейс
// Загрузить списки во вкладках
LoadCollections();
LoadTables();
}
// Имя метода пересекается с наследуемым (для разнообразия)
new void CreateControl()
{
// Динамическое создание элементов управления
tabControl = new TabControl();
this.Controls.Add(tabControl);
tabControl.Dock = DockStyle.Fill;
TabPage page1 = new TabPage("Коллекции");
tabControl.TabPages.Add(page1);// Добавили вкладку
TabPage page2 = new TabPage("Таблицы");
//tabControl.TabPages.Add(page2);// Добавили вкладку
page2.Parent = tabControl;// Добавили вкладку (равноценно!!!)
// Для динамического выравнивания элементов на первой вкладке
TableLayoutPanel layoutPanel1 = new TableLayoutPanel();
page1.Controls.Add(layoutPanel1);// Добавили панель к вкладке
layoutPanel1.Dock = DockStyle.Fill;
// Для динамического выравнивания элементов на второй вкладке
TableLayoutPanel layoutPanel2 = new TableLayoutPanel();
page2.Controls.Add(layoutPanel2);// Добавили панель к вкладке
layoutPanel2.Dock = DockStyle.Fill;
cbCollections = new ComboBox();
cbCollections.Dock = DockStyle.Fill;
layoutPanel1.Controls.Add(cbCollections);// Добавили список к панели
// Зарегистрировали обработчик для первого списка
cbCollections.SelectedIndexChanged += cbCollections_SelectedIndexChanged;
// Объект отображения данных
dataGridView1 = new DataGridView();// Создаем
layoutPanel1.Controls.Add(dataGridView1);// Добавили сетку к панели
dataGridView1.Dock = DockStyle.Fill;
dataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
cbTables = new ComboBox();
cbTables.Dock = DockStyle.Fill;
layoutPanel2.Controls.Add(cbTables);// Добавили список к панели
// Зарегистрировали обработчик для второго списка
cbTables.SelectedIndexChanged += cbTables_SelectedIndexChanged;
// Объект отображения данных
dataGridView2 = new DataGridView();// Создаем
layoutPanel2.Controls.Add(dataGridView2);// Добавили сетку к панели
dataGridView2.Dock = DockStyle.Fill;
dataGridView2.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
}
// Строка соединения к БД с абсолютным путем, определяемым сборкой
String ConnectionString()
{
// Используем построитель строки подключения
OleDbConnectionStringBuilder objConnectionStringBuilder =
new OleDbConnectionStringBuilder();
objConnectionStringBuilder.Provider = "Microsoft.Jet.OLEDB.4.0";
objConnectionStringBuilder.DataSource =
Application.StartupPath.ToString() + @"\Data\Northwind.mdb";
return objConnectionStringBuilder.ToString();
}
// Извлекаем в список имена всех коллекций БД
void LoadCollections()
{
using (OleDbConnection connection = new
OleDbConnection(ConnectionString()))
{
connection.Open();
schemaTable = connection.GetSchema();
}
foreach (DataRow row in schemaTable.Rows)
{
Object col = row["CollectionName"].ToString();
cbCollections.Items.Add(col);
}
// Несвязанный список, можно сортировать
cbCollections.Sorted = true;
cbCollections.SelectedIndex = 0;
}
// Заполняем список именами таблиц БД
void LoadTables()
{
// Создали, заполнили, скопировали в список и потеряли
DataTable table = new DataTable();// Локальная ссылка на объект
// Соединение закрывается после выхода из инструкции using
using (OleDbConnection connection = new
OleDbConnection(ConnectionString()))
{
connection.Open();
// Выбираем коллекцию только для таблиц
table = connection.GetOleDbSchemaTable(
OleDbSchemaGuid.Tables,
new object[] { null, null, null, "TABLE" });
}
// Значения столбца TABLE_NAME скопировали в ComboBox
foreach (DataRow row in table.Rows)
{
Object col = row["TABLE_NAME"].ToString();
cbTables.Items.Add(col);
}
// Несвязанный список, можно сортировать
cbTables.Sorted = true;
cbTables.SelectedIndex = 0;// Установили список в начало
}
// Извлекаем информацию о выделенной коллекции
void cbCollections_SelectedIndexChanged(object sender, EventArgs e)
{
schemaTable.Clear();// Очистим от предыдущего
using (OleDbConnection connection = new
OleDbConnection(ConnectionString()))
{
connection.Open();
schemaTable = connection.GetSchema(cbCollections.SelectedItem.ToString());
}
// Связываем таблицу и элемент отображения
dataGridView1.DataSource = schemaTable;
}
// Извлекаем информацию о выделенной таблице
void cbTables_SelectedIndexChanged(object sender, EventArgs e)
{
// Все ссылки стековые
String selectCommand = "";
String tableName = cbTables.SelectedItem.ToString();
if (tableName.IndexOf(' ') != -1)// Для таблицы 'Order Details'
selectCommand = "SELECT * FROM " + "[" + tableName + "]";
else
selectCommand = "SELECT * FROM " + tableName;
OleDbConnection connection = new OleDbConnection(ConnectionString());
OleDbCommand objCommand = new OleDbCommand(selectCommand, connection);
using (connection)// Заключаем в блок автоматического закрытия соединения при аварии
{
connection.Open();
// Выполняем объект DbCommand одним из его методов
OleDbDataReader dataReader = objCommand.ExecuteReader();
// Извлечь метаданные таблицы и сразу привязать для показа
dataGridView2.DataSource = dataReader.GetSchemaTable();
connection.Close();
}
}
}
}
Разберите код и запустите
приложениеМетаданные, составляющие описание всей БД, мы извлекаем через объект
соединения.
Метод GetSchemaTable() объекта OleDbDataReader возвращает
метаданные о каждом из столбцов в таблице.
Обратите внимание, что мы применили в качестве контейнеров элементов отображения
два класса: TabControl и TableLayoutPanel. Класс TabControl изолирует
в отдельных вкладках только интерфейс пользователя, но не изолирует код этих
вкладок - все объекты, составляющие вкладки, должны иметь уникальные имена
в рамках всего объекта TabControl. Класс TableLayoutPanel обеспечивает
динамическое размещение в невидымых ячейках таблицы, наподобие таблицы HTML.
Естественно, каждый контейнер имеет свои настройки, но мы воспользовались только
настройками по умолчанию.
Очень полезен следующий снимок экрана, показывающий принятые
в Access типы данных и их соответствие типам .NET. Большинство
данных в БД хранятся в символьном виде и занимают ровно столько
места, сколько требуется для хранения типа, например, Int16=2^16=256*256=65536
(пять знакомест), Int32=2^32=65536*65536=4294967296 (десять
знакомест), DateTime=Год_месяц_день (в символьном представлении
цифр занимает 8 знакомест). Тип DateTime называется так потому,
что в одних колонках этого типа можно хранить дату, а в других
- время.

Естественно, что типы данных в C# и типы данных в БД несовместимы. Одни называют объектными типами (типы уровня приложения), другие - реляционными типами (типы уровня БД). Однако функции API инфраструктуры ADO.NET автоматически преобразовывают их один в другой как при загрузке данных в приложение, так и при отправке их обратно в БД. Говорят, что поставщики ADO.NET отображают реляционные типы на объектные и наоборот. Здесь есть своя специфика, которую нужно учитывать при проектировании более изощренных приложений. В менее тонких случаях все происходит автоматически.
Тип данных LongBinary еще называют типом BLOB (Binary
Large Object - большой объект двоичных данных), а тип LongText - (Character
Large Object - большой объект символьных данных). В SQL Server
такие типы носят имя IMAGE и TEXT соответственно. Для
обработки больших типов данных, которые могут не поместиться
целиком в оперативной памяти, могут применяться специальные
приемы, связанные с потоковым доступом к данным и использованием
дисковой памяти.
Используя метаданные, заполняющие список именами таблиц, мы можем создать приложение, которое будет читать содержимое любой таблицы БД, выбранной пользователем в диалоговом режиме.
Вот и все, а дальше код еще одной вкладки для этого упражнения ...!!!
Добавьте к проекту WinForms1 новый пустой файл (шаблон Code File) с именем Form1Ext.cs и
заполните его следующим кодомusing System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
// Дополнительные пространства имен
using System.Data.OleDb;
using System.Data.Common;
namespace WinForms1
{
partial class Form1
{
// Объявили поля-ссылки для видимости в методах
ComboBox cbTables1;
DataGridView dataGridView;
// Создаем вкладку и ее элементы
void CreateControl1()
{
// Динамическое создание элементов управления
TabPage page = new TabPage("Данные");
tabControl.TabPages.Add(page);// Добавили вкладку
// Для динамического выравнивания элементов
TableLayoutPanel layoutPanel = new TableLayoutPanel();
page.Controls.Add(layoutPanel);// Добавили панель к вкладке
layoutPanel.Dock = DockStyle.Fill;
cbTables1 = new ComboBox();
cbTables1.Dock = DockStyle.Fill;
layoutPanel.Controls.Add(cbTables1);// Добавили список к панели
// Зарегистрировали обработчик для списка
cbTables1.SelectedIndexChanged += cbTables1_SelectedIndexChanged;
// Объект отображения данных
dataGridView = new DataGridView();// Создаем
layoutPanel.Controls.Add(dataGridView);// Добавили сетку к панели
dataGridView.Dock = DockStyle.Fill;
dataGridView.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
}
// Заполняем список именами таблиц БД
void LoadTables1()
{
// Создали, заполнили, скопировали в список и потеряли
DataTable table = new DataTable();// Локальная ссылка на объект
// Соединение закрывается после выхода из инструкции using
using (OleDbConnection connection = new
OleDbConnection(ConnectionString()))
{
connection.Open();
// Выбираем коллекцию только для таблиц
table = connection.GetOleDbSchemaTable(
OleDbSchemaGuid.Tables,
new object[] { null, null, null, "TABLE" });
}
// Значения столбца TABLE_NAME скопировали в ComboBox
foreach (DataRow row in table.Rows)
{
Object tableName = row["TABLE_NAME"].ToString();
cbTables1.Items.Add(tableName);
}
// Несвязанный список, можно сортировать
cbTables1.Sorted = true;
cbTables1.SelectedIndex = 0;// Установили список в начало
}
// Извлекаем все данные выделенной таблицы
void cbTables1_SelectedIndexChanged(object sender, EventArgs e)
{
// Все ссылки стековые
String selectCommand = "";
String tableName = cbTables1.SelectedItem.ToString();
if (tableName.IndexOf(' ') != -1)// Для таблицы 'Order Details'
selectCommand = "SELECT * FROM " + "[" + tableName + "]";
else
selectCommand = "SELECT * FROM " + tableName;
OleDbConnection connection = new OleDbConnection(ConnectionString());
OleDbCommand objCommand = new OleDbCommand(selectCommand, connection);
OleDbDataAdapter adapter = new OleDbDataAdapter(objCommand);
DataTable dataTable=new DataTable();
adapter.Fill(dataTable);
dataGridView.DataSource = dataTable;
}
}
}
Дополните конструктор
класса Form1 вызовом
методов из файла Form1Ext.cs// Конструктор формы
public Form1()
{
InitializeComponent();
this.Text = "Упражнение 1. Извлечение метаданных";
this.WindowState = FormWindowState.Maximized;
CreateControl();// Динамически создать пользовательский интерфейс
// Загрузить списки во вкладках
LoadCollections();
LoadTables();
CreateControl1();// Динамически создать пользовательский интерфейс
// Загрузить список в новой вкладке
LoadTables1();
}
Запустите приложение
- теперь в новой вкладке мы можем просматривать содержимое выбранной из БД
таблицы
В данном упражнении рассмотрим возможности инфраструктуры
ADO.NET по вставке и удалению записей данных в одной таблице.
Но прежде немного поболтаем... Основными объектами, обеспечивающими
функциональность манипулирования данными, являются набор реляционных
данных DataSet и поставщик данных DataAdapter. Хотя под поставщиком
подразумевают несколько объектов, включая адаптер, объект соединения,
построитель команд и др. Оба этих объекта реализуют отсоединенный режим
доступа к данным, когда соединение с БД устанавливается только
на момент чтения данных и сохранение изменений.
Применительно к классу принято употреблять равнозначные термины - класс или объект, поскольку и то и другое характеризует одну и ту же программную сущность: 'класс' - на этапе проектирования, 'объект-тип' - на этапе выполнения.

В основе класса DataSet лежат принципы реляционной модели данных (таблицы,
связанные отношениями), система типов CLR (Common Language Runtime) и коллекции
.NET. Кроме содержания собственно данных, объект DataSet содержит метаданные (данные общего характера), которые включают в себя описание таблиц, столбцов,
ограничений и отношений и называются схемой. Объект DataSet может
обмениваться данными с поставщиками DataAdapter, с поставщиками XML (Exchanged
Markup Language) или формировать схему и данные 'налету' в программном коде.
Класс DataSet не зависит от специфики реляционной БД, с которой он общается
через специализированные поставщики данных. Его часто называют объектной
моделью реляционной БД. Это сложный класс, включающий в свой состав
множество других классов, поэтому многие считают, что он слишком переполнен
функциональными возможностями. DataSet считается аналогом реляционной БД, но
находящейся в оперативной памяти.
Набор данных DataSet состоит из объектов таблиц DataTable.
Таблицы могут иметь имена, первичные и внешние ключи, отношения
и уникальные ограничения. Таблицы, в свою очередь, состоят
из объектов столбцов DataColumn и объектов строк DataRow. Для
каждого столбца задается ряд характеристик: имя, тип, ключ,
автозаполнение и др. К столбцам таблицы можно обращаться по
имени или индексу. Строки не имеют имен и адресуются
только индексом.
Коллекции, входящие в набор DataSet, можно связывать с теми элементами управления
Windows-форм, которые имеют свойство DataSource: ComboBox, ListBox, CheckedListBox, DataGrid, DataGridView, DataGridViewComboBoxColumn, DataGridViewComboBoxCell.
События класса DataSet позволяют упростить настройку механизма обновления посредством
внешних источников данных.
Мы долго говорили про объект DataSet и еще будем говорить.
Но в данном упражнении мы будем использовать только объектную
таблицу и на ее снове построим приложение, которое
будет добавлять или удалять новые записи в реляционную
таблицу БД. Попутно усложним приложение некоторыми
приемами работы с коллекциями, динамическим созданием элементов
пользовательского интерфейса, разбивкой кода на отдельные блоки
(и некоторой иной полезной чушью). При этом следует помнить,
что чем большую долю по обработке данных вы выполните на уровне
приложения, тем меньшую нагрузку будет испытывать БД, если
бы ту же работу выполнять SQL-командами на уровне самой БД.
Весь код упражнения подробно прокомментирован в листингах. Специально оставлен альтернативный закомментированный код. Кому скучно, лень разбираться или ЭГО не позволяет, запустите прилагаемый проект, познакомтесь с функциональностью и попробуйте создать подобное 'с нуля'. Лично я писал это упражнение почти неделю (ох и повеселился!) и оно мне порядком поднадоело, поэтому приведу только готовый код, а разбор оставлю до лучших времен (когда буду учить ваших внуков!).
![]() |
Итак, автобусом до сходней доезжаем, а там трусцой, и не стонать. Небось картошку все мы уважаем, когда с сальцой ее намять! ( Высоцкий В.С.) |
Добавьте к решению ADO новый проект оконного приложения с именем WinForms2 и назначьте его стартовым
В панели Solution
Explorer курсором мыши перетащите каталог дата из предыдущего проекта WinForms1 на
узел нового проекта WinForms2, мастеру настройки конфигурации скажите Cancel
В панели Solution Explorer выделите файл БД Northwind.mdb и
в панели Properties установите
для негоBuild Action=None Copy to Output Directory=Copy if newer Второй параметр укажет оболочке, чтобы она копировала
этот файл из каталога проекта в каталог готовой сборки bin при
первом запуске (когда там его вообще нет), и потом только
тогда, когда в проекте окажется более новый файл. Первый
параметр указывает, чтобы оболочка ничего с файлом больше
не делала. При установки других опций второго параметра файл
БД либо не будет скопирован вообще и откомпилированное и
запущенное приложение его не найдет, либо мы не найдем
своих измененных данных в БД (если будем запускать приложение через
оболочку), потому что при каждом новом запуске оболочка будет
копировать в bin исходный
файл БД из каталога проекта. Но если запускать готовую сборку
напрямую, то она будет работать с одной и той же соединенной
БД и изменения мы увидим как положено.
Еще более чудное поведение оболочки наблюдается в случае
создания самой БД и добавления в нее нужной структуры (таблиц,
столбцов, ключей, представлений, ограничений и т.д). Для этих
целей в управляемом приложении используются 2 неуправляемых
COM-объекта ADOX и ADODB (мы
в дальнейшем с ними познакомимся, когда будем создавать свою
БД прямо из кода C#). Там еще смешнее: запускаешь код создания
БД через оболочку, открываешь Windows Explorer, смотришь, что
файл создан и существует, однако через некоторое время он бесследно
исчезает или заменяется старой версией.
Знатоки говорят, что это сделано специально, чтобы в период отладки не запортить настоящую БД. Но если запустить сборку, то все будет работать нормально. В данном упражнении подобное свойство оболочки нам может пригодиться, если после манипуляций с БД через оболочку мы захотим все вернуть к прежнему состоянию. Нужно лишь установить системное время компьютера в прошлое, запустить проект, внести изменения в БД и сохранить их. Файл будет помечен прошлым временем и при последующем запуске проекта оболочка восстановит его исходным файлом из каталога проекта, посчитав оригинал более свежим.
Следует иметь ввиду, что при увеличении размеров данных, сохраняемых в файловой БД, сам файл при каждом сохранении меняет в своем заголовке только время обновления. А физическое увеличение размеров БД выполняется адаптером автоматически и дискретно - с некоторым запасом, чтобы не перезаписывать файл БД на новое место при каждом обновлении данных, как это бывает с обычными файлами.
Заполните
файл Form1.cs проекта WinForms2 следующим кодомusing System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
// Дополнительные пространства имен
using System.Data.OleDb;
using System.Data.Common;
namespace WinForms2
{
public partial class Form1 : Form
{
// Инфраструктура для интерфейса первой вкладки
TabControl tabControl;
ComboBox cbLetters;
Button button;
ListBox listBox;
// Инфраструктура для доступа к БД
OleDbConnection connection;
OleDbDataAdapter adapter;
DataTable tableCustomers;
public Form1()
{
InitializeComponent();
this.Text = "Упражнение 2. Построчное редактирование";
this.Width = 355;
this.FormClosed += Form1_FormClosed;
// Добавили панель к форме
this.Controls.Add(tabControl = new TabControl());
tabControl.Dock = DockStyle.Fill;
// Создать интерфейс пользователя
CreatePage1();
CreatePage2();
// Заполнить cbLetters, полную таблицу и связать с listBox
LoadPage1();
}
void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
// Выполнена ли функция tableCustomers.AcceptChanges(),
// меняющая состояние таблицы при обновлении в this.Update()
DataTable table = tableCustomers.GetChanges();
if (table != null)
{
DialogResult result =
MessageBox.Show("Вы не сохранили изменения в БД\nСохранить?",
"Внимание!",
MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
if (result == DialogResult.Yes)
Update();
}
}
// Строка соединения к БД с абсолютным путем, определяемым сборкой
String ConnectionString()
{
// Используем построитель строки подключения
OleDbConnectionStringBuilder objConnectionStringBuilder =
new OleDbConnectionStringBuilder();
objConnectionStringBuilder.Provider = "Microsoft.Jet.OLEDB.4.0";
objConnectionStringBuilder.DataSource =
Application.StartupPath.ToString() + @"\Data\Northwind.mdb";
return objConnectionStringBuilder.ToString();
}
void LoadPage1()
{
// Очищаем список, иначе будет накопление
cbLetters.Items.Clear();
// Блоки кода для читабельности, но видимость переменных не скрывают
{ // Загружаем раскрывающийся список
// Добавили первый элемент списка
cbLetters.Items.Add("All");
// Дополняем список буквами алфавита
for (char ch = 'A'; ch <= 'Z'; ch++)
{
cbLetters.Items.Add(ch);
}
// Устанавливаем на первый индекс
cbLetters.SelectedIndex = 0;
}
{ // Загружаем таблицу Customers целиком
connection = new OleDbConnection(ConnectionString());
adapter = new OleDbDataAdapter(
new OleDbCommand("SELECT * FROM Customers", connection));
tableCustomers = new DataTable("Customers");
// Добавляем (до загрузки!!!) вычислимые столбцы Tab и ViewColumn
// для отображения одного из них в listBox
tableCustomers.Columns.Add(new DataColumn("CustomerID", typeof(String)));
tableCustomers.Columns.Add(new DataColumn("Tab", typeof(String)));
tableCustomers.Columns["Tab"].DefaultValue = "\t - ";// Значение по умолчанию
tableCustomers.Columns.Add(new DataColumn("CompanyName", typeof(String)));
tableCustomers.Columns.Add(new DataColumn("ViewColumn", typeof(String)));
// Компонуем столбец
tableCustomers.Columns["ViewColumn"].Expression = "CustomerID + Tab + CompanyName";
adapter.Fill(tableCustomers);// Сам откроет соединение и сам закроет
// Указать первичный ключ из одного столбца для использования метода Find()
tableCustomers.PrimaryKey =
new DataColumn[] { tableCustomers.Columns["CustomerID"] };
// Не разрешать ввод пустых значений в поле CustomerID
//tableCustomers.Columns["CustomerID"].AllowDBNull = false;
}
}
// Вся таблица находится в кэше в отсоединенном режиме
// и поэтому обрабатывается быстро
// Тот же самый результат можно было получить на уровне БД
// с помощью SQL-запросов, но это намного медленнее!!!
void cbLetters_SelectedIndexChanged(object sender, EventArgs e)
{
// Еще ничего не создано или не выбрано
// Условия проверяются слева направо до первого true
if (cbLetters == null || cbLetters.SelectedIndex == -1
|| tableCustomers == null)
return;
// Извлекаем символ из cbLetters
String letter = cbLetters.SelectedItem.ToString();
if (letter == "All")
letter = String.Empty;
// Создаем для таблицы Customers объект представления и настраиваем его
DataView view = new DataView(tableCustomers);
view.RowFilter = "CustomerID LIKE '" + letter + "%'";
view.Sort = "CustomerID";
// Заполняем список ListBox столбцами CustomerID и скомпанованным
// с учетом значения фильтра в cbLetters
listBox.DataSource = view;// Связываем
listBox.DisplayMember = "ViewColumn";// Отображаем вычислимый столбец
listBox.ValueMember = "CustomerID"; // Невидимый параметр
}
void button_DeleteClick(object sender, EventArgs e)
{
// Если нечего удалять
if (cbLetters.SelectedIndex == -1 || cbLetters.Items.Count <= 0)
return;
// Ищем текущую строку в таблице по CustomerID
Object findValue = listBox.SelectedValue;// Выделенное значение ValueMember
DataRow row = tableCustomers.Rows.Find(findValue);
// Помечаем строку как удаленную
row.Delete();
}
// Отменить текущие изменения таблицы
void button_RestoreClick(object sender, EventArgs e)
{
tableCustomers.RejectChanges(); ;
}
// Отправить текущие изменения таблицы в базу. Если убрать new,
// то будет вызываться унаследованная функция Control.Update()
new void Update()
{
// Создаем построитель команды по настройкам нашего адаптера
OleDbCommandBuilder commandBuilder =
new OleDbCommandBuilder(adapter);
// Не проверять наличие чужих изменений,
// возможно внесенных другими со времени последней
// выборки данных, а записывать своей UpdateCommand поверх
commandBuilder.ConflictOption = ConflictOption.OverwriteChanges;
//commandBuilder.RefreshSchema();
// Построитель команд сам сгенерирует нужные команды,
// прочитав информацию в свойстве SelectCommand адаптера
//adapter.InsertCommand = commandBuilder.GetInsertCommand();
//adapter.UpdateCommand = commandBuilder.GetUpdateCommand();
//adapter.DeleteCommand = commandBuilder.GetDeleteCommand();
// Если соединение открыть, то адаптер его сам не закроет
//connection.Open();
// Адаптер сам откроет соединение, сам его закроет,
// сам переведет таблицу в разряд немодифицированных
adapter.Update(tableCustomers);
//adapter.InsertCommand.Connection.Close();
// Перевести таблицу в разряд немодифицированных
//tableCustomers.AcceptChanges();
}
// Отправить текущие изменения таблицы в базу
void button_UpdateClick(object sender, EventArgs e)
{
Update();
}
DataRow newRow = null;// Проверяется обработчиками кнопок
void button_CurrentClick(object sender, EventArgs e)
{
// Если ничего не выделено как текущее
if (listBox.SelectedIndex == -1)
return;
// А не включен ли режим добавления новой строки
// (а не испить ли нам кофею!!!)
if (newRow != null)
{
MessageBox.Show("Редактируйте добавленную\n"
+ "строку или отмените ее", "Ошибка",
MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
// Ищем текущую строку в таблице по CustomerID
Object findValue = listBox.SelectedValue;// Выделенное значение ValueMember
DataRow row = tableCustomers.Rows.Find(findValue);
// Заполняем текстовые поля значениями полей текущей строки
foreach (TextBox tb in listReference)
{
// Имена столбцов ранее записали в свойство tb.Name;
tb.Text = row[tb.Name].ToString();
}
}
// Добавление новой строки
void button_NewRowClick(object sender, EventArgs e)
{
if (newRow != null)
{
MessageBox.Show("Вы не отменили или \n"
+ "не сохранили предыдущую строку", "Ошибка",
MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
// Меняем цвет текстовых полей
ChangeColor(Colors.EditColor);
// Очищаем текстовые поля
foreach (TextBox tb in listReference)
{
tb.Text = String.Empty;
}
// Создаем новую строку по схеме таблицы
newRow = tableCustomers.NewRow();
}
void button_AddRowClick(object sender, EventArgs e)
{
if (newRow == null)
{
MessageBox.Show("Вы не добавили новую строку", "Ошибка",
MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
// Заполняем поля новой строки данными из TextBox
newRow.BeginEdit(); // Отключаем механизм проверки ограничений
foreach (TextBox tb in listReference)
{
if (tb.Text == String.Empty)// Пустые строки не принимает
newRow[tb.Name] = System.DBNull.Value;
else
newRow[tb.Name] = tb.Text;
}
if (newRow["CustomerID"].ToString() == String.Empty)
{
MessageBox.Show("Поле CustomerID не должно быть пустым",
"Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
// Возможно нарушение ввода, поэтому упаковываем в проверку
try
{
newRow.EndEdit();// Пробуем включить проверку ограничений
// Добавляем строку в таблицу
tableCustomers.Rows.Add(newRow);
}
catch
{
MessageBox.Show("Поле CustomerID должно быть уникальным",
"Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
newRow = null;
// Восстанавливаем цвет текстовых полей
ChangeColor(Colors.RestoreColor);
// Очищаем текстовые поля
foreach (TextBox tb in listReference)
{
tb.Text = String.Empty;
}
}
void button_ClearClick(object sender, EventArgs e)
{
// Обнуляем новую строку
newRow = null;
// Очищаем текстовые поля
foreach (TextBox tb in listReference)
{
tb.Text = String.Empty;
}
// Восстанавливаем цвет
ChangeColor(Colors.RestoreColor);
}
enum Colors
{
EditColor,
RestoreColor
}
// Изменяем цвет текстовых полей
void ChangeColor(Colors color)
{
switch (color)
{
case Colors.EditColor:
foreach (TextBox tb in listReference)
tb.BackColor = Color.Aqua;
break;
case Colors.RestoreColor:
foreach (TextBox tb in listReference)
tb.BackColor = Color.White;
break;
}
}
}
}
Добавьте
к проекту WinForms2 новый пустой файл (шаблон Code
File) с именем CreateInterface.cs и
заполните его кодом динамического создания интерфейса
пользователяusing System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace WinForms2
{
partial class Form1
{
// Динамическое создание элементов первой вкладки
void CreatePage1()
{
TabPage page = new TabPage("Строки");
tabControl.TabPages.Add(page);// Добавили вкладку к панели
// Табличная панель для вертикального выравнивания элементов
TableLayoutPanel tableLayoutPanel = new TableLayoutPanel();
page.Controls.Add(tableLayoutPanel);// Добавили табличную панель к вкладке
tableLayoutPanel.Dock = DockStyle.Fill;
// Рисуем рамку вокруг панели (для красоты)
tableLayoutPanel.CellBorderStyle = TableLayoutPanelCellBorderStyle.Single;
// Панель горизонтального выравнивания
// верхней ячейки табличной панели
FlowLayoutPanel flowLayoutPanel = new FlowLayoutPanel();
flowLayoutPanel.Dock = DockStyle.Top;
flowLayoutPanel.AutoSize = true;
tableLayoutPanel.Controls.Add(flowLayoutPanel);
// Заполняем горизонтальную панель
// Для наглядности группируем в блоки кода
{
// Create ComboBox
flowLayoutPanel.Controls.Add(cbLetters = new ComboBox());// Создаем и добавляем
cbLetters.Anchor = AnchorStyles.None;// Выравнивание по центру родителя
cbLetters.Width = 80;// Зададим ширину
cbLetters.KeyDown += new KeyEventHandler(cbLetters_KeyDown);// Не редактировать
cbLetters.KeyPress += new KeyPressEventHandler(cbLetters_KeyPress);// Не редактировать
cbLetters.SelectedIndexChanged += cbLetters_SelectedIndexChanged;// Выбрать
cbLetters.BindingContextChanged += cbLetters_SelectedIndexChanged;// Для начальной загрузки
// Всплывающая подсказка
ToolTip toolTip = new ToolTip();
String message = "Фильтр записей таблицы";
toolTip.SetToolTip(cbLetters, message);
toolTip.IsBalloon = true;
//toolTip.ToolTipIcon = ToolTipIcon.Info;
//toolTip.ForeColor = Color.Magenta;
//toolTip.BackColor = Color.LightGoldenrodYellow;
}
{
// Create Button
flowLayoutPanel.Controls.Add(button = new Button());// Создаем и добавляем
button.Anchor = AnchorStyles.None;// Выравнивание по центру родителя
button.Text = "Delete";
button.AutoSize = true;
// Регистрируем обработчик события Click
button.Click += button_DeleteClick;
// Всплывающая подсказка
ToolTip toolTip = new ToolTip();
String message = "Удаление текущей записи";
toolTip.SetToolTip(button, message);
toolTip.IsBalloon = true;
// Create Button
button = new Button();
button.Parent = flowLayoutPanel; // Добавляем к табличной панели
button.Anchor = AnchorStyles.None;// Выравнивание по центру родителя
button.Text = "Restore";
button.AutoSize = true;
// Регистрируем обработчик события Click
button.Click += button_RestoreClick;
// Всплывающая подсказка
toolTip = new ToolTip();
message = "Восстановление\nнесохраненных\nизменений";
toolTip.SetToolTip(button, message);
toolTip.IsBalloon = true;
// Create Button
button = new Button();
button.Parent = flowLayoutPanel; // Добавляем к табличной панели
button.Anchor = AnchorStyles.None;// Выравнивание по центру родителя
button.Text = "Update";
button.AutoSize = true;
// Регистрируем обработчик события Click
button.Click += button_UpdateClick;
// Всплывающая подсказка
toolTip = new ToolTip();
message = "Запись в БД";
toolTip.SetToolTip(button, message);
toolTip.IsBalloon = true;
}
// Список размещаем под горизонтальной панелью
tableLayoutPanel.Controls.Add(listBox = new ListBox());
listBox.Dock = DockStyle.Fill;
listBox.Margin = new Padding(0);// Убираем отступы вокруг списка
tableLayoutPanel.Padding = new Padding(0);// Убираем отступы внутри панели
}
void cbLetters_KeyPress(object sender, KeyPressEventArgs e)
{
e.Handled = true;
}
void cbLetters_KeyDown(object sender, KeyEventArgs e)
{
e.Handled = true;
}
// Динамическое создание элементов второй вкладки
void CreatePage2()
{
TabPage page = new TabPage("Поля");
tabControl.TabPages.Add(page);// Добавили вкладку к панели
TableLayoutPanel tablePanel = new TableLayoutPanel();// Табличная панель
tablePanel.Parent = page;// Назначили родителя
tablePanel.Dock = DockStyle.Fill;// На всю вкладку
FlowLayoutPanel flowPanel = new FlowLayoutPanel();// Горизонтальная панель
flowPanel.Parent = tablePanel;// Назначили родителя
flowPanel.Dock = DockStyle.Top;// Пристыковали вверх к родителю
flowPanel.AutoSize = true;// По размеру содержимого
flowPanel.BorderStyle = BorderStyle.Fixed3D;// Красиво тесненная
TableLayoutPanel tmp = new TableLayoutPanel();
tmp.Parent = tablePanel;
tmp.Dock = DockStyle.Fill;
tablePanel = tmp;// Бросили внешнюю панель
tmp = null;
//tablePanel.CellBorderStyle = TableLayoutPanelCellBorderStyle.Single;// Рамка
// Заполняем верхнюю панель
Button button = new Button();
button.Text = "Current";
button.AutoSize = true;
button.Parent = flowPanel;
button.Click += button_CurrentClick;
// Всплывающая подсказка
ToolTip toolTip = new ToolTip();
String message = "Заполнение полей\nзначениями текущей записи";
toolTip.SetToolTip(button, message);
toolTip.IsBalloon = true;
button = new Button();
button.Text = "NewRow";
button.AutoSize = true;
button.Parent = flowPanel;
button.Click += button_NewRowClick;
// Всплывающая подсказка
toolTip = new ToolTip();
message = "Создание новой записи";
toolTip.SetToolTip(button, message);
toolTip.IsBalloon = true;
// Используем ту же ссылку для создания другой кнопки
button = new Button();
button.Text = "AddRow";
button.AutoSize = true;
button.Parent = flowPanel;
button.Click += button_AddRowClick;
// Всплывающая подсказка
toolTip = new ToolTip();
message = "Сохранение новой\nзаписи в таблице";
toolTip.SetToolTip(button, message);
toolTip.IsBalloon = true;
button = new Button();
button.Text = "Clear";
button.AutoSize = true;
button.Parent = flowPanel;
button.Click += button_ClearClick;
// Всплывающая подсказка
toolTip = new ToolTip();
message = "Очистить поля/Отменить запись";
toolTip.SetToolTip(button, message);
toolTip.IsBalloon = true;
Label label = new Label();
label.Text = "Поля для редактирования записи:";
label.AutoSize = true;
label.Parent = tablePanel;
CreateTextBox(tablePanel);
// Собираем ссылки на все TextBox формы, так как
// на форме нет других TextBox, ненужных нам!!!
FindTextBox(this.Controls);
}
// Динамически добавляем поля во вторую вкладку
void CreateTextBox(TableLayoutPanel panel)
{
String[] columnName = {
"CustomerID",
"CompanyName",
"Address",
"City",
"Phone"
};
for (int i = 0; i < columnName.Length; i++)
{
FlowLayoutPanel flowPanel = new FlowLayoutPanel();
flowPanel.Dock = DockStyle.Top;
flowPanel.AutoSize = true;
flowPanel.Parent = panel;
Label label = new Label();
label.AutoSize = true;
label.Text = columnName[i] + ":";
label.Anchor = AnchorStyles.None;// Выравнивание по центру горизонтали
label.Parent = flowPanel;
TextBox txtBox = new TextBox();
txtBox.Name = columnName[i];
txtBox.Width = 200;
txtBox.Parent = flowPanel;
}
}
// Рекурсивный поиск всех объектов TextBox
// в дереве коллекций, начиная от текущего корня
// System.Collections.ArrayList list =
// new System.Collections.ArrayList(); - можно и этот, только он
// работает с типом Object и нужно будет явно повышать полномочия ссылки
List<TextBox> listReference = new List<TextBox>();// Типизированный список
void FindTextBox(Control.ControlCollection controls)
{
foreach (Control ctrl in controls)
{
if (ctrl is TextBox)
{
listReference.Add((TextBox)ctrl);
}
if (ctrl.Controls != null)
{
FindTextBox(ctrl.Controls);// Рекурсия!!!
}
}
}
}
}
Запустите
проект и проверьте заложенную в нем функциональностьДва снимка экрана, иллюстрирующие интерфейс приложения, выглядят так
![]() |
![]() |
С динамическим созданием пользовательского интерфейса повозились, теперь в этом упражнении вновь частично вернемся к испытанному дедовскому способу визуального проектирования, который значительно легче, поскольку кодированием занимается дизайнер формы. А уж он-то намного больше знает, какой код правильно сгенерировать. Но все же мы программисты и тонкие настройки выполним сами. В этом упражнении я постарался их 'понатыкать' побольше.
На сей раз мы
будем работать с таблицей Products готовой
БД Northwind.mdb,
потому что там есть столбец первичного ключа числового типа
и удобно сделать этот столбец автозаполняющимся (AutoIncrement),
чтобы не редактировать. В качестве элемента представления
и редактирования данных применим объект DataGridView,
который свяжем с загруженной таблицей, но не напрямую. Дело
в том, что мы еще будем использовать объект-навигатор BindingNavigator,
который обеспечит удобное перемещение по сетке DataGridView.
Для обеспечения синхронной их работы с одной и той же таблицей,
мы не сможем их сразу оба связать с ней. Но это можно легко
сделать через объект-разветвитель BindingSource, к которому
подключим таблицу, а уже через него подключим и другие объекты.
Добавьте
к решению ADO новый проект оконного приложения
с именем WinForms3 и
назначьте его стартовым
В панели Solution
Explorer курсором мыши перетащите каталог дата из предыдущего
проекта WinForms2 на узел нового проекта WinForms3,
мастеру настройки конфигурации скажите Cancel
В панели Solution
Explorer выделите файл БД Northwind.mdb и
в панели Properties установите для негоBuild Action=None Copy to Output Directory=Copy
if newer
Перетащите
на форму из вкладки Data панели Toolbox объекты DataGridView, BindingNavigator, BindingSource.
Проследите, чтобы объект BindingNavigator попал
на форму, а не внутрь объекта DataGridView как дочерний
(тогда лучше вначале перетащить BindingNavigator,
а потом DataGridView )
Настройте
свойства объектов в соответствии с таблицей свойств. Имена
экземпляров задайте в точности, иначе будет нестыковка с
приведенным далее кодом| Объект | Свойство | Значение |
|---|---|---|
Form |
Text |
Упражнение 3. |
Size |
333 ; 300 |
|
DataGridView |
(Name) |
dataGridView |
Dock |
Fill |
|
BindingNavigator |
(Name) |
bindingNavigator |
CountItemFormat |
из {0} |
|
BindingSource |
(Name) |
bindingSource |
Растяните
форму по ширине раза в два, выделите сам объект bindingNavigator (можно щелкнуть на пиктограмме в самом низу рабочей области
оболочки) и раскройте его интеллектуальный
дескриптор (smart
tag =
Щелкните
на ссылке Insert Standard Items окна смарт-тега, чтобы добавить
ряд встроенных пиктограмм
Из
добавленных пиктограмм оставьте Save и Help, а остальные
удалитеЛишние пиктограммы можно удалять по одной, выделяя их, а можно
воспользоваться окном редактора Items Collection Editor.
Щелкните
в окне смарт-тега объекта bindingNavigator на
ссылку Edit
Items, чтобы открыть редактор элементов. Этот редактор
с типовым интерфейсом и с ним легко работать
Удалите
лишние пиктограммы и добавьте сепаратор (разделитель) между
кнопкой Save и
предыдущими 'родными' пиктограммами навигатора
Установите
прежнюю ширину формы, прописанную в таблице свойствВ конечном итоге на этапе проектирования должен получиться такой интерфейс

Я приведу полный рабочий листинг кода, который получился не таким уж и большим, но какую функциональность он все-таки обеспечивает благодаря мощным библиотечным классам. Мы должны все же уважать за это Большого Била, хоть он и "проклятый загнивший капиталлист" (страшные слова, аж самому неприятно).
Заполните
файл Form1.cs проекта WinForms3 следующим кодомusing System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
// Дополнительные пространства имен
using System.Data.OleDb;
using System.Data.Common;
namespace WinForms3
{
public partial class Form1 : Form
{
public Form1()
{
// Инициализировать созданное в режиме дизайнера
InitializeComponent();
this.WindowState = FormWindowState.Maximized;// На полный экран
// Создать все остатки от дизайнера и загрузить таблицу
LoadProducts();
// Перед закрытием формы проверяем несохраненные изменения
this.FormClosing += new FormClosingEventHandler(Form1_FormClosing);
}
// Проверяем при закрытии формы несохраненные изменения
void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if(tableProducts.GetChanges() != null)
{
DialogResult result =
MessageBox.Show("Вы не сохранили изменения в БД\nСохранить?",
"Внимание!",
MessageBoxButtons.YesNoCancel, MessageBoxIcon.Warning);
if (result == DialogResult.Yes)
Update(); // Сохраняем данные
else if (result == DialogResult.Cancel)
e.Cancel = true;// Отменяем закрытие формы
}
}
// Строка соединения к БД с абсолютным путем, определяемым сборкой
String ConnectionString()
{
// Используем построитель строки подключения
OleDbConnectionStringBuilder objConnectionStringBuilder =
new OleDbConnectionStringBuilder();
objConnectionStringBuilder.Provider = "Microsoft.Jet.OLEDB.4.0";
objConnectionStringBuilder.DataSource =
Application.StartupPath.ToString() + @"\Data\Northwind.mdb";
//Application.ExecutablePath + @"\Data\Northwind.mdb";// + имя сборки
return objConnectionStringBuilder.ToString();
}
// Выносим в поля для видимости в методах
DataTable tableProducts;
OleDbDataAdapter adapter;
// Все загружаем и все настраиваем
void LoadProducts()
{
// Загружаем таблицу Products целиком
OleDbConnection connection = new OleDbConnection(ConnectionString());
adapter = new OleDbDataAdapter(
new OleDbCommand("SELECT * FROM Products", connection));
tableProducts = new DataTable();// Имя можно не указывать!
// Регистрируем обработчик события изменения поля таблицы для
// выделения ячейки после редактирования через DataGridView
tableProducts.ColumnChanged +=
new DataColumnChangeEventHandler(tableProducts_ColumnChanged);
// Указать столбец с автозаполнением, но обязательно до включения в таблицу
DataColumn col = new DataColumn("ProductID", typeof(int));
col.AutoIncrement = true;
tableProducts.Columns.Add(col);
col.ReadOnly = true;// Ключевой столбец не давать редактировать напрямую
adapter.Fill(tableProducts);// Сам откроет соединение и сам закроет
// Указать первичный ключ можно уже и в заполненной таблице
// Синтаксис определения первичного ключа требует использовать массив
// на случай, если в первичный ключ войдут несколько столбцов
tableProducts.PrimaryKey =
new DataColumn[] { tableProducts.Columns["ProductID"] };
//
// Разветвитель, навигатор и сетку создали визуально
//
// Связали таблицу с разветвителем
bindingSource.DataSource = tableProducts;
// Поключили к разветвителю навигатор
bindingNavigator.BindingSource = bindingSource;
// Подключили к разветвителю элемент редактирования
dataGridView.DataSource = bindingSource;
//
//Настраиваем DataGridView
//
// Сделаем ключевой столбец с автозаполнением только для чтения
//dataGridView.Columns["ProductID"].ReadOnly = true;// Уже сделали в таблице
// Ширина столбцов по ширине содержимого
dataGridView.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
dataGridView.RowHeadersWidth = 25;// Ширина первого (служебного) столбца
//dataGridView.RowHeadersVisible = false;// Скрываем первый (служебный) столбец
//dataGridView.Columns["ProductID"].Visible = false;// Скрываем рабочий столбец
// Запретим выделение многих строк, все-равно
// навигатор удаляет только по одной строке
dataGridView.MultiSelect = false;
// Настраиваем стиль строки заголовков сетки
DataGridViewCellStyle columnHeaderStyle = new DataGridViewCellStyle();// Создали стиль
columnHeaderStyle.BackColor = Color.Beige;// Цвет фона заголовка
columnHeaderStyle.Font = new Font("Verdana", 10, FontStyle.Bold);// Шрифт
columnHeaderStyle.Alignment =
DataGridViewContentAlignment.MiddleCenter;// Выравнивать по центру столбца
dataGridView.ColumnHeadersDefaultCellStyle = columnHeaderStyle;// Настроили и прицепили
// Фоновый цвет всех ячеек таблицы по умолчанию белый. Но явно
// задаем потому, что позже будем восстанавливать белым же цветом
DataGridViewCellStyle defaultCellStyle = new DataGridViewCellStyle();
defaultCellStyle.BackColor = Color.White;
dataGridView.DefaultCellStyle = defaultCellStyle;// Настроили и прицепили
//
// Настраиваем кнопки объекта BindingNavigator здесь
// или в панели Properties дизайнера
//
bindingNavigatorMoveFirstItem.ToolTipText = "На первую запись";
bindingNavigatorMovePreviousItem.ToolTipText = "На предыдущую запись";
bindingNavigatorPositionItem.ToolTipText = "Текущая запись";
bindingNavigatorCountItem.ToolTipText = "Всего записей";
bindingNavigatorMoveNextItem.ToolTipText = "На следующую запись";
bindingNavigatorMoveLastItem.ToolTipText = "На последнюю запись";
bindingNavigatorAddNewItem.ToolTipText = "Добавить запись";
bindingNavigatorDeleteItem.ToolTipText = "Удалить запись";
saveToolStripButton.ToolTipText = "Сохранить изменения";
helpToolStripButton.ToolTipText = "Помощь";
helpToolStripButton.Enabled = false;// Затычка для Справки, пока не хотим...
// Регистрируем обработчик события сохранения данных в БД
saveToolStripButton.Click += new EventHandler(saveToolStripButton_Click);
//
// Создаем и настраиваем контекстное меню для дополнительных команд
//
// Создаем объект контекстного меню
System.Windows.Forms.ContextMenuStrip menu = new ContextMenuStrip();
// Прицепляем к DataGridView
dataGridView.ContextMenuStrip = menu;
// Создаем объект команды и сразу с названием
ToolStripMenuItem item = new ToolStripMenuItem("Восстановить");
// Всплывающая подсказка
item.ToolTipText = "Откатить все несохраненные изменения";
// Добавляем команду в коллекцию команд контекстного меню
menu.Items.Add(item);
// Регистрируем обработчик команды для события Click
item.Click += new EventHandler(item_RestoreClick);
}
// Список для хранения ячеек с измененными стилями
// для последующего восстановления в ClearStyleModifiedCells()
// (Ничего приличнее мне в голову не пришло! НаноСидоркин - помоги!
// Надо искать оптовое решение в самом DataGridView)
System.Collections.ArrayList listCell = new System.Collections.ArrayList();
// Срабатывает при модификации любого поля
void tableProducts_ColumnChanged(object sender, DataColumnChangeEventArgs e)
{
DataGridViewCellStyle style = new DataGridViewCellStyle();
style.BackColor= Color.Aqua;
dataGridView.CurrentCell.Style = new DataGridViewCellStyle(style);
listCell.Add(dataGridView.CurrentCell);// Складываем ссылки для восстановления
}
// Обработчик команды контекстного меню
void item_RestoreClick(object sender, EventArgs e)
{
// Если нечего откатывать
if (tableProducts.GetChanges() == null)
return;
// Есть изменения, спрашиваем желание пользователя
DialogResult result =
MessageBox.Show("Вы действительно хотите откатить\n"
+ "последние несохраненные изменения?",
"Внимание!",
MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
if (result == DialogResult.Yes)
{
tableProducts.RejectChanges();// Откатываем таблицу
ClearStyleModifiedCells(); // Снимаем выделение с ячеек
}
}
// Сохраняем изменения в БД
new void Update()
{
// Чтобы лишний раз не дергать проверку на уровне БД
if (tableProducts.GetChanges() == null)
return;
// Создаем построитель команд по настройкам нашего адаптера
OleDbCommandBuilder commandBuilder =
new OleDbCommandBuilder(adapter);
// Не реагировать на конфликты параллелизма, а записывать поверх
commandBuilder.ConflictOption = ConflictOption.OverwriteChanges;
// Отправить изменения таблицы в БД. Необходимые
// команды генерирует созданный объект commandBuilder
adapter.Update(tableProducts);
}
// Обработчик кнопки сохранения изменений в БД
void saveToolStripButton_Click(object sender, EventArgs e)
{
// Проверка достоверности завершает редактирование текушего поля
this.Validate();
bindingSource.EndEdit();// Останавливаем разветвитель
Update();// Отправляем изменения в БД
ClearStyleModifiedCells();// Восстанавливаем цвет ячеек сетки
}
// Все содержимое ArrayList имеет тип Object - надо явно приводить
void ClearStyleModifiedCells()
{
// Снимаем выделение с измененных ячеек сетки
DataGridViewCellStyle style = new DataGridViewCellStyle();
style.BackColor = Color.White;
foreach (Object ob in listCell)
{
if (ob != null)// На случай, если строку с ячейками удалили!
((DataGridViewCell)ob).Style = new DataGridViewCellStyle(style);
}
listCell.Clear();// Очистить список, начать с чистого листа!
}
}
}
Запустите
приложение, разберитесь с кодом, что не достроено - достройте,
что недокрашено - докрасьте. Вот результат
Здесь есть один нюанс. Я не смог усмирить счетчик автозаполнения первого столбца. Если последние строки добавляешь, а потом удаляешь и сохраняешь - он все-равно не сбрасывается в значение, следующее за последним столбцом при новой выборке данных, а считает сам по себе. Как до этого счетчика добраться, может кто и догадается, я не смог.
Еще раз приведу схему связей таблиц в нашей учебной БД, из
которой видно, что столбец ProductID таблицы Products является первичным ключем, но есть
еще и два столбца внешних ключей

Можно не ставить автозаполнение на столбец первичного ключа,
а вынести счетчик отдельно, а при выборке данных посылать
SQL-команду с функцией MAX().
"SELECT MAX(ProductID) FROM Products"
Но для большой таблицы это не выход, хотя для ключевого столбца команда выполнится быстро. Можно вообще с ключевого столбца снять свойство только для чтения, снять автозаполнение и дать пользователю самому заполнять ключевые поля уникальными значениями. Но тогда все равно нужно проверять уникальность значений ключевого столбца, иначе будет нарушено ограничение таблицы и мы получим ряд исключений. Мы знаем, что у нас проблемные пользователи (такие же, как дороги).
Попробуйте обойти эту дырку. Одно из направлений такое
Добавьте
в класс Form1 проекта WinForms3 примерно
следующий метод, получающий через SQL-запрос максимальное
значение ключевого столбца// Максимальное значение ключа
void MaxPrimaryKey()
{
OleDbConnection conn = new OleDbConnection(ConnectionString());
OleDbCommand command = new OleDbCommand(
"SELECT MAX(ProductID) FROM Products",
conn);
conn.Open();
int max = (int)command.ExecuteScalar();
conn.Close();
MessageBox.Show(max.ToString());
}
Добавьте
в конструктор класса (или еще куда-нибудь) вызов этого метода
и выполните проектДля показанного ранее на снимке состояния БД получим такой 'непричесанный' результат

Далее
действуйте по обстановке, может быть и другим способомМожно, также, подсчитывать количество записей SQL-запросом
"SELECT COUNT(*) FROM Products"
и их считать отправной точкой для счетчика при добавлении новых записей в таблицу. Но это будет работать при условии, что ключи изначально представляют собой натуральный ряд чисел. Для нашего примера это будет уже расхождение с подсчетом максимального значения ключа

Можно попробовать что-то сделать и с таким кодом
// Все загружаем и все настраиваем
void LoadProducts()
{
.........................................
tableProducts.RowChanged += new DataRowChangeEventHandler(tableProducts_RowChanged);
}
void tableProducts_RowChanged(object sender, DataRowChangeEventArgs e)
{
if (e.Row["ProductName"].ToString() == String.Empty)
e.Row.Delete();
// А что дальше???
}
Думайте!!!
Классики хотели, я и сподобился...Xml-формат является универсальным на любых платформах. Он не зависит от специфики формата БД. Поэтому его удобно применять данными между различными БД. Еще одно достоинство - текстовое представление данных. Это очень удобно при пересылке данных по сети, поскольку не содержит в себе случайных управляющих символов и сочетаний байт, которые могут быть восприняты в процессе передачи как служебные команды.
На фрагментах кода поясним работу приложения для данного упражнения.
Прежде всего сформируем строку соединения с помощью библиотечного объекта OleDbConnectionStringBuilder, которую возвращает функция
// Строка соединения к БД с абсолютным путем, определяемым сборкой
String ConnectionString()
{
// Используем построитель строки подключения
OleDbConnectionStringBuilder objConnectionStringBuilder =
new OleDbConnectionStringBuilder();
objConnectionStringBuilder.Provider = "Microsoft.Jet.OLEDB.4.0";
objConnectionStringBuilder.DataSource =
Application.StartupPath.ToString() + @"\Data\Northwind.mdb";
// Текущий путь
//System.Environment.CurrentDirectory.ToString()
// Путь к сборке + имя сборки с расширением .exe
//Application.ExecutablePath + @"\Data\Northwind.mdb";
return objConnectionStringBuilder.ToString();
}
Далее мы создаем метод, который возвращает заполненный набор
данных.
DataSet LoadDataSet()
{
DataSet ds= new DataSet();
// Заполняем множественный набор данных из БД
using (OleDbConnection conn = new OleDbConnection(ConnectionString()))
{
OleDbCommand selectCommand = conn.CreateCommand();// Команда получила соединение
OleDbDataAdapter adapter = new OleDbDataAdapter(selectCommand);
//adapter.FillSchema(ds, SchemaType.Source);// Лишнее, загружается вместе с данными
selectCommand.CommandText = "SELECT * FROM Employees";
// Загружает данные и схему (по умолчанию) первой таблицы
// Без маркера может добавиться только одна
// таблица и будет иметь дежурное имя Table
adapter.Fill(ds, "Empl");
// Загружает данные и схему второй таблицы
selectCommand.CommandText = "SELECT * FROM Products";
adapter.Fill(ds, "Prod");
// Чтобы повторно не добавлялось одно и то же
adapter.MissingSchemaAction = MissingSchemaAction.AddWithKey;
adapter.Fill(ds, "Prod");// Не добавится, т.к. установили AddWithKey
//adapter.FillSchema(ds, SchemaType.Source);// Загрузить только схему
}
return ds;
}
Обратите внимание, что в один и тот же объект DataSet
загружаются две таблицы сразу. Причем сделана попытка вторую
таблицу загрузить дважды. Этому препятствует свойство адаптера MissingSchemaAction, установленное в значение одноименного
перечисления MissingSchemaAction.AddWithKey. Если в методе Fill() адаптера не указать имя добавляемой таблицы, то будет
загружена первая таблица с дежурным именем Table. Имя загруженной
таблицы можно присвоить и после ее загрузки,
только тогда может успешно загрузится и вторая таблица
DataSet LoadDataSet()
{
DataSet ds= new DataSet();
// Заполняем множественный набор данных из БД
using (OleDbConnection conn = new OleDbConnection(ConnectionString()))
{
OleDbCommand selectCommand = conn.CreateCommand();// Команда получила соединение
OleDbDataAdapter adapter = new OleDbDataAdapter(selectCommand);
// Загружает данные и схему первой таблицы
selectCommand.CommandText = "SELECT * FROM Employees";
adapter.Fill(ds);
ds.Tables[0].TableName="Empl";
// Загружает данные и схему второй таблицы
selectCommand.CommandText = "SELECT * FROM Products";
adapter.Fill(ds);
ds.Tables[1].TableName = "Prod";
}
return ds;
}
В коде клиента заполненный множественный набор данных можно отобразить, связав его с созданными объектами сетки любым из двух равнозначных способов
// Заполняем множественный набор данными из БД
DataSet ds = LoadDataSet();
// Связываем с сетками
// Первый способ
dataGridView1.DataSource = ds.Tables["Empl"];// Как таблицу
// Второй способ
dataGridView2.DataSource = ds;// Как множественный набор
dataGridView2.DataMember = "Prod";// Уточняем из набора
Набор данных может сохранять загруженные в него объектные
таблицы в файл формата Xml с помощью своего метода WriteXml.
Точно такой же метод имеет и объект таблицы. Существенной разницы
между работой этих методов нет, оба они могут применяться с
дополнительным параметром перечисления XmlWriteMode, который
может указывать своим значением XmlWriteMode.WriteSchema, чтобы
в Xml-файл включалась и схема таблиц. Эти объекты имеют и симметричные
методы DataSet.ReadXml и DataTable.ReadXml. Но здесь есть разница,
для работы метода DataSet.ReadXml не требуется Xml-файл со
схемой.
// Заполняем множественный набор данными из БД
DataSet ds = LoadDataSet();
// Путь к каталогу записи
String path = Application.StartupPath.ToString() + @"\Data\";
// Записываем Xml-файл без схемы
//ds.WriteXml(path + "ds.xml", XmlWriteMode.WriteSchema);
ds.WriteXml(path + "ds.xml");// Без схемы
// Извлекаем данные из Xml-файла с помощью DataSet и показываем
ds = new DataSet();// Создаем новый объект, используем ту же ссылку
ds.ReadXml(path + "ds.xml");// Загружаем из Xml-файла
// Привязываем наборы данных к сеткам для отображения
dataGridView1.DataSource = ds.Tables["Empl"];// Как таблицу
dataGridView2.DataSource = ds;// Как множественный набор
dataGridView2.DataMember = "Prod";// Уточняем из набора
// Удаляем файл (просто так, для примера)
System.IO.File.Delete(path + "ds.xml");
А для метода DataTable.ReadXml требуется файл со схемой
// Заполняем множественный набор данными из БД
DataSet ds = LoadDataSet();
// Сохраняем физически таблицу из набора в другой таблице
DataTable table = ds.Tables["Empl"].Copy();
// Путь к каталогу записи
String path = Application.StartupPath.ToString() + @"\Data\";
// Сохраняем таблицу со схемой в файле Xml
table.WriteXml(path + "Employees.xml", XmlWriteMode.WriteSchema);
//table.WriteXml(path + "Employees.xml"); // Сохраняем без схемы
table = null;// Бросаем объект
table = new DataTable();// Создаем новый объект таблицы
// Читаем таблицу из Xml-файла
table.ReadXml(path + "Employees.xml");
// Показываем таблицу пользователю
dataGridView.DataSource = table;
Но прочитать файл без схемы можно с помощью DataSet.ReadXml
// Заполняем множественный набор данными из БД
DataSet ds = LoadDataSet();
// Сохраняем физически таблицу из набора в другой таблице
DataTable table = ds.Tables["Empl"].Copy();
// Путь к каталогу записи
String path = Application.StartupPath.ToString() + @"\Data\";
// Сохраняем таблицу со схемой в файле Xml
table.WriteXml(path + "Employees.xml");// Записываем без схемы
// Читаем Xml без схемы с помощью набора DataSet
ds = new DataSet();
ds.ReadXml(path + "Employees.xml");// Читает и без схемы
//table.ReadXml(path + "Employees.xml");// Будет сбой при выполнении
// Показываем прочитанную таблицу пользователю
dataGridView.DataSource = ds.Tables[0];// Единственная в коллекции
Ситуацию с чтением DataTable.ReadXml можно исправить, если
при записи сохранять в Xml сразу и схему и данные, но можно
схему сохранить отдельно, а потом перед чтением данных ее загрузить.
// Путь к каталогу записи
String path = Application.StartupPath.ToString() + @"\Data\";
// Объект отображения
DataGridView dataGridView = new DataGridView();
dataGridView.Parent = this;
dataGridView.Dock = DockStyle.Fill;
// Заполняем множественный набор данными из БД
DataSet ds = LoadDataSet();
// Создаем объект таблицы
DataTable table = new DataTable();
// Копируем в таблицу данные из набора, где есть и схема
table = ds.Tables["Empl"].Copy();
// Сохраняем схему в отдельном файле (сейчас или ранее!)
table.WriteXmlSchema(path + "Schema.xml");
// Сохраняем таблицу без схемы
table.WriteXml(path + "Employees.xml");
// Создаем новый чистый объект таблицы
table = new DataTable();
// Читаем схему из файла
table.ReadXmlSchema(path + "Schema.xml");
// Сюда же читаем файл без схемы, теперь можно
table.ReadXml(path + "Employees.xml");
// Показываем пользователю
dataGridView.DataSource = table;
Иногда может потребоваться записать в Xml-формате строки объектной таблицы, каждый в своем файле. Это можно сделать так
// Путь к каталогу записи
String path = Application.StartupPath.ToString() + @"\Data\";
DataSet ds = LoadDataSet();// Заполняем набор из БД
DataSet ds1 = new DataSet();// Вспомогательный DataSet
int i = 0;
foreach (DataRow r in ds.Tables["Empl"].Rows)
{
ds1.Clear();
DataRow[] row = { r };// Merge() ожидает массив строк
ds1.Merge(row);// Добавляем строку из другого набора
ds1.WriteXml(path + "row" + ++i + ".xml");// Записываем в Xml-файл
}
Собрать Xml-строки, сохраненные предыдущим кодом, в набор данных можно так
// Путь к каталогу записи
String path = Application.StartupPath.ToString() + @"\Data\";
// Объект отображения
DataGridView dataGridView = new DataGridView();
dataGridView.Parent = this;
dataGridView.Dock = DockStyle.Fill;
// Заполняем множественный набор данными из БД
DataSet ds = LoadDataSet();
int count = ds.Tables["Empl"].Rows.Count;
ds = new DataSet();// Новый DataSet
for (int i = 0; i < count; i++)
{
DataSet ds1 = new DataSet();
ds1.Clear();
ds1.ReadXml(path + "row" + (i + 1) + ".xml");
DataRow[] row = { ds1.Tables[0].Rows[0] };
ds.Merge(row);// Добавляем в текущий DataSet
}
// Показываем пользователю
dataGridView.DataSource = ds.Tables[0];
В этом упражнении мы познакомились с возможностью ADO.NET хранить наборы данных и таблицы в формате Xml без всякой потери информации. Для устранения рутины весь интерфейс создается динамически, так что можно просто создать новый проект, поместить в него БД из предыдущего упражнения, настроить ее как и ранее, затем скопировать приведенное ниже содержимое и готово...
Вот код упражнения
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
// Дополнительные пространства имен
using System.Data.OleDb;
using System.Data.Common;
using System.Xml;
namespace WinForms4
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.Text = "Упражнение 4";
this.Size = new Size(350, 300);
CreatePage1();
CreatePage2();
CreatePage3();
CreatePage4();
CreatePage5();
CreatePage6();
// Регистрируем обработчик для удаления файлов при закрытии
//this.FormClosed += new FormClosedEventHandler(Form1_FormClosed);
}
void DeleteFile(String name)
{
// Определяем полный путь к хранилищу данных
String path = Application.StartupPath.ToString() + @"\Data\";
if (System.IO.File.Exists(path + name))
System.IO.File.Delete(path + name);
}
void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
// Удаляем файлы, если существуют (для примера)
DeleteFile("ds.xml");
DeleteFile("dsItSchema.xml");
DeleteFile("Employees.xml");
DeleteFile("Products.xml");
}
~Form1()
{
/*
* Можно и в деструктор засунуть удаление файлов
* Но это произойдет не немедленно, а когда
* сборщик мусора доберется до брошенного объекта приложения
* Либо самим вызвать сборщик мусора, вставив в файл Program.cs
* после цикла сообщений код System.GC.Collect();
* Попробуйте!!!
//*/
}
// Строка соединения к БД с абсолютным путем, определяемым сборкой
String ConnectionString()
{
// Используем построитель строки подключения
OleDbConnectionStringBuilder objConnectionStringBuilder =
new OleDbConnectionStringBuilder();
objConnectionStringBuilder.Provider = "Microsoft.Jet.OLEDB.4.0";
objConnectionStringBuilder.DataSource =
Application.StartupPath.ToString() + @"\Data\Northwind.mdb";
// Текущий путь
//System.Environment.CurrentDirectory.ToString()
// Путь к сборке + имя сборки с расширением .exe
//Application.ExecutablePath + @"\Data\Northwind.mdb";
return objConnectionStringBuilder.ToString();
}
DataSet LoadDataSet()
{
DataSet ds= new DataSet();
// Заполняем множественный набор данных из БД
using (OleDbConnection conn = new OleDbConnection(ConnectionString()))
{
OleDbCommand selectCommand = conn.CreateCommand();// Команда получила соединение
OleDbDataAdapter adapter = new OleDbDataAdapter(selectCommand);
//adapter.FillSchema(ds, SchemaType.Source);// Лишнее, загружается вместе с данными
selectCommand.CommandText = "SELECT * FROM Employees";
// Загружает данные и схему (по умолчанию) первой таблицы
// Без маркера может добавиться только одна
// таблица и будет иметь дежурное имя Table
adapter.Fill(ds, "Empl");
// Загружает данные и схему второй таблицы
selectCommand.CommandText = "SELECT * FROM Products";
adapter.Fill(ds, "Prod");
// Чтобы повторно не добавлялось одно и то же
adapter.MissingSchemaAction = MissingSchemaAction.AddWithKey;
adapter.Fill(ds, "Prod");// Не добавится, т.к. установили AddWithKey
//adapter.FillSchema(ds, SchemaType.Source);// Загрузить только схему
}
return ds;
}
TabControl tabControl;
void CreatePage1()
{
// Контейнер вкладок создаем здесь
tabControl = new TabControl();
tabControl.Parent = this;
tabControl.Dock = DockStyle.Fill;
tabControl.Multiline = true;// Если ярлыки вкладок на помещаются
tabControl.ShowToolTips = true;// Разрешить всплывающие посказки
tabControl.SelectedIndex = 5;// Устанавливает начальную вкладку (просто так!)
// Вкладка Page1
TabPage tabPage = new TabPage("Данные из БД");
tabPage.Parent = tabControl;
tabPage.ToolTipText = "Множественный набор,\nполученный из БД";
SplitContainer split = new SplitContainer();
split.Orientation = Orientation.Horizontal;
split.Dock = DockStyle.Fill;
split.Parent = tabPage;
DataGridView dataGridView1 = new DataGridView();
dataGridView1.Dock = DockStyle.Fill;
// Сетки помещаем в панели сплиттера
dataGridView1.Parent = split.Panel1;
DataGridView dataGridView2 = new DataGridView();
dataGridView2.Dock = DockStyle.Fill;
dataGridView2.Parent = split.Panel2;
// Заполняем множественный набор данными из БД
DataSet ds = LoadDataSet();
// Связываем с сетками
// Первый способ
dataGridView1.DataSource = ds.Tables["Empl"];// Как таблицу
// Второй способ
dataGridView2.DataSource = ds;// Как множественный набор
dataGridView2.DataMember = "Prod";// Уточняем из набора
}
void CreatePage2()
{
// Вкладка Page2
TabPage tabPage = new TabPage("Данные из Xml");
tabPage.Parent = tabControl;
tabPage.ToolTipText = "Множественный набор,\nполученный из Xml";
SplitContainer split = new SplitContainer();
split.Orientation = Orientation.Horizontal;
split.Dock = DockStyle.Fill;
split.Parent = tabPage;
DataGridView dataGridView1 = new DataGridView();
dataGridView1.Dock = DockStyle.Fill;
dataGridView1.Parent = split.Panel1;
DataGridView dataGridView2 = new DataGridView();
dataGridView2.Dock = DockStyle.Fill;
dataGridView2.Parent = split.Panel2;
// Заполняем множественный набор данными из БД
DataSet ds = LoadDataSet();
//
// Сохраняем загруженные данные из набора в Xml-файл (Без схемы!!!)
//
// Определяем полный путь к хранилищу данных
String path = Application.StartupPath.ToString() + @"\Data\";
// Множественный набор в Xml-файле не обязательно сохранять вместе
// со схемой, метод DataSet.ReadXml() потом итак будет читать
// Но Xml-файл будет без схемы
//ds.WriteXml(path + "ds.xml", XmlWriteMode.WriteSchema);
ds.WriteXml(path + "ds.xml");
// Извлекаем данные из Xml-файла набора и показываем
ds = null;// Бросаем старый объект
ds = new DataSet();// Создаем новый объект
ds.ReadXml(path + "ds.xml");// Загружаем из Xml-файла
// Привязываем наборы данных к сеткам для отображения
// Первый способ
dataGridView1.DataSource = ds.Tables["Empl"];// Как таблицу
// Второй способ
dataGridView2.DataSource = ds;// Как множественный набор
dataGridView2.DataMember = "Prod";// Уточняем из набора
// Удаляем файл (для примера)
System.IO.File.Delete(path + "ds.xml");
}
void CreatePage3()
{
// Вкладка Page3
TabPage tabPage = new TabPage("Xml без схемы");
tabPage.Parent = tabControl;
tabPage.ToolTipText = "Представление набора\nв Xml без схемы";
WebBrowser browser = new WebBrowser();
browser.Dock = DockStyle.Fill;
browser.Parent = tabPage;
// Заполняем множественный набор данными из БД
DataSet ds = LoadDataSet();
// Сохраняем загруженные данные из набора в Xml-файл (Без схемы!!!)
String path = Application.StartupPath.ToString() + @"\Data\";
ds.WriteXml(path + "ds.xml");
// Извлекаем данные из Xml-файла набора и показываем
browser.Navigate(path + "ds.xml");// Связываемся с Xml-файлом
// Удалять файл нельзя, открыт навигатором в потоковом режиме
//System.IO.File.Delete(path + "ds.xml");
}
void CreatePage4()
{
// Вкладка Page4
TabPage tabPage = new TabPage("Xml со схемой");
tabPage.Parent = tabControl;
tabPage.ToolTipText = "Представление набора\nв Xml со схемой";
WebBrowser browser = new WebBrowser();
browser.Dock = DockStyle.Fill;
browser.Parent = tabPage;
// Заполняем множественный набор данными из БД
DataSet ds = LoadDataSet();
// Сохраняем загруженные данные из набора в Xml-файл (и схема!!!)
String path = Application.StartupPath.ToString() + @"\Data\";
ds.WriteXml(path + "dsItSchema.xml", XmlWriteMode.WriteSchema);
// Извлекаем данные из Xml-файла набора и показываем
browser.Navigate(path + "dsItSchema.xml");// Связываемся с Xml-файлом
}
void CreatePage5()
{
// Вкладка Page5
TabPage tabPage = new TabPage("Таблица в Xml");
tabPage.Parent = tabControl;
tabPage.ToolTipText = "Таблица в Xml-представлении\n"
+ "с включенной схемой\n(иначе не прочесть)";
WebBrowser browser = new WebBrowser();
browser.Dock = DockStyle.Fill;
browser.Parent = tabPage;
String path = Application.StartupPath.ToString() + @"\Data\";
// Читаем Xml-файл множественного набора
// Он сохранен предыдущей вкладкой
DataSet ds = new DataSet();
ds.ReadXml(path + "dsItSchema.xml", XmlReadMode.ReadSchema);
// Создаем новый объект для таблицы Employees
DataTable table = new DataTable();
table = ds.Tables["Empl"].Copy();// Теперь самостоятельная таблица
// Сохраним для следующей вкладки в Xml-файле
table.WriteXml(path + "Employees.xml", XmlWriteMode.WriteSchema);
// Теряем набор сборщику мусора (... для осознанья и просветления!!!)
ds = null;
GC.Collect();// Запустить уборку
GC.WaitForPendingFinalizers();// Ждать конца уборки
// Показываем Xml пользователю
browser.Navigate(path + "Employees.xml");
}
void CreatePage6()
{
// Вкладка Page6
TabPage tabPage = new TabPage("Таблица из Xml");
tabPage.Parent = tabControl;
tabPage.ToolTipText = "Таблица данных,\nполученных из файла Xml";
DataGridView dataGridView = new DataGridView();
dataGridView.Dock = DockStyle.Fill;
dataGridView.Parent = tabPage;
String path = Application.StartupPath.ToString() + @"\Data\";
// Читаем Xml-файл для таблицы Employees
DataTable table = new DataTable();
table.ReadXml(path + "Employees.xml");
// Показываем пользователю
dataGridView.DataSource = table;
}
}
}
Разберитесь
с кодом, создайте проект WinForms4 и поэкспериментируйте
с нимВот пара снимков
![]() |
![]() |
Мы видим, что данные полность восстанавливаются из Xml и могут безбоязненно быть отправлены в БД. Очень удобно брать работу на дом, если сделать небольшое приложение, которое перед уходом скопирует секретные данные в Xml-файл. Всю базу копировать долго и нести тяжело, а пару файлов Xml можно дома опять преобразовать в БД и спокойно работать (особенно шпиенам). Да и переслать по сети будет легче.
Объект XmlDataDocument хранит в себе данные, загруженные из
БД, сразу в двух форматах: обычный набор данных DataSet и Xml-формат
данных. В некоторых случаях это может быть полезным. В этом
упражнении проиллюстрируем некоторые возможности объекта XmlDataDocument.
Добавьте
к приложению новый проект оконного приложения с именем WinForms5
Скопируйте
из предыдущего проекта WinForms4 в корневой узел проекта WinForms5 папку Data вместе с файловой БД Northwind.mdb.
На вопрос мастера конфигурации скажите Cancel
В панели Solution
Explorer выделите файл БД Northwind.mdb и
в панели Properties установите для негоBuild Action=None Copy to Output Directory=Copy
if newer
Добавьте
для корневого узла проекта WinForms5 командой Add/New Item заготовку файла конфигурации приложения с именем App.config
Иногда бывает удобно не прятать строку соединения в код приложения, а вынести ее в конфигурационный файл, чтобы при изменении условий выполнения не исправлять сам код. В этом упражнении мы и применим такой способ. При наличии множества БД можно заготовить несколько строк соединения в конфигурационном файле с разными именами, а в извлекать нужную строку по ее имени без изменения кода самого приложения.
Заполните
конфигурационный файл кодом настройки строки соединения с
БД, значение которой мы будем извлекать из кода приложения
во время выполнения<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<connectionStrings>
<add name="MyNorthwind"
connectionString="Provider=Microsoft.Jet.OLEDB.4.0;Data Source=|DataDirectory|\Data\Northwind.mdb"
providerName="System.Data.OleDb" />
</connectionStrings>
</configuration>
Выделите
корневой узел проекта WinForms5 и командой Project/Add Reference добавьте
к проекту библиотечную сборку System.configuration.dll,
в которой находится нужный нам класс ConfigurationManager для
работы с конфигурационным файлом
Выделите
корневой узел проекта WinForms5 и командой Project/WinForms5 Properties установите
для вкладки Application список Output type в
значение Console Application, чтобы
иметь возможность просматривать информацию стандартного потока
вывода
Заполните
файл Form1.cs проекта WinForms5 следующим
кодомusing System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
// Дополнительные пространства имен
using System.Data.OleDb;
using System.Data.Common;
using System.Xml;
using System.IO;
namespace WinForms5
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
SetInterface();
Load();
}
WebBrowser browser;
DataGridView dataGridView;
void SetInterface()
{
this.Text = "Упражнение 5";
// Создаем сплиттер
SplitContainer split = new SplitContainer();
split.Orientation = Orientation.Horizontal;
split.Dock = DockStyle.Fill;
split.Parent = this;
// Создаем элементы отображения
dataGridView = new DataGridView();
dataGridView.Dock = DockStyle.Fill;
dataGridView.AutoSizeColumnsMode =
DataGridViewAutoSizeColumnsMode.Fill;
dataGridView.RowHeadersWidth = 25;// Служебный столбец
dataGridView.Parent = split.Panel1;
browser = new WebBrowser();
browser.Dock = DockStyle.Fill;
browser.Parent = split.Panel2;
}
DataSet LoadDataSet()
{
// Извлекаем строку соединения из файла App.config
String connectStr = System.Configuration.
ConfigurationManager.ConnectionStrings["MyNorthwind"].ConnectionString;
DataSet ds = new DataSet();
// Заполняем множественный набор данных из БД
using (OleDbConnection conn = new OleDbConnection(connectStr))
{
OleDbCommand selectCommand = conn.CreateCommand();// Команда получила соединение
OleDbDataAdapter adapter = new OleDbDataAdapter(selectCommand);
// Загружает данные и схему первой таблицы
selectCommand.CommandText = "SELECT EmployeeID, " +
"(LastName + ', ' + FirstName) AS FullName, " +
"Address, BirthDate, Region FROM Employees";
adapter.Fill(ds);
}
return ds;
}
XmlDataDocument document;
String path;
new void Load()
{
// Определяем полный путь к хранилищу данных
path = Application.StartupPath.ToString() + @"\Data\";
// Создаем Xml-документ и загружаем его данными
document = new XmlDataDocument(LoadDataSet());
// Связываем с сеткой для отображения
dataGridView.DataSource = document.DataSet;// Только для чтения
dataGridView.DataMember = "Table";// Дежурное имя, когда не задано явно
// Все тонкие ссылки можно объявить типом
// ближайшего абстрактного предка (просто так!)
TextWriter tw = new StringWriter();// Создали поток записи
document.Save(tw); ;// Сохраняем документ в потоке
document.Save(path + "XmlDataDocument.xml");// Сохранили в файле
// Выводим в консольное окно
TextReader tr = new StringReader(tw.ToString());// Создали поток чтения
String str = tr.ToString();// Если ничего нет, то будет null
while (str != null)
{
str = tr.ReadLine();
Console.WriteLine(str);
}
browser.Navigate(path + "XmlDataDocument.xml");
// Регистрируем обработчик события редактирования строки
document.DataSet.Tables[0].RowChanged +=
new DataRowChangeEventHandler(Form1_RowChanged);
}
void Form1_RowChanged(object sender, DataRowChangeEventArgs e)
{
document.Save(path + "XmlDataDocument.xml");
browser.Navigate(path + "XmlDataDocument.xml");
}
}
}
Разберитесь
с кодом упражнения, запустите проект и удостоверьтесь, что
редактирование строк в формате DataSet немедленно
отражается и в Xml-представлении с помощью объекта XmlDataDocument.
Хотя здесь мы и не сохраняем изменения в самой БД
Все необходимые для выполнения данной работы программы можно найти в прилагаемом каталоге.

Разбирайте код и экспериментируйте. Все отлажено и все работает! В непонятных местах кода задавайте вопросы или читайте книги - всего не опишешь. Программирование - это не развлечение, это работа, причем тяжелая. А иначе не научиться и не успеть. Вот, можете повеселиться...
НаноСидоркин, посмотри, какие красивые упражнения! Неужели и тебе будет скучно? Тогда мне действительно пора на пензию.
Данная лабораторная работа основана на мне-е-е и Большом Биле!
Любая БД является хранилищем данных, а ADO.NET является инструментом манипулирования содержимым БД: обеспечивает согласованное извлечение, редактирование, вставку, обновление и удаление данных с минимальными возможностями их потери или искажения.
Вставку, удаление можно делать построчно командами SQL на уровне БД. Редактирование тоже можно делать построчно, блокируя выбранные записи открытым соединением. Тогда проверка ограничений будет выполняться на уровне БД и мы вынуждены будем обрабатывать исключения самой БД.
Но более правильным будет обработка загруженных данных и проверка их целостности на уровне приложения в отсоединенном режиме с последующим сохранением сразу всех изменений в БД. В последнем случае соединение с БД произойдет только дважды: при загрузке данных в приложение и выгрузки их в БД. А вся проверка целостности данных может выполняться в приложении средствами ADO.NET. Такая стратегия называется оптимистическим параллелизмом. Она требует своих дополнительных заморочек, но обеспечивает одновременную работу с БД большого количества клиентов. В Web-программировании без нее просто не обойтись.
Файловая БД имеет свою структуру, определяемую в ее заголовке. Там содержится общая информация служебного характера, описывающая БД в целом. Обычно эту общую информацию называют схемой. В данном упражнении мы научимся извлекать из БД наиболее важные сведения, касающиеся самой БД или отдельных ее составляющих, например, таблиц или столбцов. И эти данные будем называть метаданными ( данные о данных ).
Чтобы сделать описание более кратким (и облегчить жизнь тем, кому скучно), будем стараться избегать этапа визуального проектирования, а все действия по формированию пользовательского интерфейса выполним в коде. В таком случае мне останется только привести отлаженный листинг и конечный снимок окна результата (а вам - лихо, 'по-программистски', скопировать, запустить и повеселиться...).
Создайте новое решение
с именем ADO и добавьте в него новый проект с именем WinForms1
Заполните файл WinForms1.cs следующим
кодомusing System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
// Дополнительные пространства имен
using System.Data.OleDb;
using System.Data.Common;
namespace WinForms1
{
public partial class Form1 : Form
{
// Объявили поля-ссылки для видимости в методах
TabControl tabControl;
DataTable schemaTable;
ComboBox cbCollections, cbTables;
DataGridView dataGridView1, dataGridView2;
// Конструктор формы
public Form1()
{
InitializeComponent();
this.Text = "Упражнение 1. Извлечение метаданных";
this.WindowState = FormWindowState.Maximized;
CreateControl();// Динамически создать пользовательский интерфейс
// Загрузить списки во вкладках
LoadCollections();
LoadTables();
}
// Имя метода пересекается с наследуемым (для разнообразия)
new void CreateControl()
{
// Динамическое создание элементов управления
tabControl = new TabControl();
this.Controls.Add(tabControl);
tabControl.Dock = DockStyle.Fill;
TabPage page1 = new TabPage("Коллекции");
tabControl.TabPages.Add(page1);// Добавили вкладку
TabPage page2 = new TabPage("Таблицы");
//tabControl.TabPages.Add(page2);// Добавили вкладку
page2.Parent = tabControl;// Добавили вкладку (равноценно!!!)
// Для динамического выравнивания элементов на первой вкладке
TableLayoutPanel layoutPanel1 = new TableLayoutPanel();
page1.Controls.Add(layoutPanel1);// Добавили панель к вкладке
layoutPanel1.Dock = DockStyle.Fill;
// Для динамического выравнивания элементов на второй вкладке
TableLayoutPanel layoutPanel2 = new TableLayoutPanel();
page2.Controls.Add(layoutPanel2);// Добавили панель к вкладке
layoutPanel2.Dock = DockStyle.Fill;
cbCollections = new ComboBox();
cbCollections.Dock = DockStyle.Fill;
layoutPanel1.Controls.Add(cbCollections);// Добавили список к панели
// Зарегистрировали обработчик для первого списка
cbCollections.SelectedIndexChanged += cbCollections_SelectedIndexChanged;
// Объект отображения данных
dataGridView1 = new DataGridView();// Создаем
layoutPanel1.Controls.Add(dataGridView1);// Добавили сетку к панели
dataGridView1.Dock = DockStyle.Fill;
dataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
cbTables = new ComboBox();
cbTables.Dock = DockStyle.Fill;
layoutPanel2.Controls.Add(cbTables);// Добавили список к панели
// Зарегистрировали обработчик для второго списка
cbTables.SelectedIndexChanged += cbTables_SelectedIndexChanged;
// Объект отображения данных
dataGridView2 = new DataGridView();// Создаем
layoutPanel2.Controls.Add(dataGridView2);// Добавили сетку к панели
dataGridView2.Dock = DockStyle.Fill;
dataGridView2.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
}
// Строка соединения к БД с абсолютным путем, определяемым сборкой
String ConnectionString()
{
// Используем построитель строки подключения
OleDbConnectionStringBuilder objConnectionStringBuilder =
new OleDbConnectionStringBuilder();
objConnectionStringBuilder.Provider = "Microsoft.Jet.OLEDB.4.0";
objConnectionStringBuilder.DataSource =
Application.StartupPath.ToString() + @"\Data\Northwind.mdb";
return objConnectionStringBuilder.ToString();
}
// Извлекаем в список имена всех коллекций БД
void LoadCollections()
{
using (OleDbConnection connection = new
OleDbConnection(ConnectionString()))
{
connection.Open();
schemaTable = connection.GetSchema();
}
foreach (DataRow row in schemaTable.Rows)
{
Object col = row["CollectionName"].ToString();
cbCollections.Items.Add(col);
}
// Несвязанный список, можно сортировать
cbCollections.Sorted = true;
cbCollections.SelectedIndex = 0;
}
// Заполняем список именами таблиц БД
void LoadTables()
{
// Создали, заполнили, скопировали в список и потеряли
DataTable table = new DataTable();// Локальная ссылка на объект
// Соединение закрывается после выхода из инструкции using
using (OleDbConnection connection = new
OleDbConnection(ConnectionString()))
{
connection.Open();
// Выбираем коллекцию только для таблиц
table = connection.GetOleDbSchemaTable(
OleDbSchemaGuid.Tables,
new object[] { null, null, null, "TABLE" });
}
// Значения столбца TABLE_NAME скопировали в ComboBox
foreach (DataRow row in table.Rows)
{
Object col = row["TABLE_NAME"].ToString();
cbTables.Items.Add(col);
}
// Несвязанный список, можно сортировать
cbTables.Sorted = true;
cbTables.SelectedIndex = 0;// Установили список в начало
}
// Извлекаем информацию о выделенной коллекции
void cbCollections_SelectedIndexChanged(object sender, EventArgs e)
{
schemaTable.Clear();// Очистим от предыдущего
using (OleDbConnection connection = new
OleDbConnection(ConnectionString()))
{
connection.Open();
schemaTable = connection.GetSchema(cbCollections.SelectedItem.ToString());
}
// Связываем таблицу и элемент отображения
dataGridView1.DataSource = schemaTable;
}
// Извлекаем информацию о выделенной таблице
void cbTables_SelectedIndexChanged(object sender, EventArgs e)
{
// Все ссылки стековые
String selectCommand = "";
String tableName = cbTables.SelectedItem.ToString();
if (tableName.IndexOf(' ') != -1)// Для таблицы 'Order Details'
selectCommand = "SELECT * FROM " + "[" + tableName + "]";
else
selectCommand = "SELECT * FROM " + tableName;
OleDbConnection connection = new OleDbConnection(ConnectionString());
OleDbCommand objCommand = new OleDbCommand(selectCommand, connection);
using (connection)// Заключаем в блок автоматического закрытия соединения при аварии
{
connection.Open();
// Выполняем объект DbCommand одним из его методов
OleDbDataReader dataReader = objCommand.ExecuteReader();
// Извлечь метаданные таблицы и сразу привязать для показа
dataGridView2.DataSource = dataReader.GetSchemaTable();
connection.Close();
}
}
}
}
Разберите код и запустите
приложениеМетаданные, составляющие описание всей БД, мы извлекаем через объект
соединения.
Метод GetSchemaTable() объекта OleDbDataReader возвращает
метаданные о каждом из столбцов в таблице.
Обратите внимание, что мы применили в качестве контейнеров элементов отображения
два класса: TabControl и TableLayoutPanel. Класс TabControl изолирует
в отдельных вкладках только интерфейс пользователя, но не изолирует код этих
вкладок - все объекты, составляющие вкладки, должны иметь уникальные имена
в рамках всего объекта TabControl. Класс TableLayoutPanel обеспечивает
динамическое размещение в невидымых ячейках таблицы, наподобие таблицы HTML.
Естественно, каждый контейнер имеет свои настройки, но мы воспользовались только
настройками по умолчанию.
Очень полезен следующий снимок экрана, показывающий принятые
в Access типы данных и их соответствие типам .NET. Большинство
данных в БД хранятся в символьном виде и занимают ровно столько
места, сколько требуется для хранения типа, например, Int16=2^16=256*256=65536
(пять знакомест), Int32=2^32=65536*65536=4294967296 (десять
знакомест), DateTime=Год_месяц_день (в символьном представлении
цифр занимает 8 знакомест). Тип DateTime называется так потому,
что в одних колонках этого типа можно хранить дату, а в других
- время.

Естественно, что типы данных в C# и типы данных в БД несовместимы. Одни называют объектными типами (типы уровня приложения), другие - реляционными типами (типы уровня БД). Однако функции API инфраструктуры ADO.NET автоматически преобразовывают их один в другой как при загрузке данных в приложение, так и при отправке их обратно в БД. Говорят, что поставщики ADO.NET отображают реляционные типы на объектные и наоборот. Здесь есть своя специфика, которую нужно учитывать при проектировании более изощренных приложений. В менее тонких случаях все происходит автоматически.
Тип данных LongBinary еще называют типом BLOB (Binary
Large Object - большой объект двоичных данных), а тип LongText - (Character
Large Object - большой объект символьных данных). В SQL Server
такие типы носят имя IMAGE и TEXT соответственно. Для
обработки больших типов данных, которые могут не поместиться
целиком в оперативной памяти, могут применяться специальные
приемы, связанные с потоковым доступом к данным и использованием
дисковой памяти.
Используя метаданные, заполняющие список именами таблиц, мы можем создать приложение, которое будет читать содержимое любой таблицы БД, выбранной пользователем в диалоговом режиме.
Вот и все, а дальше код еще одной вкладки для этого упражнения ...!!!
Добавьте к проекту WinForms1 новый пустой файл (шаблон Code File) с именем Form1Ext.cs и
заполните его следующим кодомusing System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
// Дополнительные пространства имен
using System.Data.OleDb;
using System.Data.Common;
namespace WinForms1
{
partial class Form1
{
// Объявили поля-ссылки для видимости в методах
ComboBox cbTables1;
DataGridView dataGridView;
// Создаем вкладку и ее элементы
void CreateControl1()
{
// Динамическое создание элементов управления
TabPage page = new TabPage("Данные");
tabControl.TabPages.Add(page);// Добавили вкладку
// Для динамического выравнивания элементов
TableLayoutPanel layoutPanel = new TableLayoutPanel();
page.Controls.Add(layoutPanel);// Добавили панель к вкладке
layoutPanel.Dock = DockStyle.Fill;
cbTables1 = new ComboBox();
cbTables1.Dock = DockStyle.Fill;
layoutPanel.Controls.Add(cbTables1);// Добавили список к панели
// Зарегистрировали обработчик для списка
cbTables1.SelectedIndexChanged += cbTables1_SelectedIndexChanged;
// Объект отображения данных
dataGridView = new DataGridView();// Создаем
layoutPanel.Controls.Add(dataGridView);// Добавили сетку к панели
dataGridView.Dock = DockStyle.Fill;
dataGridView.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
}
// Заполняем список именами таблиц БД
void LoadTables1()
{
// Создали, заполнили, скопировали в список и потеряли
DataTable table = new DataTable();// Локальная ссылка на объект
// Соединение закрывается после выхода из инструкции using
using (OleDbConnection connection = new
OleDbConnection(ConnectionString()))
{
connection.Open();
// Выбираем коллекцию только для таблиц
table = connection.GetOleDbSchemaTable(
OleDbSchemaGuid.Tables,
new object[] { null, null, null, "TABLE" });
}
// Значения столбца TABLE_NAME скопировали в ComboBox
foreach (DataRow row in table.Rows)
{
Object tableName = row["TABLE_NAME"].ToString();
cbTables1.Items.Add(tableName);
}
// Несвязанный список, можно сортировать
cbTables1.Sorted = true;
cbTables1.SelectedIndex = 0;// Установили список в начало
}
// Извлекаем все данные выделенной таблицы
void cbTables1_SelectedIndexChanged(object sender, EventArgs e)
{
// Все ссылки стековые
String selectCommand = "";
String tableName = cbTables1.SelectedItem.ToString();
if (tableName.IndexOf(' ') != -1)// Для таблицы 'Order Details'
selectCommand = "SELECT * FROM " + "[" + tableName + "]";
else
selectCommand = "SELECT * FROM " + tableName;
OleDbConnection connection = new OleDbConnection(ConnectionString());
OleDbCommand objCommand = new OleDbCommand(selectCommand, connection);
OleDbDataAdapter adapter = new OleDbDataAdapter(objCommand);
DataTable dataTable=new DataTable();
adapter.Fill(dataTable);
dataGridView.DataSource = dataTable;
}
}
}
Дополните конструктор
класса Form1 вызовом
методов из файла Form1Ext.cs// Конструктор формы
public Form1()
{
InitializeComponent();
this.Text = "Упражнение 1. Извлечение метаданных";
this.WindowState = FormWindowState.Maximized;
CreateControl();// Динамически создать пользовательский интерфейс
// Загрузить списки во вкладках
LoadCollections();
LoadTables();
CreateControl1();// Динамически создать пользовательский интерфейс
// Загрузить список в новой вкладке
LoadTables1();
}
Запустите приложение
- теперь в новой вкладке мы можем просматривать содержимое выбранной из БД
таблицы
В данном упражнении рассмотрим возможности инфраструктуры
ADO.NET по вставке и удалению записей данных в одной таблице.
Но прежде немного поболтаем... Основными объектами, обеспечивающими
функциональность манипулирования данными, являются набор реляционных
данных DataSet и поставщик данных DataAdapter. Хотя под поставщиком
подразумевают несколько объектов, включая адаптер, объект соединения,
построитель команд и др. Оба этих объекта реализуют отсоединенный режим
доступа к данным, когда соединение с БД устанавливается только
на момент чтения данных и сохранение изменений.
Применительно к классу принято употреблять равнозначные термины - класс или объект, поскольку и то и другое характеризует одну и ту же программную сущность: 'класс' - на этапе проектирования, 'объект-тип' - на этапе выполнения.

В основе класса DataSet лежат принципы реляционной модели данных (таблицы,
связанные отношениями), система типов CLR (Common Language Runtime) и коллекции
.NET. Кроме содержания собственно данных, объект DataSet содержит метаданные (данные общего характера), которые включают в себя описание таблиц, столбцов,
ограничений и отношений и называются схемой. Объект DataSet может
обмениваться данными с поставщиками DataAdapter, с поставщиками XML (Exchanged
Markup Language) или формировать схему и данные 'налету' в программном коде.
Класс DataSet не зависит от специфики реляционной БД, с которой он общается
через специализированные поставщики данных. Его часто называют объектной
моделью реляционной БД. Это сложный класс, включающий в свой состав
множество других классов, поэтому многие считают, что он слишком переполнен
функциональными возможностями. DataSet считается аналогом реляционной БД, но
находящейся в оперативной памяти.
Набор данных DataSet состоит из объектов таблиц DataTable.
Таблицы могут иметь имена, первичные и внешние ключи, отношения
и уникальные ограничения. Таблицы, в свою очередь, состоят
из объектов столбцов DataColumn и объектов строк DataRow. Для
каждого столбца задается ряд характеристик: имя, тип, ключ,
автозаполнение и др. К столбцам таблицы можно обращаться по
имени или индексу. Строки не имеют имен и адресуются
только индексом.
Коллекции, входящие в набор DataSet, можно связывать с теми элементами управления
Windows-форм, которые имеют свойство DataSource: ComboBox, ListBox, CheckedListBox, DataGrid, DataGridView, DataGridViewComboBoxColumn, DataGridViewComboBoxCell.
События класса DataSet позволяют упростить настройку механизма обновления посредством
внешних источников данных.
Мы долго говорили про объект DataSet и еще будем говорить.
Но в данном упражнении мы будем использовать только объектную
таблицу и на ее снове построим приложение, которое
будет добавлять или удалять новые записи в реляционную
таблицу БД. Попутно усложним приложение некоторыми
приемами работы с коллекциями, динамическим созданием элементов
пользовательского интерфейса, разбивкой кода на отдельные блоки
(и некоторой иной полезной чушью). При этом следует помнить,
что чем большую долю по обработке данных вы выполните на уровне
приложения, тем меньшую нагрузку будет испытывать БД, если
бы ту же работу выполнять SQL-командами на уровне самой БД.
Весь код упражнения подробно прокомментирован в листингах. Специально оставлен альтернативный закомментированный код. Кому скучно, лень разбираться или ЭГО не позволяет, запустите прилагаемый проект, познакомтесь с функциональностью и попробуйте создать подобное 'с нуля'. Лично я писал это упражнение почти неделю (ох и повеселился!) и оно мне порядком поднадоело, поэтому приведу только готовый код, а разбор оставлю до лучших времен (когда буду учить ваших внуков!).
![]() |
Итак, автобусом до сходней доезжаем, а там трусцой, и не стонать. Небось картошку все мы уважаем, когда с сальцой ее намять! ( Высоцкий В.С.) |
Добавьте к решению ADO новый проект оконного приложения с именем WinForms2 и назначьте его стартовым
В панели Solution
Explorer курсором мыши перетащите каталог дата из предыдущего проекта WinForms1 на
узел нового проекта WinForms2, мастеру настройки конфигурации скажите Cancel
В панели Solution Explorer выделите файл БД Northwind.mdb и
в панели Properties установите
для негоBuild Action=None Copy to Output Directory=Copy if newer Второй параметр укажет оболочке, чтобы она копировала
этот файл из каталога проекта в каталог готовой сборки bin при
первом запуске (когда там его вообще нет), и потом только
тогда, когда в проекте окажется более новый файл. Первый
параметр указывает, чтобы оболочка ничего с файлом больше
не делала. При установки других опций второго параметра файл
БД либо не будет скопирован вообще и откомпилированное и
запущенное приложение его не найдет, либо мы не найдем
своих измененных данных в БД (если будем запускать приложение через
оболочку), потому что при каждом новом запуске оболочка будет
копировать в bin исходный
файл БД из каталога проекта. Но если запускать готовую сборку
напрямую, то она будет работать с одной и той же соединенной
БД и изменения мы увидим как положено.
Еще более чудное поведение оболочки наблюдается в случае
создания самой БД и добавления в нее нужной структуры (таблиц,
столбцов, ключей, представлений, ограничений и т.д). Для этих
целей в управляемом приложении используются 2 неуправляемых
COM-объекта ADOX и ADODB (мы
в дальнейшем с ними познакомимся, когда будем создавать свою
БД прямо из кода C#). Там еще смешнее: запускаешь код создания
БД через оболочку, открываешь Windows Explorer, смотришь, что
файл создан и существует, однако через некоторое время он бесследно
исчезает или заменяется старой версией.
Знатоки говорят, что это сделано специально, чтобы в период отладки не запортить настоящую БД. Но если запустить сборку, то все будет работать нормально. В данном упражнении подобное свойство оболочки нам может пригодиться, если после манипуляций с БД через оболочку мы захотим все вернуть к прежнему состоянию. Нужно лишь установить системное время компьютера в прошлое, запустить проект, внести изменения в БД и сохранить их. Файл будет помечен прошлым временем и при последующем запуске проекта оболочка восстановит его исходным файлом из каталога проекта, посчитав оригинал более свежим.
Следует иметь ввиду, что при увеличении размеров данных, сохраняемых в файловой БД, сам файл при каждом сохранении меняет в своем заголовке только время обновления. А физическое увеличение размеров БД выполняется адаптером автоматически и дискретно - с некоторым запасом, чтобы не перезаписывать файл БД на новое место при каждом обновлении данных, как это бывает с обычными файлами.
Заполните
файл Form1.cs проекта WinForms2 следующим кодомusing System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
// Дополнительные пространства имен
using System.Data.OleDb;
using System.Data.Common;
namespace WinForms2
{
public partial class Form1 : Form
{
// Инфраструктура для интерфейса первой вкладки
TabControl tabControl;
ComboBox cbLetters;
Button button;
ListBox listBox;
// Инфраструктура для доступа к БД
OleDbConnection connection;
OleDbDataAdapter adapter;
DataTable tableCustomers;
public Form1()
{
InitializeComponent();
this.Text = "Упражнение 2. Построчное редактирование";
this.Width = 355;
this.FormClosed += Form1_FormClosed;
// Добавили панель к форме
this.Controls.Add(tabControl = new TabControl());
tabControl.Dock = DockStyle.Fill;
// Создать интерфейс пользователя
CreatePage1();
CreatePage2();
// Заполнить cbLetters, полную таблицу и связать с listBox
LoadPage1();
}
void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
// Выполнена ли функция tableCustomers.AcceptChanges(),
// меняющая состояние таблицы при обновлении в this.Update()
DataTable table = tableCustomers.GetChanges();
if (table != null)
{
DialogResult result =
MessageBox.Show("Вы не сохранили изменения в БД\nСохранить?",
"Внимание!",
MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
if (result == DialogResult.Yes)
Update();
}
}
// Строка соединения к БД с абсолютным путем, определяемым сборкой
String ConnectionString()
{
// Используем построитель строки подключения
OleDbConnectionStringBuilder objConnectionStringBuilder =
new OleDbConnectionStringBuilder();
objConnectionStringBuilder.Provider = "Microsoft.Jet.OLEDB.4.0";
objConnectionStringBuilder.DataSource =
Application.StartupPath.ToString() + @"\Data\Northwind.mdb";
return objConnectionStringBuilder.ToString();
}
void LoadPage1()
{
// Очищаем список, иначе будет накопление
cbLetters.Items.Clear();
// Блоки кода для читабельности, но видимость переменных не скрывают
{ // Загружаем раскрывающийся список
// Добавили первый элемент списка
cbLetters.Items.Add("All");
// Дополняем список буквами алфавита
for (char ch = 'A'; ch <= 'Z'; ch++)
{
cbLetters.Items.Add(ch);
}
// Устанавливаем на первый индекс
cbLetters.SelectedIndex = 0;
}
{ // Загружаем таблицу Customers целиком
connection = new OleDbConnection(ConnectionString());
adapter = new OleDbDataAdapter(
new OleDbCommand("SELECT * FROM Customers", connection));
tableCustomers = new DataTable("Customers");
// Добавляем (до загрузки!!!) вычислимые столбцы Tab и ViewColumn
// для отображения одного из них в listBox
tableCustomers.Columns.Add(new DataColumn("CustomerID", typeof(String)));
tableCustomers.Columns.Add(new DataColumn("Tab", typeof(String)));
tableCustomers.Columns["Tab"].DefaultValue = "\t - ";// Значение по умолчанию
tableCustomers.Columns.Add(new DataColumn("CompanyName", typeof(String)));
tableCustomers.Columns.Add(new DataColumn("ViewColumn", typeof(String)));
// Компонуем столбец
tableCustomers.Columns["ViewColumn"].Expression = "CustomerID + Tab + CompanyName";
adapter.Fill(tableCustomers);// Сам откроет соединение и сам закроет
// Указать первичный ключ из одного столбца для использования метода Find()
tableCustomers.PrimaryKey =
new DataColumn[] { tableCustomers.Columns["CustomerID"] };
// Не разрешать ввод пустых значений в поле CustomerID
//tableCustomers.Columns["CustomerID"].AllowDBNull = false;
}
}
// Вся таблица находится в кэше в отсоединенном режиме
// и поэтому обрабатывается быстро
// Тот же самый результат можно было получить на уровне БД
// с помощью SQL-запросов, но это намного медленнее!!!
void cbLetters_SelectedIndexChanged(object sender, EventArgs e)
{
// Еще ничего не создано или не выбрано
// Условия проверяются слева направо до первого true
if (cbLetters == null || cbLetters.SelectedIndex == -1
|| tableCustomers == null)
return;
// Извлекаем символ из cbLetters
String letter = cbLetters.SelectedItem.ToString();
if (letter == "All")
letter = String.Empty;
// Создаем для таблицы Customers объект представления и настраиваем его
DataView view = new DataView(tableCustomers);
view.RowFilter = "CustomerID LIKE '" + letter + "%'";
view.Sort = "CustomerID";
// Заполняем список ListBox столбцами CustomerID и скомпанованным
// с учетом значения фильтра в cbLetters
listBox.DataSource = view;// Связываем
listBox.DisplayMember = "ViewColumn";// Отображаем вычислимый столбец
listBox.ValueMember = "CustomerID"; // Невидимый параметр
}
void button_DeleteClick(object sender, EventArgs e)
{
// Если нечего удалять
if (cbLetters.SelectedIndex == -1 || cbLetters.Items.Count <= 0)
return;
// Ищем текущую строку в таблице по CustomerID
Object findValue = listBox.SelectedValue;// Выделенное значение ValueMember
DataRow row = tableCustomers.Rows.Find(findValue);
// Помечаем строку как удаленную
row.Delete();
}
// Отменить текущие изменения таблицы
void button_RestoreClick(object sender, EventArgs e)
{
tableCustomers.RejectChanges(); ;
}
// Отправить текущие изменения таблицы в базу. Если убрать new,
// то будет вызываться унаследованная функция Control.Update()
new void Update()
{
// Создаем построитель команды по настройкам нашего адаптера
OleDbCommandBuilder commandBuilder =
new OleDbCommandBuilder(adapter);
// Не проверять наличие чужих изменений,
// возможно внесенных другими со времени последней
// выборки данных, а записывать своей UpdateCommand поверх
commandBuilder.ConflictOption = ConflictOption.OverwriteChanges;
//commandBuilder.RefreshSchema();
// Построитель команд сам сгенерирует нужные команды,
// прочитав информацию в свойстве SelectCommand адаптера
//adapter.InsertCommand = commandBuilder.GetInsertCommand();
//adapter.UpdateCommand = commandBuilder.GetUpdateCommand();
//adapter.DeleteCommand = commandBuilder.GetDeleteCommand();
// Если соединение открыть, то адаптер его сам не закроет
//connection.Open();
// Адаптер сам откроет соединение, сам его закроет,
// сам переведет таблицу в разряд немодифицированных
adapter.Update(tableCustomers);
//adapter.InsertCommand.Connection.Close();
// Перевести таблицу в разряд немодифицированных
//tableCustomers.AcceptChanges();
}
// Отправить текущие изменения таблицы в базу
void button_UpdateClick(object sender, EventArgs e)
{
Update();
}
DataRow newRow = null;// Проверяется обработчиками кнопок
void button_CurrentClick(object sender, EventArgs e)
{
// Если ничего не выделено как текущее
if (listBox.SelectedIndex == -1)
return;
// А не включен ли режим добавления новой строки
// (а не испить ли нам кофею!!!)
if (newRow != null)
{
MessageBox.Show("Редактируйте добавленную\n"
+ "строку или отмените ее", "Ошибка",
MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
// Ищем текущую строку в таблице по CustomerID
Object findValue = listBox.SelectedValue;// Выделенное значение ValueMember
DataRow row = tableCustomers.Rows.Find(findValue);
// Заполняем текстовые поля значениями полей текущей строки
foreach (TextBox tb in listReference)
{
// Имена столбцов ранее записали в свойство tb.Name;
tb.Text = row[tb.Name].ToString();
}
}
// Добавление новой строки
void button_NewRowClick(object sender, EventArgs e)
{
if (newRow != null)
{
MessageBox.Show("Вы не отменили или \n"
+ "не сохранили предыдущую строку", "Ошибка",
MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
// Меняем цвет текстовых полей
ChangeColor(Colors.EditColor);
// Очищаем текстовые поля
foreach (TextBox tb in listReference)
{
tb.Text = String.Empty;
}
// Создаем новую строку по схеме таблицы
newRow = tableCustomers.NewRow();
}
void button_AddRowClick(object sender, EventArgs e)
{
if (newRow == null)
{
MessageBox.Show("Вы не добавили новую строку", "Ошибка",
MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
// Заполняем поля новой строки данными из TextBox
newRow.BeginEdit(); // Отключаем механизм проверки ограничений
foreach (TextBox tb in listReference)
{
if (tb.Text == String.Empty)// Пустые строки не принимает
newRow[tb.Name] = System.DBNull.Value;
else
newRow[tb.Name] = tb.Text;
}
if (newRow["CustomerID"].ToString() == String.Empty)
{
MessageBox.Show("Поле CustomerID не должно быть пустым",
"Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
// Возможно нарушение ввода, поэтому упаковываем в проверку
try
{
newRow.EndEdit();// Пробуем включить проверку ограничений
// Добавляем строку в таблицу
tableCustomers.Rows.Add(newRow);
}
catch
{
MessageBox.Show("Поле CustomerID должно быть уникальным",
"Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
newRow = null;
// Восстанавливаем цвет текстовых полей
ChangeColor(Colors.RestoreColor);
// Очищаем текстовые поля
foreach (TextBox tb in listReference)
{
tb.Text = String.Empty;
}
}
void button_ClearClick(object sender, EventArgs e)
{
// Обнуляем новую строку
newRow = null;
// Очищаем текстовые поля
foreach (TextBox tb in listReference)
{
tb.Text = String.Empty;
}
// Восстанавливаем цвет
ChangeColor(Colors.RestoreColor);
}
enum Colors
{
EditColor,
RestoreColor
}
// Изменяем цвет текстовых полей
void ChangeColor(Colors color)
{
switch (color)
{
case Colors.EditColor:
foreach (TextBox tb in listReference)
tb.BackColor = Color.Aqua;
break;
case Colors.RestoreColor:
foreach (TextBox tb in listReference)
tb.BackColor = Color.White;
break;
}
}
}
}
Добавьте
к проекту WinForms2 новый пустой файл (шаблон Code
File) с именем CreateInterface.cs и
заполните его кодом динамического создания интерфейса
пользователяusing System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace WinForms2
{
partial class Form1
{
// Динамическое создание элементов первой вкладки
void CreatePage1()
{
TabPage page = new TabPage("Строки");
tabControl.TabPages.Add(page);// Добавили вкладку к панели
// Табличная панель для вертикального выравнивания элементов
TableLayoutPanel tableLayoutPanel = new TableLayoutPanel();
page.Controls.Add(tableLayoutPanel);// Добавили табличную панель к вкладке
tableLayoutPanel.Dock = DockStyle.Fill;
// Рисуем рамку вокруг панели (для красоты)
tableLayoutPanel.CellBorderStyle = TableLayoutPanelCellBorderStyle.Single;
// Панель горизонтального выравнивания
// верхней ячейки табличной панели
FlowLayoutPanel flowLayoutPanel = new FlowLayoutPanel();
flowLayoutPanel.Dock = DockStyle.Top;
flowLayoutPanel.AutoSize = true;
tableLayoutPanel.Controls.Add(flowLayoutPanel);
// Заполняем горизонтальную панель
// Для наглядности группируем в блоки кода
{
// Create ComboBox
flowLayoutPanel.Controls.Add(cbLetters = new ComboBox());// Создаем и добавляем
cbLetters.Anchor = AnchorStyles.None;// Выравнивание по центру родителя
cbLetters.Width = 80;// Зададим ширину
cbLetters.KeyDown += new KeyEventHandler(cbLetters_KeyDown);// Не редактировать
cbLetters.KeyPress += new KeyPressEventHandler(cbLetters_KeyPress);// Не редактировать
cbLetters.SelectedIndexChanged += cbLetters_SelectedIndexChanged;// Выбрать
cbLetters.BindingContextChanged += cbLetters_SelectedIndexChanged;// Для начальной загрузки
// Всплывающая подсказка
ToolTip toolTip = new ToolTip();
String message = "Фильтр записей таблицы";
toolTip.SetToolTip(cbLetters, message);
toolTip.IsBalloon = true;
//toolTip.ToolTipIcon = ToolTipIcon.Info;
//toolTip.ForeColor = Color.Magenta;
//toolTip.BackColor = Color.LightGoldenrodYellow;
}
{
// Create Button
flowLayoutPanel.Controls.Add(button = new Button());// Создаем и добавляем
button.Anchor = AnchorStyles.None;// Выравнивание по центру родителя
button.Text = "Delete";
button.AutoSize = true;
// Регистрируем обработчик события Click
button.Click += button_DeleteClick;
// Всплывающая подсказка
ToolTip toolTip = new ToolTip();
String message = "Удаление текущей записи";
toolTip.SetToolTip(button, message);
toolTip.IsBalloon = true;
// Create Button
button = new Button();
button.Parent = flowLayoutPanel; // Добавляем к табличной панели
button.Anchor = AnchorStyles.None;// Выравнивание по центру родителя
button.Text = "Restore";
button.AutoSize = true;
// Регистрируем обработчик события Click
button.Click += button_RestoreClick;
// Всплывающая подсказка
toolTip = new ToolTip();
message = "Восстановление\nнесохраненных\nизменений";
toolTip.SetToolTip(button, message);
toolTip.IsBalloon = true;
// Create Button
button = new Button();
button.Parent = flowLayoutPanel; // Добавляем к табличной панели
button.Anchor = AnchorStyles.None;// Выравнивание по центру родителя
button.Text = "Update";
button.AutoSize = true;
// Регистрируем обработчик события Click
button.Click += button_UpdateClick;
// Всплывающая подсказка
toolTip = new ToolTip();
message = "Запись в БД";
toolTip.SetToolTip(button, message);
toolTip.IsBalloon = true;
}
// Список размещаем под горизонтальной панелью
tableLayoutPanel.Controls.Add(listBox = new ListBox());
listBox.Dock = DockStyle.Fill;
listBox.Margin = new Padding(0);// Убираем отступы вокруг списка
tableLayoutPanel.Padding = new Padding(0);// Убираем отступы внутри панели
}
void cbLetters_KeyPress(object sender, KeyPressEventArgs e)
{
e.Handled = true;
}
void cbLetters_KeyDown(object sender, KeyEventArgs e)
{
e.Handled = true;
}
// Динамическое создание элементов второй вкладки
void CreatePage2()
{
TabPage page = new TabPage("Поля");
tabControl.TabPages.Add(page);// Добавили вкладку к панели
TableLayoutPanel tablePanel = new TableLayoutPanel();// Табличная панель
tablePanel.Parent = page;// Назначили родителя
tablePanel.Dock = DockStyle.Fill;// На всю вкладку
FlowLayoutPanel flowPanel = new FlowLayoutPanel();// Горизонтальная панель
flowPanel.Parent = tablePanel;// Назначили родителя
flowPanel.Dock = DockStyle.Top;// Пристыковали вверх к родителю
flowPanel.AutoSize = true;// По размеру содержимого
flowPanel.BorderStyle = BorderStyle.Fixed3D;// Красиво тесненная
TableLayoutPanel tmp = new TableLayoutPanel();
tmp.Parent = tablePanel;
tmp.Dock = DockStyle.Fill;
tablePanel = tmp;// Бросили внешнюю панель
tmp = null;
//tablePanel.CellBorderStyle = TableLayoutPanelCellBorderStyle.Single;// Рамка
// Заполняем верхнюю панель
Button button = new Button();
button.Text = "Current";
button.AutoSize = true;
button.Parent = flowPanel;
button.Click += button_CurrentClick;
// Всплывающая подсказка
ToolTip toolTip = new ToolTip();
String message = "Заполнение полей\nзначениями текущей записи";
toolTip.SetToolTip(button, message);
toolTip.IsBalloon = true;
button = new Button();
button.Text = "NewRow";
button.AutoSize = true;
button.Parent = flowPanel;
button.Click += button_NewRowClick;
// Всплывающая подсказка
toolTip = new ToolTip();
message = "Создание новой записи";
toolTip.SetToolTip(button, message);
toolTip.IsBalloon = true;
// Используем ту же ссылку для создания другой кнопки
button = new Button();
button.Text = "AddRow";
button.AutoSize = true;
button.Parent = flowPanel;
button.Click += button_AddRowClick;
// Всплывающая подсказка
toolTip = new ToolTip();
message = "Сохранение новой\nзаписи в таблице";
toolTip.SetToolTip(button, message);
toolTip.IsBalloon = true;
button = new Button();
button.Text = "Clear";
button.AutoSize = true;
button.Parent = flowPanel;
button.Click += button_ClearClick;
// Всплывающая подсказка
toolTip = new ToolTip();
message = "Очистить поля/Отменить запись";
toolTip.SetToolTip(button, message);
toolTip.IsBalloon = true;
Label label = new Label();
label.Text = "Поля для редактирования записи:";
label.AutoSize = true;
label.Parent = tablePanel;
CreateTextBox(tablePanel);
// Собираем ссылки на все TextBox формы, так как
// на форме нет других TextBox, ненужных нам!!!
FindTextBox(this.Controls);
}
// Динамически добавляем поля во вторую вкладку
void CreateTextBox(TableLayoutPanel panel)
{
String[] columnName = {
"CustomerID",
"CompanyName",
"Address",
"City",
"Phone"
};
for (int i = 0; i < columnName.Length; i++)
{
FlowLayoutPanel flowPanel = new FlowLayoutPanel();
flowPanel.Dock = DockStyle.Top;
flowPanel.AutoSize = true;
flowPanel.Parent = panel;
Label label = new Label();
label.AutoSize = true;
label.Text = columnName[i] + ":";
label.Anchor = AnchorStyles.None;// Выравнивание по центру горизонтали
label.Parent = flowPanel;
TextBox txtBox = new TextBox();
txtBox.Name = columnName[i];
txtBox.Width = 200;
txtBox.Parent = flowPanel;
}
}
// Рекурсивный поиск всех объектов TextBox
// в дереве коллекций, начиная от текущего корня
// System.Collections.ArrayList list =
// new System.Collections.ArrayList(); - можно и этот, только он
// работает с типом Object и нужно будет явно повышать полномочия ссылки
List<TextBox> listReference = new List<TextBox>();// Типизированный список
void FindTextBox(Control.ControlCollection controls)
{
foreach (Control ctrl in controls)
{
if (ctrl is TextBox)
{
listReference.Add((TextBox)ctrl);
}
if (ctrl.Controls != null)
{
FindTextBox(ctrl.Controls);// Рекурсия!!!
}
}
}
}
}
Запустите
проект и проверьте заложенную в нем функциональностьДва снимка экрана, иллюстрирующие интерфейс приложения, выглядят так
![]() |
![]() |
С динамическим созданием пользовательского интерфейса повозились, теперь в этом упражнении вновь частично вернемся к испытанному дедовскому способу визуального проектирования, который значительно легче, поскольку кодированием занимается дизайнер формы. А уж он-то намного больше знает, какой код правильно сгенерировать. Но все же мы программисты и тонкие настройки выполним сами. В этом упражнении я постарался их 'понатыкать' побольше.
На сей раз мы
будем работать с таблицей Products готовой
БД Northwind.mdb,
потому что там есть столбец первичного ключа числового типа
и удобно сделать этот столбец автозаполняющимся (AutoIncrement),
чтобы не редактировать. В качестве элемента представления
и редактирования данных применим объект DataGridView,
который свяжем с загруженной таблицей, но не напрямую. Дело
в том, что мы еще будем использовать объект-навигатор BindingNavigator,
который обеспечит удобное перемещение по сетке DataGridView.
Для обеспечения синхронной их работы с одной и той же таблицей,
мы не сможем их сразу оба связать с ней. Но это можно легко
сделать через объект-разветвитель BindingSource, к которому
подключим таблицу, а уже через него подключим и другие объекты.
Добавьте
к решению ADO новый проект оконного приложения
с именем WinForms3 и
назначьте его стартовым
В панели Solution
Explorer курсором мыши перетащите каталог дата из предыдущего
проекта WinForms2 на узел нового проекта WinForms3,
мастеру настройки конфигурации скажите Cancel
В панели Solution
Explorer выделите файл БД Northwind.mdb и
в панели Properties установите для негоBuild Action=None Copy to Output Directory=Copy
if newer
Перетащите
на форму из вкладки Data панели Toolbox объекты DataGridView, BindingNavigator, BindingSource.
Проследите, чтобы объект BindingNavigator попал
на форму, а не внутрь объекта DataGridView как дочерний
(тогда лучше вначале перетащить BindingNavigator,
а потом DataGridView )
Настройте
свойства объектов в соответствии с таблицей свойств. Имена
экземпляров задайте в точности, иначе будет нестыковка с
приведенным далее кодом| Объект | Свойство | Значение |
|---|---|---|
Form |
Text |
Упражнение 3. |
Size |
333 ; 300 |
|
DataGridView |
(Name) |
dataGridView |
Dock |
Fill |
|
BindingNavigator |
(Name) |
bindingNavigator |
CountItemFormat |
из {0} |
|
BindingSource |
(Name) |
bindingSource |
Растяните
форму по ширине раза в два, выделите сам объект bindingNavigator (можно щелкнуть на пиктограмме в самом низу рабочей области
оболочки) и раскройте его интеллектуальный
дескриптор (smart
tag =
Щелкните
на ссылке Insert Standard Items окна смарт-тега, чтобы добавить
ряд встроенных пиктограмм
Из
добавленных пиктограмм оставьте Save и Help, а остальные
удалитеЛишние пиктограммы можно удалять по одной, выделяя их, а можно
воспользоваться окном редактора Items Collection Editor.
Щелкните
в окне смарт-тега объекта bindingNavigator на
ссылку Edit
Items, чтобы открыть редактор элементов. Этот редактор
с типовым интерфейсом и с ним легко работать
Удалите
лишние пиктограммы и добавьте сепаратор (разделитель) между
кнопкой Save и
предыдущими 'родными' пиктограммами навигатора
Установите
прежнюю ширину формы, прописанную в таблице свойствВ конечном итоге на этапе проектирования должен получиться такой интерфейс

Я приведу полный рабочий листинг кода, который получился не таким уж и большим, но какую функциональность он все-таки обеспечивает благодаря мощным библиотечным классам. Мы должны все же уважать за это Большого Била, хоть он и "проклятый загнивший капиталлист" (страшные слова, аж самому неприятно).
Заполните
файл Form1.cs проекта WinForms3 следующим кодомusing System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
// Дополнительные пространства имен
using System.Data.OleDb;
using System.Data.Common;
namespace WinForms3
{
public partial class Form1 : Form
{
public Form1()
{
// Инициализировать созданное в режиме дизайнера
InitializeComponent();
this.WindowState = FormWindowState.Maximized;// На полный экран
// Создать все остатки от дизайнера и загрузить таблицу
LoadProducts();
// Перед закрытием формы проверяем несохраненные изменения
this.FormClosing += new FormClosingEventHandler(Form1_FormClosing);
}
// Проверяем при закрытии формы несохраненные изменения
void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if(tableProducts.GetChanges() != null)
{
DialogResult result =
MessageBox.Show("Вы не сохранили изменения в БД\nСохранить?",
"Внимание!",
MessageBoxButtons.YesNoCancel, MessageBoxIcon.Warning);
if (result == DialogResult.Yes)
Update(); // Сохраняем данные
else if (result == DialogResult.Cancel)
e.Cancel = true;// Отменяем закрытие формы
}
}
// Строка соединения к БД с абсолютным путем, определяемым сборкой
String ConnectionString()
{
// Используем построитель строки подключения
OleDbConnectionStringBuilder objConnectionStringBuilder =
new OleDbConnectionStringBuilder();
objConnectionStringBuilder.Provider = "Microsoft.Jet.OLEDB.4.0";
objConnectionStringBuilder.DataSource =
Application.StartupPath.ToString() + @"\Data\Northwind.mdb";
//Application.ExecutablePath + @"\Data\Northwind.mdb";// + имя сборки
return objConnectionStringBuilder.ToString();
}
// Выносим в поля для видимости в методах
DataTable tableProducts;
OleDbDataAdapter adapter;
// Все загружаем и все настраиваем
void LoadProducts()
{
// Загружаем таблицу Products целиком
OleDbConnection connection = new OleDbConnection(ConnectionString());
adapter = new OleDbDataAdapter(
new OleDbCommand("SELECT * FROM Products", connection));
tableProducts = new DataTable();// Имя можно не указывать!
// Регистрируем обработчик события изменения поля таблицы для
// выделения ячейки после редактирования через DataGridView
tableProducts.ColumnChanged +=
new DataColumnChangeEventHandler(tableProducts_ColumnChanged);
// Указать столбец с автозаполнением, но обязательно до включения в таблицу
DataColumn col = new DataColumn("ProductID", typeof(int));
col.AutoIncrement = true;
tableProducts.Columns.Add(col);
col.ReadOnly = true;// Ключевой столбец не давать редактировать напрямую
adapter.Fill(tableProducts);// Сам откроет соединение и сам закроет
// Указать первичный ключ можно уже и в заполненной таблице
// Синтаксис определения первичного ключа требует использовать массив
// на случай, если в первичный ключ войдут несколько столбцов
tableProducts.PrimaryKey =
new DataColumn[] { tableProducts.Columns["ProductID"] };
//
// Разветвитель, навигатор и сетку создали визуально
//
// Связали таблицу с разветвителем
bindingSource.DataSource = tableProducts;
// Поключили к разветвителю навигатор
bindingNavigator.BindingSource = bindingSource;
// Подключили к разветвителю элемент редактирования
dataGridView.DataSource = bindingSource;
//
//Настраиваем DataGridView
//
// Сделаем ключевой столбец с автозаполнением только для чтения
//dataGridView.Columns["ProductID"].ReadOnly = true;// Уже сделали в таблице
// Ширина столбцов по ширине содержимого
dataGridView.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
dataGridView.RowHeadersWidth = 25;// Ширина первого (служебного) столбца
//dataGridView.RowHeadersVisible = false;// Скрываем первый (служебный) столбец
//dataGridView.Columns["ProductID"].Visible = false;// Скрываем рабочий столбец
// Запретим выделение многих строк, все-равно
// навигатор удаляет только по одной строке
dataGridView.MultiSelect = false;
// Настраиваем стиль строки заголовков сетки
DataGridViewCellStyle columnHeaderStyle = new DataGridViewCellStyle();// Создали стиль
columnHeaderStyle.BackColor = Color.Beige;// Цвет фона заголовка
columnHeaderStyle.Font = new Font("Verdana", 10, FontStyle.Bold);// Шрифт
columnHeaderStyle.Alignment =
DataGridViewContentAlignment.MiddleCenter;// Выравнивать по центру столбца
dataGridView.ColumnHeadersDefaultCellStyle = columnHeaderStyle;// Настроили и прицепили
// Фоновый цвет всех ячеек таблицы по умолчанию белый. Но явно
// задаем потому, что позже будем восстанавливать белым же цветом
DataGridViewCellStyle defaultCellStyle = new DataGridViewCellStyle();
defaultCellStyle.BackColor = Color.White;
dataGridView.DefaultCellStyle = defaultCellStyle;// Настроили и прицепили
//
// Настраиваем кнопки объекта BindingNavigator здесь
// или в панели Properties дизайнера
//
bindingNavigatorMoveFirstItem.ToolTipText = "На первую запись";
bindingNavigatorMovePreviousItem.ToolTipText = "На предыдущую запись";
bindingNavigatorPositionItem.ToolTipText = "Текущая запись";
bindingNavigatorCountItem.ToolTipText = "Всего записей";
bindingNavigatorMoveNextItem.ToolTipText = "На следующую запись";
bindingNavigatorMoveLastItem.ToolTipText = "На последнюю запись";
bindingNavigatorAddNewItem.ToolTipText = "Добавить запись";
bindingNavigatorDeleteItem.ToolTipText = "Удалить запись";
saveToolStripButton.ToolTipText = "Сохранить изменения";
helpToolStripButton.ToolTipText = "Помощь";
helpToolStripButton.Enabled = false;// Затычка для Справки, пока не хотим...
// Регистрируем обработчик события сохранения данных в БД
saveToolStripButton.Click += new EventHandler(saveToolStripButton_Click);
//
// Создаем и настраиваем контекстное меню для дополнительных команд
//
// Создаем объект контекстного меню
System.Windows.Forms.ContextMenuStrip menu = new ContextMenuStrip();
// Прицепляем к DataGridView
dataGridView.ContextMenuStrip = menu;
// Создаем объект команды и сразу с названием
ToolStripMenuItem item = new ToolStripMenuItem("Восстановить");
// Всплывающая подсказка
item.ToolTipText = "Откатить все несохраненные изменения";
// Добавляем команду в коллекцию команд контекстного меню
menu.Items.Add(item);
// Регистрируем обработчик команды для события Click
item.Click += new EventHandler(item_RestoreClick);
}
// Список для хранения ячеек с измененными стилями
// для последующего восстановления в ClearStyleModifiedCells()
// (Ничего приличнее мне в голову не пришло! НаноСидоркин - помоги!
// Надо искать оптовое решение в самом DataGridView)
System.Collections.ArrayList listCell = new System.Collections.ArrayList();
// Срабатывает при модификации любого поля
void tableProducts_ColumnChanged(object sender, DataColumnChangeEventArgs e)
{
DataGridViewCellStyle style = new DataGridViewCellStyle();
style.BackColor= Color.Aqua;
dataGridView.CurrentCell.Style = new DataGridViewCellStyle(style);
listCell.Add(dataGridView.CurrentCell);// Складываем ссылки для восстановления
}
// Обработчик команды контекстного меню
void item_RestoreClick(object sender, EventArgs e)
{
// Если нечего откатывать
if (tableProducts.GetChanges() == null)
return;
// Есть изменения, спрашиваем желание пользователя
DialogResult result =
MessageBox.Show("Вы действительно хотите откатить\n"
+ "последние несохраненные изменения?",
"Внимание!",
MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
if (result == DialogResult.Yes)
{
tableProducts.RejectChanges();// Откатываем таблицу
ClearStyleModifiedCells(); // Снимаем выделение с ячеек
}
}
// Сохраняем изменения в БД
new void Update()
{
// Чтобы лишний раз не дергать проверку на уровне БД
if (tableProducts.GetChanges() == null)
return;
// Создаем построитель команд по настройкам нашего адаптера
OleDbCommandBuilder commandBuilder =
new OleDbCommandBuilder(adapter);
// Не реагировать на конфликты параллелизма, а записывать поверх
commandBuilder.ConflictOption = ConflictOption.OverwriteChanges;
// Отправить изменения таблицы в БД. Необходимые
// команды генерирует созданный объект commandBuilder
adapter.Update(tableProducts);
}
// Обработчик кнопки сохранения изменений в БД
void saveToolStripButton_Click(object sender, EventArgs e)
{
// Проверка достоверности завершает редактирование текушего поля
this.Validate();
bindingSource.EndEdit();// Останавливаем разветвитель
Update();// Отправляем изменения в БД
ClearStyleModifiedCells();// Восстанавливаем цвет ячеек сетки
}
// Все содержимое ArrayList имеет тип Object - надо явно приводить
void ClearStyleModifiedCells()
{
// Снимаем выделение с измененных ячеек сетки
DataGridViewCellStyle style = new DataGridViewCellStyle();
style.BackColor = Color.White;
foreach (Object ob in listCell)
{
if (ob != null)// На случай, если строку с ячейками удалили!
((DataGridViewCell)ob).Style = new DataGridViewCellStyle(style);
}
listCell.Clear();// Очистить список, начать с чистого листа!
}
}
}
Запустите
приложение, разберитесь с кодом, что не достроено - достройте,
что недокрашено - докрасьте. Вот результат
Здесь есть один нюанс. Я не смог усмирить счетчик автозаполнения первого столбца. Если последние строки добавляешь, а потом удаляешь и сохраняешь - он все-равно не сбрасывается в значение, следующее за последним столбцом при новой выборке данных, а считает сам по себе. Как до этого счетчика добраться, может кто и догадается, я не смог.
Еще раз приведу схему связей таблиц в нашей учебной БД, из
которой видно, что столбец ProductID таблицы Products является первичным ключем, но есть
еще и два столбца внешних ключей

Можно не ставить автозаполнение на столбец первичного ключа,
а вынести счетчик отдельно, а при выборке данных посылать
SQL-команду с функцией MAX().
"SELECT MAX(ProductID) FROM Products"
Но для большой таблицы это не выход, хотя для ключевого столбца команда выполнится быстро. Можно вообще с ключевого столбца снять свойство только для чтения, снять автозаполнение и дать пользователю самому заполнять ключевые поля уникальными значениями. Но тогда все равно нужно проверять уникальность значений ключевого столбца, иначе будет нарушено ограничение таблицы и мы получим ряд исключений. Мы знаем, что у нас проблемные пользователи (такие же, как дороги).
Попробуйте обойти эту дырку. Одно из направлений такое
Добавьте
в класс Form1 проекта WinForms3 примерно
следующий метод, получающий через SQL-запрос максимальное
значение ключевого столбца// Максимальное значение ключа
void MaxPrimaryKey()
{
OleDbConnection conn = new OleDbConnection(ConnectionString());
OleDbCommand command = new OleDbCommand(
"SELECT MAX(ProductID) FROM Products",
conn);
conn.Open();
int max = (int)command.ExecuteScalar();
conn.Close();
MessageBox.Show(max.ToString());
}
Добавьте
в конструктор класса (или еще куда-нибудь) вызов этого метода
и выполните проектДля показанного ранее на снимке состояния БД получим такой 'непричесанный' результат

Далее
действуйте по обстановке, может быть и другим способомМожно, также, подсчитывать количество записей SQL-запросом
"SELECT COUNT(*) FROM Products"
и их считать отправной точкой для счетчика при добавлении новых записей в таблицу. Но это будет работать при условии, что ключи изначально представляют собой натуральный ряд чисел. Для нашего примера это будет уже расхождение с подсчетом максимального значения ключа

Можно попробовать что-то сделать и с таким кодом
// Все загружаем и все настраиваем
void LoadProducts()
{
.........................................
tableProducts.RowChanged += new DataRowChangeEventHandler(tableProducts_RowChanged);
}
void tableProducts_RowChanged(object sender, DataRowChangeEventArgs e)
{
if (e.Row["ProductName"].ToString() == String.Empty)
e.Row.Delete();
// А что дальше???
}
Думайте!!!
Классики хотели, я и сподобился...Xml-формат является универсальным на любых платформах. Он не зависит от специфики формата БД. Поэтому его удобно применять данными между различными БД. Еще одно достоинство - текстовое представление данных. Это очень удобно при пересылке данных по сети, поскольку не содержит в себе случайных управляющих символов и сочетаний байт, которые могут быть восприняты в процессе передачи как служебные команды.
На фрагментах кода поясним работу приложения для данного упражнения.
Прежде всего сформируем строку соединения с помощью библиотечного объекта OleDbConnectionStringBuilder, которую возвращает функция
// Строка соединения к БД с абсолютным путем, определяемым сборкой
String ConnectionString()
{
// Используем построитель строки подключения
OleDbConnectionStringBuilder objConnectionStringBuilder =
new OleDbConnectionStringBuilder();
objConnectionStringBuilder.Provider = "Microsoft.Jet.OLEDB.4.0";
objConnectionStringBuilder.DataSource =
Application.StartupPath.ToString() + @"\Data\Northwind.mdb";
// Текущий путь
//System.Environment.CurrentDirectory.ToString()
// Путь к сборке + имя сборки с расширением .exe
//Application.ExecutablePath + @"\Data\Northwind.mdb";
return objConnectionStringBuilder.ToString();
}
Далее мы создаем метод, который возвращает заполненный набор
данных.
DataSet LoadDataSet()
{
DataSet ds= new DataSet();
// Заполняем множественный набор данных из БД
using (OleDbConnection conn = new OleDbConnection(ConnectionString()))
{
OleDbCommand selectCommand = conn.CreateCommand();// Команда получила соединение
OleDbDataAdapter adapter = new OleDbDataAdapter(selectCommand);
//adapter.FillSchema(ds, SchemaType.Source);// Лишнее, загружается вместе с данными
selectCommand.CommandText = "SELECT * FROM Employees";
// Загружает данные и схему (по умолчанию) первой таблицы
// Без маркера может добавиться только одна
// таблица и будет иметь дежурное имя Table
adapter.Fill(ds, "Empl");
// Загружает данные и схему второй таблицы
selectCommand.CommandText = "SELECT * FROM Products";
adapter.Fill(ds, "Prod");
// Чтобы повторно не добавлялось одно и то же
adapter.MissingSchemaAction = MissingSchemaAction.AddWithKey;
adapter.Fill(ds, "Prod");// Не добавится, т.к. установили AddWithKey
//adapter.FillSchema(ds, SchemaType.Source);// Загрузить только схему
}
return ds;
}
Обратите внимание, что в один и тот же объект DataSet
загружаются две таблицы сразу. Причем сделана попытка вторую
таблицу загрузить дважды. Этому препятствует свойство адаптера MissingSchemaAction, установленное в значение одноименного
перечисления MissingSchemaAction.AddWithKey. Если в методе Fill() адаптера не указать имя добавляемой таблицы, то будет
загружена первая таблица с дежурным именем Table. Имя загруженной
таблицы можно присвоить и после ее загрузки,
только тогда может успешно загрузится и вторая таблица
DataSet LoadDataSet()
{
DataSet ds= new DataSet();
// Заполняем множественный набор данных из БД
using (OleDbConnection conn = new OleDbConnection(ConnectionString()))
{
OleDbCommand selectCommand = conn.CreateCommand();// Команда получила соединение
OleDbDataAdapter adapter = new OleDbDataAdapter(selectCommand);
// Загружает данные и схему первой таблицы
selectCommand.CommandText = "SELECT * FROM Employees";
adapter.Fill(ds);
ds.Tables[0].TableName="Empl";
// Загружает данные и схему второй таблицы
selectCommand.CommandText = "SELECT * FROM Products";
adapter.Fill(ds);
ds.Tables[1].TableName = "Prod";
}
return ds;
}
В коде клиента заполненный множественный набор данных можно отобразить, связав его с созданными объектами сетки любым из двух равнозначных способов
// Заполняем множественный набор данными из БД
DataSet ds = LoadDataSet();
// Связываем с сетками
// Первый способ
dataGridView1.DataSource = ds.Tables["Empl"];// Как таблицу
// Второй способ
dataGridView2.DataSource = ds;// Как множественный набор
dataGridView2.DataMember = "Prod";// Уточняем из набора
Набор данных может сохранять загруженные в него объектные
таблицы в файл формата Xml с помощью своего метода WriteXml.
Точно такой же метод имеет и объект таблицы. Существенной разницы
между работой этих методов нет, оба они могут применяться с
дополнительным параметром перечисления XmlWriteMode, который
может указывать своим значением XmlWriteMode.WriteSchema, чтобы
в Xml-файл включалась и схема таблиц. Эти объекты имеют и симметричные
методы DataSet.ReadXml и DataTable.ReadXml. Но здесь есть разница,
для работы метода DataSet.ReadXml не требуется Xml-файл со
схемой.
// Заполняем множественный набор данными из БД
DataSet ds = LoadDataSet();
// Путь к каталогу записи
String path = Application.StartupPath.ToString() + @"\Data\";
// Записываем Xml-файл без схемы
//ds.WriteXml(path + "ds.xml", XmlWriteMode.WriteSchema);
ds.WriteXml(path + "ds.xml");// Без схемы
// Извлекаем данные из Xml-файла с помощью DataSet и показываем
ds = new DataSet();// Создаем новый объект, используем ту же ссылку
ds.ReadXml(path + "ds.xml");// Загружаем из Xml-файла
// Привязываем наборы данных к сеткам для отображения
dataGridView1.DataSource = ds.Tables["Empl"];// Как таблицу
dataGridView2.DataSource = ds;// Как множественный набор
dataGridView2.DataMember = "Prod";// Уточняем из набора
// Удаляем файл (просто так, для примера)
System.IO.File.Delete(path + "ds.xml");
А для метода DataTable.ReadXml требуется файл со схемой
// Заполняем множественный набор данными из БД
DataSet ds = LoadDataSet();
// Сохраняем физически таблицу из набора в другой таблице
DataTable table = ds.Tables["Empl"].Copy();
// Путь к каталогу записи
String path = Application.StartupPath.ToString() + @"\Data\";
// Сохраняем таблицу со схемой в файле Xml
table.WriteXml(path + "Employees.xml", XmlWriteMode.WriteSchema);
//table.WriteXml(path + "Employees.xml"); // Сохраняем без схемы
table = null;// Бросаем объект
table = new DataTable();// Создаем новый объект таблицы
// Читаем таблицу из Xml-файла
table.ReadXml(path + "Employees.xml");
// Показываем таблицу пользователю
dataGridView.DataSource = table;
Но прочитать файл без схемы можно с помощью DataSet.ReadXml
// Заполняем множественный набор данными из БД
DataSet ds = LoadDataSet();
// Сохраняем физически таблицу из набора в другой таблице
DataTable table = ds.Tables["Empl"].Copy();
// Путь к каталогу записи
String path = Application.StartupPath.ToString() + @"\Data\";
// Сохраняем таблицу со схемой в файле Xml
table.WriteXml(path + "Employees.xml");// Записываем без схемы
// Читаем Xml без схемы с помощью набора DataSet
ds = new DataSet();
ds.ReadXml(path + "Employees.xml");// Читает и без схемы
//table.ReadXml(path + "Employees.xml");// Будет сбой при выполнении
// Показываем прочитанную таблицу пользователю
dataGridView.DataSource = ds.Tables[0];// Единственная в коллекции
Ситуацию с чтением DataTable.ReadXml можно исправить, если
при записи сохранять в Xml сразу и схему и данные, но можно
схему сохранить отдельно, а потом перед чтением данных ее загрузить.
// Путь к каталогу записи
String path = Application.StartupPath.ToString() + @"\Data\";
// Объект отображения
DataGridView dataGridView = new DataGridView();
dataGridView.Parent = this;
dataGridView.Dock = DockStyle.Fill;
// Заполняем множественный набор данными из БД
DataSet ds = LoadDataSet();
// Создаем объект таблицы
DataTable table = new DataTable();
// Копируем в таблицу данные из набора, где есть и схема
table = ds.Tables["Empl"].Copy();
// Сохраняем схему в отдельном файле (сейчас или ранее!)
table.WriteXmlSchema(path + "Schema.xml");
// Сохраняем таблицу без схемы
table.WriteXml(path + "Employees.xml");
// Создаем новый чистый объект таблицы
table = new DataTable();
// Читаем схему из файла
table.ReadXmlSchema(path + "Schema.xml");
// Сюда же читаем файл без схемы, теперь можно
table.ReadXml(path + "Employees.xml");
// Показываем пользователю
dataGridView.DataSource = table;
Иногда может потребоваться записать в Xml-формате строки объектной таблицы, каждый в своем файле. Это можно сделать так
// Путь к каталогу записи
String path = Application.StartupPath.ToString() + @"\Data\";
DataSet ds = LoadDataSet();// Заполняем набор из БД
DataSet ds1 = new DataSet();// Вспомогательный DataSet
int i = 0;
foreach (DataRow r in ds.Tables["Empl"].Rows)
{
ds1.Clear();
DataRow[] row = { r };// Merge() ожидает массив строк
ds1.Merge(row);// Добавляем строку из другого набора
ds1.WriteXml(path + "row" + ++i + ".xml");// Записываем в Xml-файл
}
Собрать Xml-строки, сохраненные предыдущим кодом, в набор данных можно так
// Путь к каталогу записи
String path = Application.StartupPath.ToString() + @"\Data\";
// Объект отображения
DataGridView dataGridView = new DataGridView();
dataGridView.Parent = this;
dataGridView.Dock = DockStyle.Fill;
// Заполняем множественный набор данными из БД
DataSet ds = LoadDataSet();
int count = ds.Tables["Empl"].Rows.Count;
ds = new DataSet();// Новый DataSet
for (int i = 0; i < count; i++)
{
DataSet ds1 = new DataSet();
ds1.Clear();
ds1.ReadXml(path + "row" + (i + 1) + ".xml");
DataRow[] row = { ds1.Tables[0].Rows[0] };
ds.Merge(row);// Добавляем в текущий DataSet
}
// Показываем пользователю
dataGridView.DataSource = ds.Tables[0];
В этом упражнении мы познакомились с возможностью ADO.NET хранить наборы данных и таблицы в формате Xml без всякой потери информации. Для устранения рутины весь интерфейс создается динамически, так что можно просто создать новый проект, поместить в него БД из предыдущего упражнения, настроить ее как и ранее, затем скопировать приведенное ниже содержимое и готово...
Вот код упражнения
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
// Дополнительные пространства имен
using System.Data.OleDb;
using System.Data.Common;
using System.Xml;
namespace WinForms4
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.Text = "Упражнение 4";
this.Size = new Size(350, 300);
CreatePage1();
CreatePage2();
CreatePage3();
CreatePage4();
CreatePage5();
CreatePage6();
// Регистрируем обработчик для удаления файлов при закрытии
//this.FormClosed += new FormClosedEventHandler(Form1_FormClosed);
}
void DeleteFile(String name)
{
// Определяем полный путь к хранилищу данных
String path = Application.StartupPath.ToString() + @"\Data\";
if (System.IO.File.Exists(path + name))
System.IO.File.Delete(path + name);
}
void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
// Удаляем файлы, если существуют (для примера)
DeleteFile("ds.xml");
DeleteFile("dsItSchema.xml");
DeleteFile("Employees.xml");
DeleteFile("Products.xml");
}
~Form1()
{
/*
* Можно и в деструктор засунуть удаление файлов
* Но это произойдет не немедленно, а когда
* сборщик мусора доберется до брошенного объекта приложения
* Либо самим вызвать сборщик мусора, вставив в файл Program.cs
* после цикла сообщений код System.GC.Collect();
* Попробуйте!!!
//*/
}
// Строка соединения к БД с абсолютным путем, определяемым сборкой
String ConnectionString()
{
// Используем построитель строки подключения
OleDbConnectionStringBuilder objConnectionStringBuilder =
new OleDbConnectionStringBuilder();
objConnectionStringBuilder.Provider = "Microsoft.Jet.OLEDB.4.0";
objConnectionStringBuilder.DataSource =
Application.StartupPath.ToString() + @"\Data\Northwind.mdb";
// Текущий путь
//System.Environment.CurrentDirectory.ToString()
// Путь к сборке + имя сборки с расширением .exe
//Application.ExecutablePath + @"\Data\Northwind.mdb";
return objConnectionStringBuilder.ToString();
}
DataSet LoadDataSet()
{
DataSet ds= new DataSet();
// Заполняем множественный набор данных из БД
using (OleDbConnection conn = new OleDbConnection(ConnectionString()))
{
OleDbCommand selectCommand = conn.CreateCommand();// Команда получила соединение
OleDbDataAdapter adapter = new OleDbDataAdapter(selectCommand);
//adapter.FillSchema(ds, SchemaType.Source);// Лишнее, загружается вместе с данными
selectCommand.CommandText = "SELECT * FROM Employees";
// Загружает данные и схему (по умолчанию) первой таблицы
// Без маркера может добавиться только одна
// таблица и будет иметь дежурное имя Table
adapter.Fill(ds, "Empl");
// Загружает данные и схему второй таблицы
selectCommand.CommandText = "SELECT * FROM Products";
adapter.Fill(ds, "Prod");
// Чтобы повторно не добавлялось одно и то же
adapter.MissingSchemaAction = MissingSchemaAction.AddWithKey;
adapter.Fill(ds, "Prod");// Не добавится, т.к. установили AddWithKey
//adapter.FillSchema(ds, SchemaType.Source);// Загрузить только схему
}
return ds;
}
TabControl tabControl;
void CreatePage1()
{
// Контейнер вкладок создаем здесь
tabControl = new TabControl();
tabControl.Parent = this;
tabControl.Dock = DockStyle.Fill;
tabControl.Multiline = true;// Если ярлыки вкладок на помещаются
tabControl.ShowToolTips = true;// Разрешить всплывающие посказки
tabControl.SelectedIndex = 5;// Устанавливает начальную вкладку (просто так!)
// Вкладка Page1
TabPage tabPage = new TabPage("Данные из БД");
tabPage.Parent = tabControl;
tabPage.ToolTipText = "Множественный набор,\nполученный из БД";
SplitContainer split = new SplitContainer();
split.Orientation = Orientation.Horizontal;
split.Dock = DockStyle.Fill;
split.Parent = tabPage;
DataGridView dataGridView1 = new DataGridView();
dataGridView1.Dock = DockStyle.Fill;
// Сетки помещаем в панели сплиттера
dataGridView1.Parent = split.Panel1;
DataGridView dataGridView2 = new DataGridView();
dataGridView2.Dock = DockStyle.Fill;
dataGridView2.Parent = split.Panel2;
// Заполняем множественный набор данными из БД
DataSet ds = LoadDataSet();
// Связываем с сетками
// Первый способ
dataGridView1.DataSource = ds.Tables["Empl"];// Как таблицу
// Второй способ
dataGridView2.DataSource = ds;// Как множественный набор
dataGridView2.DataMember = "Prod";// Уточняем из набора
}
void CreatePage2()
{
// Вкладка Page2
TabPage tabPage = new TabPage("Данные из Xml");
tabPage.Parent = tabControl;
tabPage.ToolTipText = "Множественный набор,\nполученный из Xml";
SplitContainer split = new SplitContainer();
split.Orientation = Orientation.Horizontal;
split.Dock = DockStyle.Fill;
split.Parent = tabPage;
DataGridView dataGridView1 = new DataGridView();
dataGridView1.Dock = DockStyle.Fill;
dataGridView1.Parent = split.Panel1;
DataGridView dataGridView2 = new DataGridView();
dataGridView2.Dock = DockStyle.Fill;
dataGridView2.Parent = split.Panel2;
// Заполняем множественный набор данными из БД
DataSet ds = LoadDataSet();
//
// Сохраняем загруженные данные из набора в Xml-файл (Без схемы!!!)
//
// Определяем полный путь к хранилищу данных
String path = Application.StartupPath.ToString() + @"\Data\";
// Множественный набор в Xml-файле не обязательно сохранять вместе
// со схемой, метод DataSet.ReadXml() потом итак будет читать
// Но Xml-файл будет без схемы
//ds.WriteXml(path + "ds.xml", XmlWriteMode.WriteSchema);
ds.WriteXml(path + "ds.xml");
// Извлекаем данные из Xml-файла набора и показываем
ds = null;// Бросаем старый объект
ds = new DataSet();// Создаем новый объект
ds.ReadXml(path + "ds.xml");// Загружаем из Xml-файла
// Привязываем наборы данных к сеткам для отображения
// Первый способ
dataGridView1.DataSource = ds.Tables["Empl"];// Как таблицу
// Второй способ
dataGridView2.DataSource = ds;// Как множественный набор
dataGridView2.DataMember = "Prod";// Уточняем из набора
// Удаляем файл (для примера)
System.IO.File.Delete(path + "ds.xml");
}
void CreatePage3()
{
// Вкладка Page3
TabPage tabPage = new TabPage("Xml без схемы");
tabPage.Parent = tabControl;
tabPage.ToolTipText = "Представление набора\nв Xml без схемы";
WebBrowser browser = new WebBrowser();
browser.Dock = DockStyle.Fill;
browser.Parent = tabPage;
// Заполняем множественный набор данными из БД
DataSet ds = LoadDataSet();
// Сохраняем загруженные данные из набора в Xml-файл (Без схемы!!!)
String path = Application.StartupPath.ToString() + @"\Data\";
ds.WriteXml(path + "ds.xml");
// Извлекаем данные из Xml-файла набора и показываем
browser.Navigate(path + "ds.xml");// Связываемся с Xml-файлом
// Удалять файл нельзя, открыт навигатором в потоковом режиме
//System.IO.File.Delete(path + "ds.xml");
}
void CreatePage4()
{
// Вкладка Page4
TabPage tabPage = new TabPage("Xml со схемой");
tabPage.Parent = tabControl;
tabPage.ToolTipText = "Представление набора\nв Xml со схемой";
WebBrowser browser = new WebBrowser();
browser.Dock = DockStyle.Fill;
browser.Parent = tabPage;
// Заполняем множественный набор данными из БД
DataSet ds = LoadDataSet();
// Сохраняем загруженные данные из набора в Xml-файл (и схема!!!)
String path = Application.StartupPath.ToString() + @"\Data\";
ds.WriteXml(path + "dsItSchema.xml", XmlWriteMode.WriteSchema);
// Извлекаем данные из Xml-файла набора и показываем
browser.Navigate(path + "dsItSchema.xml");// Связываемся с Xml-файлом
}
void CreatePage5()
{
// Вкладка Page5
TabPage tabPage = new TabPage("Таблица в Xml");
tabPage.Parent = tabControl;
tabPage.ToolTipText = "Таблица в Xml-представлении\n"
+ "с включенной схемой\n(иначе не прочесть)";
WebBrowser browser = new WebBrowser();
browser.Dock = DockStyle.Fill;
browser.Parent = tabPage;
String path = Application.StartupPath.ToString() + @"\Data\";
// Читаем Xml-файл множественного набора
// Он сохранен предыдущей вкладкой
DataSet ds = new DataSet();
ds.ReadXml(path + "dsItSchema.xml", XmlReadMode.ReadSchema);
// Создаем новый объект для таблицы Employees
DataTable table = new DataTable();
table = ds.Tables["Empl"].Copy();// Теперь самостоятельная таблица
// Сохраним для следующей вкладки в Xml-файле
table.WriteXml(path + "Employees.xml", XmlWriteMode.WriteSchema);
// Теряем набор сборщику мусора (... для осознанья и просветления!!!)
ds = null;
GC.Collect();// Запустить уборку
GC.WaitForPendingFinalizers();// Ждать конца уборки
// Показываем Xml пользователю
browser.Navigate(path + "Employees.xml");
}
void CreatePage6()
{
// Вкладка Page6
TabPage tabPage = new TabPage("Таблица из Xml");
tabPage.Parent = tabControl;
tabPage.ToolTipText = "Таблица данных,\nполученных из файла Xml";
DataGridView dataGridView = new DataGridView();
dataGridView.Dock = DockStyle.Fill;
dataGridView.Parent = tabPage;
String path = Application.StartupPath.ToString() + @"\Data\";
// Читаем Xml-файл для таблицы Employees
DataTable table = new DataTable();
table.ReadXml(path + "Employees.xml");
// Показываем пользователю
dataGridView.DataSource = table;
}
}
}
Разберитесь
с кодом, создайте проект WinForms4 и поэкспериментируйте
с нимВот пара снимков
![]() |
![]() |
Мы видим, что данные полность восстанавливаются из Xml и могут безбоязненно быть отправлены в БД. Очень удобно брать работу на дом, если сделать небольшое приложение, которое перед уходом скопирует секретные данные в Xml-файл. Всю базу копировать долго и нести тяжело, а пару файлов Xml можно дома опять преобразовать в БД и спокойно работать (особенно шпиенам). Да и переслать по сети будет легче.
Объект XmlDataDocument хранит в себе данные, загруженные из
БД, сразу в двух форматах: обычный набор данных DataSet и Xml-формат
данных. В некоторых случаях это может быть полезным. В этом
упражнении проиллюстрируем некоторые возможности объекта XmlDataDocument.
Добавьте
к приложению новый проект оконного приложения с именем WinForms5
Скопируйте
из предыдущего проекта WinForms4 в корневой узел проекта WinForms5 папку Data вместе с файловой БД Northwind.mdb.
На вопрос мастера конфигурации скажите Cancel
В панели Solution
Explorer выделите файл БД Northwind.mdb и
в панели Properties установите для негоBuild Action=None Copy to Output Directory=Copy
if newer
Добавьте
для корневого узла проекта WinForms5 командой Add/New Item заготовку файла конфигурации приложения с именем App.config
Иногда бывает удобно не прятать строку соединения в код приложения, а вынести ее в конфигурационный файл, чтобы при изменении условий выполнения не исправлять сам код. В этом упражнении мы и применим такой способ. При наличии множества БД можно заготовить несколько строк соединения в конфигурационном файле с разными именами, а в извлекать нужную строку по ее имени без изменения кода самого приложения.
Заполните
конфигурационный файл кодом настройки строки соединения с
БД, значение которой мы будем извлекать из кода приложения
во время выполнения<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<connectionStrings>
<add name="MyNorthwind"
connectionString="Provider=Microsoft.Jet.OLEDB.4.0;Data Source=|DataDirectory|\Data\Northwind.mdb"
providerName="System.Data.OleDb" />
</connectionStrings>
</configuration>
Выделите
корневой узел проекта WinForms5 и командой Project/Add Reference добавьте
к проекту библиотечную сборку System.configuration.dll,
в которой находится нужный нам класс ConfigurationManager для
работы с конфигурационным файлом
Выделите
корневой узел проекта WinForms5 и командой Project/WinForms5 Properties установите
для вкладки Application список Output type в
значение Console Application, чтобы
иметь возможность просматривать информацию стандартного потока
вывода
Заполните
файл Form1.cs проекта WinForms5 следующим
кодомusing System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
// Дополнительные пространства имен
using System.Data.OleDb;
using System.Data.Common;
using System.Xml;
using System.IO;
namespace WinForms5
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
SetInterface();
Load();
}
WebBrowser browser;
DataGridView dataGridView;
void SetInterface()
{
this.Text = "Упражнение 5";
// Создаем сплиттер
SplitContainer split = new SplitContainer();
split.Orientation = Orientation.Horizontal;
split.Dock = DockStyle.Fill;
split.Parent = this;
// Создаем элементы отображения
dataGridView = new DataGridView();
dataGridView.Dock = DockStyle.Fill;
dataGridView.AutoSizeColumnsMode =
DataGridViewAutoSizeColumnsMode.Fill;
dataGridView.RowHeadersWidth = 25;// Служебный столбец
dataGridView.Parent = split.Panel1;
browser = new WebBrowser();
browser.Dock = DockStyle.Fill;
browser.Parent = split.Panel2;
}
DataSet LoadDataSet()
{
// Извлекаем строку соединения из файла App.config
String connectStr = System.Configuration.
ConfigurationManager.ConnectionStrings["MyNorthwind"].ConnectionString;
DataSet ds = new DataSet();
// Заполняем множественный набор данных из БД
using (OleDbConnection conn = new OleDbConnection(connectStr))
{
OleDbCommand selectCommand = conn.CreateCommand();// Команда получила соединение
OleDbDataAdapter adapter = new OleDbDataAdapter(selectCommand);
// Загружает данные и схему первой таблицы
selectCommand.CommandText = "SELECT EmployeeID, " +
"(LastName + ', ' + FirstName) AS FullName, " +
"Address, BirthDate, Region FROM Employees";
adapter.Fill(ds);
}
return ds;
}
XmlDataDocument document;
String path;
new void Load()
{
// Определяем полный путь к хранилищу данных
path = Application.StartupPath.ToString() + @"\Data\";
// Создаем Xml-документ и загружаем его данными
document = new XmlDataDocument(LoadDataSet());
// Связываем с сеткой для отображения
dataGridView.DataSource = document.DataSet;// Только для чтения
dataGridView.DataMember = "Table";// Дежурное имя, когда не задано явно
// Все тонкие ссылки можно объявить типом
// ближайшего абстрактного предка (просто так!)
TextWriter tw = new StringWriter();// Создали поток записи
document.Save(tw); ;// Сохраняем документ в потоке
document.Save(path + "XmlDataDocument.xml");// Сохранили в файле
// Выводим в консольное окно
TextReader tr = new StringReader(tw.ToString());// Создали поток чтения
String str = tr.ToString();// Если ничего нет, то будет null
while (str != null)
{
str = tr.ReadLine();
Console.WriteLine(str);
}
browser.Navigate(path + "XmlDataDocument.xml");
// Регистрируем обработчик события редактирования строки
document.DataSet.Tables[0].RowChanged +=
new DataRowChangeEventHandler(Form1_RowChanged);
}
void Form1_RowChanged(object sender, DataRowChangeEventArgs e)
{
document.Save(path + "XmlDataDocument.xml");
browser.Navigate(path + "XmlDataDocument.xml");
}
}
}
Разберитесь
с кодом упражнения, запустите проект и удостоверьтесь, что
редактирование строк в формате DataSet немедленно
отражается и в Xml-представлении с помощью объекта XmlDataDocument.
Хотя здесь мы и не сохраняем изменения в самой БД
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.