Практикум прикладного программирования на C# в среде VS.NET 2008

Просмотр данных OLE DB средствами ADO.NET

Показывать лекцию целиком

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

Наиболее современной технологией разработки приложений, управляющих данными, является построение многоуровневых систем:

  • Уровень "Хранилище данных" (Data Store) - место, где находятся сами данные. Это может быть реляционная база данных, XML-файл, текстовый файл или какая-то другая структурированная система хранения .
  • Уровень "Доступ к данным" (Data Access Layer - DAL) - код, который необходим для извлечения и манипулирования необработанными данными, находящимися в хранилище.
  • Уровень "Бизнес-логика" (Business Logic Layer - BLL) - код, который берет извлеченные данные и предоставляет их клиенту в более понятном виде, а также обеспечивает безопасность и согласованность действий клиента и данных.
  • Уровень "Представление" ("Пользовательский интерфейс") (Presentation/User Interface Layer - UI) - код, который определяет, что именно должен видеть пользователь на экране, включая форматирование данных и навигационное меню системы.
  • Доступ к данным обеспечивает обобщенный поставщик данных, который включает в себя следующие классы:

  • DbConnection - используется для установки соединения с источником данных
  • DbCommand - используется для выполнения SQL-команд и хранимых процедур
  • DbDataReader - модуль чтения, который предоставляет быстрый последовательный доступ к данным только для чтения. Он сам автоматически не управляет открытием и закрытием соединения и это нужно делать вручную, открывая как можно позже и закрывая как можно раньше
  • DbDataAdapter - выполняет две задачи:
  • Наполнение набора данных DataSet (автономная коллекция таблиц и отношений) информацией, извлеченной из источника данных
  • Применение изменений данных, выполненных пользователем в DataSet, к источнику данных
  • Этот обобщенный поставщик напрямую не применяется, а наследуется специализированными поставщиками, имена которых имеют префиксы Odbc, OleDb, Sql, Oracle. Мы будем использовать набор классов OleDb для файловой БД Northwind.mdb. Нужно четко понимать, что поставщики ADO.NET отображают реляционную модель данных хранилища в объектно-ориентированную модель приложения. Это значит, что данные таблицы или множества таблиц после их считывания приложением представляются наборами взаимосвязанных объектов соответствующих типов и дальнейшая работа проводится уже с этими объектами.

    Объект DbCommand может выполнять три типа команд, определяемых перечислением CommandType:

  • Text - команда будет выполнять прямой SQL-оператор, который мы укажем в свойстве DbCommand.CommandText
  • StoredProcedure - команда будет выполнять созданную нами ранее процедуру, хранимую в источнике данных. Свойство DbCommand.CommandText представляет имя хранимой процедуры
  • TableDirect - команда извлечет все записи указанной в свойстве DbCommand.CommandText таблицы подключенной БД (не работает с поставщиком SQL Server)
  • Для выполнения созданного и настроенного объекта DbCommand он имеет три команды:

  • ExecuteReader() - выполняет запрос SELECT и возвращает объект DbDataReader, который является оболочкой однонаправленного курсора, доступного только для чтения
  • ExecuteScalar() - выполняет запрос SELECT и возвращает значение первого поля первой строки из набора строк, сгенерированного командой. Этот метод обычно применяется при исполнении агрегатной команды SELECT, использующей функции наподобие COUNT() или SUM() для вычисления единственного значения
  • ExecuteNonQuery() - для выполнения незапросной команды, которая не требует выборки данных с помощью SQL-оператора SELECT. Применяется для исполнения SQL-команд вставки, удаления или обновления записей. Возвращаемое значение означает количество строк, обработанное командой
  • Модуль чтения DbDataReader способен загрузить в себя данные из нескольких таблиц одной БД, т.е. получить несколько наборов результатов. Доступ к считанным наборам результатов можно получить с помощью методов DbDataReader. Вот некоторые из них:

    Некоторые методы DbDataReader
    Метод Описание
    bool Read() Перемещает курсор строки результирующего набора на следующую строку. Этот метод должен быть вызван, также, перед чтением первой строки данных, т.к. после наполнения DbDataReader данными курсор устанавливается перед первой строкой. Метод возвращает true, если есть куда переводить, при применении к последней строке он вернет false
    object GetValue(int ordinal) Возвращает значение поля текущей строки с указанным индексом столбца. Тип возвращенного значения заменяется типом .NET, наиболее подходящим типу столбца в хранилище
    int GetValues(object[ ] values) Заполняет массив values полями текущей записи. Количество полей определяется размером массива, который можно определить по свойству FieldCount
    int GetInt32(int ordinal) Эти методы возвращают значение поля с указанным индексом в текущей строке (нумерайия полей начинается с нуля). Тип поля определяет имя метода
    char GetChar(int ordinal)
    bool GetBoolean(int ordinal)
    System.DateTime GetDateTime(int ordinal)
    bool NextResult() Переводит курсор перед первой строкой следующего набора результатов, если было прочитано несколько наборов составной SQL-командой SELECT
    void Close() Закрывает модуль чтения

    Упражнение 1. Просмотр таблицы с помощью объектов DbCommand и DbDataReader

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

  • Создайте командой File/New/Project новое решение с именем ADO и проект WinForms1 для первого упражнения
  • Поместите на форму из панели Toolbox нужные компоненты и настройте их в соответствии с таблицей свойств
  • Компонент Свойство Значение
    Form Text Упражнение 1
    DataGridView (Name) myDataGrid
      Dock Top
      AutoSizeColumnsMode Fill
    Button (Name) btnPopulate
      Text Заполнить
      AutoSize true
    Button (Name) btnDataBind
      Text Связать
      AutoSize true
    Label (Name) lblRegCount
      Text Количество записей таблицы:
  • В панели Solution Explorer создайте папку Data для корневого узла проекта командой Add/New Folder, вызовите для нее контекстное меню и скопируйте из прилагаемой папки Source файл Northwind.mdb командой Add/Existing Item. При появлении мастера Data Source Configuration Wizard отмените его кнопкой Cancel
  • Выделите в Solution Explorer файл Northwind.mdb и в панели Properties проверьте, что его свойства имеют значение
  • Build Action=Content
  • Copy to Output Directory=Copy always
  • Создайте обработчик для кнопки btnPopulate и btnDataBind
  • Внесите изменения в файл Form1.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;
    using System.Collections;
        
    namespace WinForms1
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
        
                // Отключаем кнопку 'Связать'
                btnDataBind.Enabled = false;
                // Отключить системную кнопку
                this.MaximizeBox = false;
            }
        
            // Создаем объект массива списков
            ArrayList dbRecordsList = new ArrayList();// Поле
        
            // Строка соединения с абсолютным путем к БД, определяемым сборкой
            String ConnectionString(String fileName)
            {
                string JetEngineString = @"Provider=Microsoft.Jet.OLEDB.4.0;"
                    + "Jet OLEDB:Engine Type=5;Data Source=";
                string pathToFile = Application.StartupPath.ToString() + "\\Data\\";
                return JetEngineString + pathToFile + fileName.Trim() + ".mdb";
            }
        
            // Заполнение массива списков данными 
            private void btnPopulate_Click(object sender, EventArgs e)
            {
                // Создаем объект соединения
                OleDbConnection objConnection = 
                    new OleDbConnection(ConnectionString("Northwind"));
        
                dbRecordsList.Clear();// Очищаем список (необязательно!)
        
                // После выполнения тела инструкции using освободится подключение
                using (objConnection)
                {
                    // Создаем объект команды с последующими настройками
                    OleDbCommand objCommand = new OleDbCommand();
                    objCommand.CommandType = CommandType.Text;// Не обязательно, по умолчанию
                    objCommand.CommandText = "SELECT * FROM Customers";
                    objCommand.Connection = objConnection;
        
                    // Или сразу одним конструктором
                    //OleDbCommand objCommand = new OleDbCommand("SELECT * FROM Customers", objConnection);
        
                    objConnection.Open();// Открываем соединение
        
                    // Выполняем объект DbCommand одним из его методов
                    OleDbDataReader dataReader = 
                        objCommand.ExecuteReader(           // Заполнить набор данных
                        CommandBehavior.CloseConnection);   // Отработать и сразу закрыть соединение
        
                    // Счетчик количества записей
                    int recCount = 0;
                    if (dataReader.HasRows)// Флаг поднят, если есть строки 
                    {
                        // Перебрать все строки набора и добавить в коллекцию списка
                        foreach (DbDataRecord rec in dataReader)
                        {
                            dbRecordsList.Add(rec);
                            recCount++;// Не будем использовать, подсчитаем ниже командой
                        }
                    }
        
                    // Подсчитываем число записей в таблице
                    objCommand = new OleDbCommand("SELECT COUNT(*) FROM Customers", objConnection);
                    objConnection.Open();
                    recCount = (int)objCommand.ExecuteScalar();
                    objConnection.Close();
                    lblRegCount.Text += recCount.ToString();
                }
        
                // Переключаем доступность кнопок
                btnPopulate.Enabled = false;
                btnDataBind.Enabled = true;
            }
        
            private void btnDataBind_Click(object sender, EventArgs e)
            {
                // Связываем список с элементом отображения
                myDataGrid.DataSource = dbRecordsList;
        
                btnDataBind.Enabled = false;// Отключаем кнопку
            }
        }
    }
  • Запустите приложение, чтобы получить результат
  • DbDataReader, возвращаемый методом objCommand.ExecuteReader(), позволяет читать данные в соответствии с исполняемой командой SELECT построчно в однонаправленном, доступном только для чтения потоке. Применение DbDataReader - простейший путь извлечения данных из хранилища, но ему недостает возможностей сортировки и связывания автономного DataSet.

    DbDataReader не позволяет выполнять обратный просмотр набора результатов и редактировать их. Это лишь способ последовательного чтения в одном направлении, использующий постоянное подключение к БД. В соответствии со значением перечисления CommandBehavior.CloseConnection соединение с БД закрывается сразу же, как только объект команд закончит чтение данных в DbDataReader. Но для подстраховки в нашем приложении модуль чтения и подключение закрываются автоматически после завершения инструкции using.

    В отключенном режиме загруженные данные находятся в объекте ArrayList и показываются пользователю после связывания с DataGridView. Объект DataGridView, в нашем случае, показывает данные в режиме только для чтения, поскольку 'знает', что ArrayList не является редактируемым.

    Упражнение 2. Выборочный просмотр таблицы с помощью объектов DbCommand и DbDataReader

    Часто пользователь хочет просмотреть часть результатов, хранящихся в некоторой таблице БД. Здесь также может пригодиться модуль чтения DbDataReader. В данном упражнении мы разработаем форму, в которой пользователь выбирает из раскрывающегося списка конкретного служащего и просматривает заказы, обслуживаемые этим сотрудником. Здесь мы вновь будем использовать файловую БД Northwind.mdb, схема связей которой изображена на рисунке, построенном в MS Office Access:

  • Добавьте к решению ADO командой File/Add/New Project новый проект с именем WinForms2 и назначьте его стартовым командой Project/Set as StartUp Project
  • В панели Solution Explorer создайте папку Data для корневого узла проекта командой Add/New Folder и скопируйте в нее перетаскиванием мышью через панель Solution Explorer из предыдущего проекта файл Northwind.mdb. При появлении мастера Data Source Configuration Wizard отмените его кнопкой Cancel
  • Поместите на форму из панели Toolbox нужные компоненты и настройте их в соответствии с таблицей свойств
  • Компонент Свойство Значение
    Form Text Упражнение 2
      MaximizeBox False
    Label Text Сотрудник:
    ComboBox (Name) cbEmployees
    ListBox (Name) lstCustomers
      Dock Bottom

    Вначале мы заполним раскрывающийся список ComboBox сотрудниками, обслуживающими заказы. Список будем заполнять в конструкторе формы.

  • Модифицируйте файл Form1.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;
    using System.Collections;
        
    namespace WinForms2
    {
        public partial class Form1 : Form
        {
            // Создаем объект списка сотрудников
            ArrayList dbEmployeesList = new ArrayList();// Поле
        
            public Form1()
            {
                InitializeComponent();
        
                LoadEmployees();
            }
        
            // Строка соединения с абсолютным путем к БД, определяемым сборкой
            String ConnectionString(String fileName)
            {
                string JetEngineString = @"Provider=Microsoft.Jet.OLEDB.4.0;"
                    + "Jet OLEDB:Engine Type=5;Data Source=";
                string pathToFile = Application.StartupPath.ToString() + "\\Data\\";
                return JetEngineString + pathToFile + fileName.Trim() + ".mdb";
            }
        
            // Заполнение раскрывающегося списка из БД
            void LoadEmployees()
            {
                // Создаем объект соединения
                OleDbConnection objConnection = 
                    new OleDbConnection(ConnectionString("Northwind"));
                // Создаем объект команды
                OleDbCommand objCommand = new OleDbCommand(
                    "SELECT EmployeeID, RTRIM(LastName) + ', ' + RTRIM(FirstName) AS FullName FROM Employees",
                    objConnection);
        
                // Открываем соединение
                objConnection.Open();
                // Читаем в DbDataReader
                OleDbDataReader reader = objCommand.ExecuteReader();
                // Заполняем автономный список записей
                foreach (DbDataRecord rec in reader)
                {
                    dbEmployeesList.Add(rec);
                }
                // Закрываем соединение
                objConnection.Close();
        
                // Связываем автономный список записей с ComboBox
                cbEmployees.BeginUpdate();
                cbEmployees.DataSource = dbEmployeesList;
                cbEmployees.DisplayMember = "FullName";     
                cbEmployees.ValueMember = "EmployeeID";      
                cbEmployees.EndUpdate();
                cbEmployees.SelectedIndex = 0;// Устанавливаем на первый элемент
            }
        }
    }
  • Запустите проект WinForms2 и убедитесь, что раскрывающийся список заполняется информацией из таблицы Employees БД Northwind
  • Теперь, по выбранному сотруднику, мы сформируем список заказов из таблицы Orders, которые этот сотрудник обслуживает. Причем, в этом списке нам нужен будет только идентификатор заказчиков, сделавших заказ. В дальнейшем, загружая информацию из таблицы заказчиков ( Customers ), мы выбирем только тех из них, которые делали заказы у заданного сотрудника. Останется только отобразить этих заказчиков пользователю.

  • Создайте обработчик события SelectedIndexChanged для объекта cbEmployees и дополните класс Form1 проекта WinForms2 следующим кодом
  • private void cbEmployees_SelectedIndexChanged(object sender, EventArgs e)
            {
                LoadOrders();               // Выбираем информацию из таблицы Orders
        
                // Прореживаем список
                ArrayList tmpList = new ArrayList();
                for (int i = 0; i < ordersCustomerID.Count; i++)
                {
                    object val = ordersCustomerID[i];
                    if (tmpList.IndexOf(val) == -1)
                        tmpList.Add(val);
                }
                //ordersCustomerID = tmpList.Clone() as ArrayList;
                ordersCustomerID = tmpList; // Бросаем старый список
                ordersCustomerID.Sort(); // Сортируем список
        
                lstCustomers.Items.Clear(); // Чистим ListBox
                // Отображаем список
                for (int i = 0; i < ordersCustomerID.Count; i++)
                    lstCustomers.Items.Add(ordersCustomerID[i]);
            }
        
            // Получение всех закрепленных за сотрудником заказов
            ArrayList ordersCustomerID = new ArrayList();
            void LoadOrders()
            {
                if (cbEmployees.SelectedIndex == -1)
                    return;
        
                // Создаем объект соединения
                OleDbConnection objConnection =
                    new OleDbConnection(ConnectionString("Northwind"));
                // Создаем объект команды
                OleDbCommand objCommand = new OleDbCommand(
                    "SELECT CustomerID FROM Orders WHERE (EmployeeID = ?)",
                    objConnection);
                objCommand.Parameters.Add(new OleDbParameter());
                objCommand.Parameters[0].Value = cbEmployees.SelectedValue.ToString();
        
                objConnection.Open();   // Открываем соединение
                OleDbDataReader reader = objCommand.ExecuteReader();// Читаем в DbDataReader
                if (reader.HasRows)
                {
                    // Чистим список
                    ordersCustomerID.Clear();
                    while(reader.Read())
                    {
                        ordersCustomerID.Add(reader["CustomerID"]);
                    }
                }
                objConnection.Close();  // Закрываем соединение
            }
  • Запустите проект - список lstCustomers заполняется неповторяющимися идентификаторами заказчиков из поля CustomerID
  • Обратите внимание, что список lstCustomers заполняется только после явного выбора служащего в объекте cbEmployees при возбуждении события cbEmployees.SelectedIndexChanged, но при первом запуске формы остается пустым. Можно это так и оставить, но лучше - пусть заполняется и в самом начале.

  • Выделите на форме объект cbEmployees, в панели Properties установите режим Events, найдите событие BindingContextChanged и подпишите его на уже существующий обработчик cbEmployees_SelectedIndexChanged()
  • Запустите проект - теперь список заполняется и в самом начале появления формы на экране
  • Задача, которую мы поставили в данном упражнении, решена еще не полностью. Цель заключалась в просмотре заказчиков по выбранному служащему, а пока отображаются только идентификаторы заказчиков. Теперь нужно из таблицы Customers для каждого CustomerID, закрепленного за выбранным служащим, запросить значение поля CompanyName и отобразить в списке lstCustomers.

  • Модифицируйте класс Form1 проекта 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;
    using System.Collections;
        
    namespace WinForms2
    {
        public partial class Form1 : Form
        {
            // Создаем объект списка сотрудников
            ArrayList dbEmployeesList = new ArrayList();// Поле
        
            public Form1()
            {
                InitializeComponent();
        
                LoadEmployees();
                LoadCustomers();
            }
        
            // Строка соединения с абсолютным путем к БД, определяемым сборкой
            String ConnectionString(String fileName)
            {
                string JetEngineString = @"Provider=Microsoft.Jet.OLEDB.4.0;"
                    + "Jet OLEDB:Engine Type=5;Data Source=";
                string pathToFile = Application.StartupPath.ToString() + "\\Data\\";
                return JetEngineString + pathToFile + fileName.Trim() + ".mdb";
            }
        
            // Заполнение раскрывающегося списка из БД
            void LoadEmployees()
            {
                // Создаем объект соединения
                OleDbConnection objConnection = 
                    new OleDbConnection(ConnectionString("Northwind"));
                // Создаем объект команды
                OleDbCommand objCommand = new OleDbCommand(
                    "SELECT EmployeeID, RTRIM(LastName) + ', ' + RTRIM(FirstName) AS FullName FROM Employees",
                    objConnection);
        
                // Открываем соединение
                objConnection.Open();
                // Читаем в DbDataReader
                OleDbDataReader reader = objCommand.ExecuteReader();
                // Заполняем автономный список записей
                foreach (DbDataRecord rec in reader)
                {
                    dbEmployeesList.Add(rec);
                }
                // Закрываем соединение
                objConnection.Close();
        
                // Связываем автономный список записей с ComboBox
                cbEmployees.BeginUpdate();
                cbEmployees.DataSource = dbEmployeesList;
                cbEmployees.DisplayMember = "FullName";
                cbEmployees.ValueMember = "EmployeeID";
                cbEmployees.EndUpdate();
                cbEmployees.SelectedIndex = 0;// Устанавливаем на первый элемент
            }
        
            private void cbEmployees_SelectedIndexChanged(object sender, EventArgs e)
            {
                LoadOrders();               // Выбираем информацию из таблицы Orders
        
                // Прореживаем список
                ArrayList tmpList = new ArrayList();
                for (int i = 0; i < ordersCustomerID.Count; i++)
                {
                    object val = ordersCustomerID[i];
                    if (tmpList.IndexOf(val) == -1)
                        tmpList.Add(val);
                }
                //ordersCustomerID = tmpList.Clone() as ArrayList;
                ordersCustomerID = tmpList; // Бросаем старый список
                ordersCustomerID.Sort(); // Сортируем список
        
                lstCustomers.Items.Clear(); // Чистим ListBox 
                // Отображаем список
                for (int i = 0; i < ordersCustomerID.Count; i++)
                {
                    //lstCustomers.Items.Add(ordersCustomerID[i]);
                    int index = customerID.IndexOf(ordersCustomerID[i]);
                    lstCustomers.Items.Add(customerID[index] + "\t - " + companyName[index]);
                }
            }
        
            // Читаем таблицу заказчиков Customers
            ArrayList companyName = new ArrayList();
            ArrayList customerID = new ArrayList();
            void LoadCustomers()
            {
                OleDbConnection objConnection =
                    new OleDbConnection(ConnectionString("Northwind"));
                OleDbCommand objCommand = new OleDbCommand(
                    "SELECT CustomerID, CompanyName FROM Customers",
                    objConnection);
                objConnection.Open();
                OleDbDataReader reader =
                    objCommand.ExecuteReader(CommandBehavior.CloseConnection);
                while (reader.Read())
                {
                    customerID.Add(reader.GetString(0));
                    companyName.Add(reader.GetString(1));
                }
                reader.Close();
            }
        
            // Получение всех закрепленных за сотрудником заказов
            ArrayList ordersCustomerID = new ArrayList();
            void LoadOrders()
            {
                if (cbEmployees.SelectedIndex == -1)
                    return;
        
                // Создаем объект соединения
                OleDbConnection objConnection =
                    new OleDbConnection(ConnectionString("Northwind"));
                // Создаем объект команды
                OleDbCommand objCommand = new OleDbCommand(
                    "SELECT CustomerID FROM Orders WHERE (EmployeeID = ?)",
                    objConnection);
                objCommand.Parameters.Add(new OleDbParameter());
                objCommand.Parameters[0].Value = cbEmployees.SelectedValue.ToString();
        
                objConnection.Open();   // Открываем соединение
                OleDbDataReader reader = objCommand.ExecuteReader();// Читаем в DbDataReader
                if (reader.HasRows)
                {
                    // Чистим список
                    ordersCustomerID.Clear();
                    while(reader.Read())
                    {
                        ordersCustomerID.Add(reader["CustomerID"]);
                    }
                }
                objConnection.Close();  // Закрываем соединение
            }
        }
    }
  • Запустите проект - все работает как задумали
  • Упражнение 3. Выборка данных с помощью объекта DataTable

    Объект System.Data.DataTable применяется для работы с одной таблицей из БД. Класс DataTable содержит три важных коллекции:

  • Columns - содержит ноль и более объектов DataColumn, которые определяют имя, тип хранимых данных и первичный ключ каждого столбца из DataTable
  • Rows - содержит ноль и более объектов DataRow, которые содержат реальные записи таблицы загруженных данных
  • Constraints - содержит объекты типа ForeignKeyConstraint или UniqueConstraint. Первые определяют действия, выполняемые над ключевым столбцом при изменении или удалении строки, вторые используются для обеспечения уникальности всех значений в данном столбце
  • Объекты DataTable могут быть частью набора DataSet, но могут существовать и отдельно. Объект DataTable может заполняться адаптером данных точно также, как и объект DataSet - с помощью метода Fill() адаптера. Для адаптера данных не нужно специально открывать соединение с БД, оно устанавливается автоматически при работе метода Fill() и сразу закрывается после завершения работы метода. DataTable предназначен для работы в качестве автономного кэша табличных данных. Загруженные в DataTable данные можно динамически связывать с элементами отображения.

    В данном упражнении мы соединимся с таблицей Customers БД и загрузим из нее в отображаемый список имена заказчиков CompanyName по начальному словосочетанию ключа CustomerID. При этом для отключенного хранения данных в приложении будем использовать объект DataTable.

  • Добавьте к решению новый проект оконного приложения с именем WinForms3 и назначьте его стартовым
  • Создайте в корне проекта папку Data и скопируйте в нее из предыдущего проекта файл Northwind.mdb (при этом отклоните предложение мастера конфигурации источника данных)
  • Поместите на форму компоненты в соответствии с таблицей
  • Компонент Свойство Значение
    Form Text Упражнение 3
      MaximizeBox False
    Label Text Часть ID заказчика:
    TextBox (Name) txtCustomerID
      Text пусто
    ListBox (Name) lstCustomers
      Dock Bottom
  • Модифицируйте файл 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;
    using System.Collections;
        
    namespace WinForms3
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
        
                LoadCustomers();
            }
        
            // Строка соединения с абсолютным путем к БД, определяемым сборкой
            String ConnectionString(String fileName)
            {
                string JetEngineString = @"Provider=Microsoft.Jet.OLEDB.4.0;"
                    + "Jet OLEDB:Engine Type=5;Data Source=";
                string pathToFile = Application.StartupPath.ToString() + "\\Data\\";
                return JetEngineString + pathToFile + fileName.Trim() + ".mdb";
            }
        
            OleDbDataAdapter adapter;   // Создадим в методе LoadCustomers() 
            DataTable table = new DataTable();
        
            void LoadCustomers()
            {
                String strCommand = "SELECT CustomerID, CompanyName FROM Customers "
                    + "WHERE CustomerID LIKE '" + txtCustomerID.Text.Trim() + "%'";
                try
                {
                    adapter = new OleDbDataAdapter(strCommand, ConnectionString("Northwind"));
                    table.Clear();// Надо очистить, а то будет накопление !!!
                    adapter.Fill(table);
        
                    lstCustomers.Items.Clear();// Очищаем список
                    foreach (DataRow row in table.Rows)
                    {
                        lstCustomers.Items.Add(
                            row["CustomerID"] + "\t - " + row["CompanyName"]);
                    }
        
                    /*
                    // Связываем прочитанные данные с элементом отображения
                    //lstCustomers.Items.Clear(); // Нельзя очищать связанный список!!!!!
                    lstCustomers.DataSource = table;
                    lstCustomers.DisplayMember = "CompanyName";
                    lstCustomers.ValueMember = "CustomerID";
                    */
                }
                catch (OleDbException exc)
                {
                    MessageBox.Show(exc.Message, "Ошибка", 
                        MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
        }
    }

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

    Предикат LIKE команды задает шаблон ' xx% ' проверки соответствия начальной фразы с любым последующим количеством символов поля CustomerID. Шаблон ' %xx% ' выбирает записи с установленной фразой в любом месте поля поиска. Если в шаблон с LIKE передается пустая строка, то будут выбраны все записи указанной таблицы.

  • Создайте обработчик события KeyDown для текстового поля txtCustomerID с тем, чтобы список обновлялся по нажатию пользователем клавиши Enter при завершении ввода очередного значения параметра, и заполните его следующим кодом
  • private void txtCustomerID_KeyDown(object sender, KeyEventArgs e)
            {
                if (e.KeyCode == Keys.Enter)
                    LoadCustomers();
            }
  • Запустите приложение и поэкспериментируйте с его функциональностью, заложенной нами. Одно из представлений будет таким
  • Из кода видно, что объект DataTable является очень удобным средством работы с данными в отсоединенном режиме, как и вся инфраструктура ADO.NET в целом.

    Упражнение 4. Поиск строки DataTable с помощью метода Find()

    Объект DataTable - это находящееся в кэше памяти представление выбранных из БД данных в табличной форме. Он может быть заполнен объединением одной или нескольких таблиц из базы данных. Первоначальное заполнение DataTable выполняется адаптером в соответствии с SQL-запросом. Но после первоначальной загрузки некоторые столбцы могут быть добавлены в DataTable уже как результат вычислительных действий над исходными столбцами в отсоединенном режиме.

    Можно по исходным таблицам создавать новые виртуальные таблицы, готовя их для отображения пользователю в соответствии с уровнем бизнес-логики приложения. Эти таблицы можно наделять свойствами физических таблиц, назначая им ключевые поля и устанавливая отношения между ними типа главная-дочерняя.

    Есть два пути выборки нескольких строк или поиска конкретной строки таблицы: либо сформировать SQL-запрос к исходной таблице БД с заданным условием, либо загрузить исходную таблицу в кэш и уже в нем выполнять требуемую выборку. Объект DataTable имеет встроенные средства выборки нескольких строк из таблицы кэша с их сортировкой или поиска конкретной строки. В последнем случае применяется метод Find(), который работает только со столбцом, содержащим значения первичного ключа. Поэтому перед вызовом метода Find() нужно либо загрузить схему, либо вручную указать первичный ключ. Результатом выборки будет новая таблица, строка или массив строк.

    В данном упражнении мы будем работать с таблицей Customers, схема которой показана на рисунке

    SQL-командой мы полностью загрузим в кэш все столбцы таблицы Customers. В отсоединенном режиме по выбранному CustomerID будем искать соответствующую запись и показывать пользователю значения полей CustomerID, CompanyName, Adress, City, Region и Phone виртуальной таблицы. Столбцы CustomerID и CompanyName динамически свяжем с раскрывающимся списком ComboBox, причем столбец CompanyName сделаем видимым. Значения полей найденной записи будем отображать в элементах TextBox, недоступных для редактирования. Столбцу CustomerID присвоим статус первичного ключа виртуальной таблицы.

  • Добавьте к решению новый проект оконного приложения с именем WinForms4, назначьте его стартовым и создайте пользовательский интерфейс в соответствии с таблицей
  • Объект Свойство Значение Пояснения
    Form1 Text Упражнение 4 Заголовок окна
      MaximizeBox False Отключили системную кнопку
      FormBorderStyle FixedSingle Размеры окна менять нельзя
    Label Text Customer:  
    ComboBox (Name) cbCustomers  
      DropDownStyle DropDownList Текст в поле списка редактировать нельзя
    Label Text Customer ID:  
    TextBox (Name) txtCustomerID  
    Label Text Company Name:  
    TextBox (Name) txtCompanyName  
    Label Text Address:  
    TextBox (Name) txtAddress  
    Label Text City:  
    TextBox (Name) txtCity  
    Label Text Region:  
    TextBox (Name) txtRegion  
    Label Text Phone:  
    TextBox (Name) txtPhone  
    txtCustomerID, txtCompanyName, txtAddress, txtCity, txtRegion, txtPhone BorderStyle FixedSingle Простая рамка вокруг текстового поля

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

  • Защитите текстовые поля от редактирования, для этого: выделите на форме все элементы TextBox и в панели Properties в режиме Events создайте для них общий обработчик события KeyPress, затем общий обработчик события KeyDown. Обработчики заполните так
  • private void txtCustomerID_KeyPress(object sender, KeyPressEventArgs e)
            {
                e.Handled = true;
            }
        
            private void txtCustomerID_KeyDown(object sender, KeyEventArgs e)
            {
                e.Handled = true;
            }
  • В панели Solution Explorer добавьте к корневому узлу проекта WinForms4 папку Data и перетащите в нее мышью из соседнего проекта файл Northwind.mdb (при этом отклоните предложение мастера конфигурации источника данных)
  • Добавьте в начало файла Form1.cs подключения пространства имен сборки System.Data.dll, нужные для обеспечения видимости библиотечных классов
  • // Дополнительные пространства имен
    using System.Data.OleDb;
    using System.Data.Common;
    using System.Collections;
  • Добавьте в класс Form1 метод LoadCustomers(), который загружает таблицу Customers из БД с помощью динамически созданного поставщика данных и SQL-запроса
  • // Строка соединения с абсолютным путем к БД, определяемым сборкой
            String ConnectionString(String fileName)
            {
                string JetEngineString = @"Provider=Microsoft.Jet.OLEDB.4.0;"
                    + "Jet OLEDB:Engine Type=5;Data Source=";
                string pathToFile = Application.StartupPath.ToString() + "\\Data\\";
                return JetEngineString + pathToFile + fileName.Trim() + ".mdb";
            }
        
            DataTable tableCustomers;
            void LoadCustomers()
            {
                OleDbCommand selectCommand = new OleDbCommand();
                selectCommand.CommandText="SELECT * FROM Customers";
                selectCommand.Connection = new OleDbConnection(ConnectionString("Northwind"));
                selectCommand.CommandType = CommandType.Text;
                OleDbDataAdapter adapter = new OleDbDataAdapter(selectCommand);
        
                tableCustomers = new DataTable();// Создать таблицу
                try
                {
                    // Заполнить таблицу
                    adapter.Fill(tableCustomers);
                    // Указать первичный ключ из одного столбца для использования метода Find()
                    tableCustomers.PrimaryKey = 
                        new DataColumn[] { tableCustomers.Columns["CustomerID"] };
                }
                catch (OleDbException exc)
                {
                    tableCustomers = null;
                    MessageBox.Show(exc.Message, "Ошибка",
                        MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
                finally
                {
                    selectCommand.Connection.Close();
                }
        
                // Связываем ComboBox с виртуальной таблицей
                cbCustomers.DataSource = tableCustomers;
                cbCustomers.DisplayMember = "CompanyName";
                cbCustomers.ValueMember = "CustomerID";
            }
  • Вставьте вызов метода LoadCustomers() в конструктор класса Form1 проекта WinForms4
  • public Form1()
            {
                InitializeComponent();
        
                LoadCustomers();
            }
  • Выделите объект cbCustomers и создайте для его события SelectedIndexChanged обработчик. Этот же обработчик зарегистрируйте и для события BindingContextChanged объекта cbCustomers. Заполните обработчик следующим кодом
  • private void cbCustomers_SelectedIndexChanged(object sender, EventArgs e)
            {
                if (tableCustomers == null)
                    return; // При исключении выходить
        
                // Найти запись
                Object findValue = cbCustomers.SelectedValue;// Выделенное значение ValueMember
                DataRow row = tableCustomers.Rows.Find(findValue);
        
                // Заполнить текстовые поля
                if (row != null)
                {
                    txtCustomerID.Text = row["CustomerID"].ToString();
                    txtCompanyName.Text = row["CompanyName"].ToString();
                    txtAddress.Text = row["Address"].ToString();
                    txtCity.Text = row["City"].ToString();
                    txtRegion.Text = row["Region"].ToString();
                    txtPhone.Text = row["Phone"].ToString();
                }
            }
  • Запустите проект: список заполняется сразу при запуске проекта, текстовые поля отображают подробности при выборе заказчика и недоступны для редактирования, как и поле ComboBox
  • Поиск строки в виртуальной таблице DataTable выполняется методом Find() по столбцу CustomerID, назначенному нами первичным ключем. Без этого метод работать не будет. Критерий поиска берется из выбранного пользователем значения списка. Задача решена.

    Упражнение 5. Выборка строк DataTable с помощью метода Select()

    Выборку строк по определенному критерию традиционно можно выполнить с помощью SQL-запроса. Например, можно запросить всех заказчиков из таблицы Customers, имена которых начинаются на " My ". с помощью такой команды

    SELECT * FROM Customers WHERE FirstName LIKE 'My%

    Или запросить всех заказчиков, имена которых заканчиваются на " jon "

    SELECT * FROM Customers WHERE FirstName LIKE '%jon'

    Если нужно выполнять много подобных запросов к одной и той же таблице с помощью SQL, то для этого потребуется часто соединяться с БД, которая может быть занята запросами других пользователей. Хорошо бы было загрузить в кэш полную таблицу и обращаться к ней с запросами сколько угодно раз, не обращаясь к самой центральной БД. Для таких целей предусмотрен метод Select() объекта DataTable, который возвращает коллекцию строк, удовлетворяющих условию выборки. В методе можно также задавать и условие сортировки результирующего набора данных.

    В данном упражнении поставим следующую задачу. В раскрывающийся список считаем из БД всех служащих из таблицы Employees. Загрузим в кэш полную таблицу заказов Orders и дадим возможность пользователю просматривать из этой таблицы те заказы, которые оформил конкретный служащий, выбранный в раскрывающемся списке.

  • Добавьте к решению новый проект оконного приложения с именем WinForms5 и назначьте его стартовым
  • В панели Solution Explorer в корневой узел проекта WinForms5 перетащите мышью из соседнего проекта папку Data с файлом Northwind.mdb (при этом отклоните предложение мастера конфигурации источника данных)
  • Добавьте в начало файла Form1.cs подключения пространства имен сборки System.Data.dll, нужные для обеспечения видимости библиотечных классов
  • // Дополнительные пространства имен
    using System.Data.OleDb;
    using System.Data.Common;
    using System.Collections;
  • Оформите пользовательский интерфейс формы в соответствии с таблицей
  • Объект Свойство Значение Пояснения
    Form1 Text Упражнение 5 Заголовок окна
      MaximizeBox False Отключили системную кнопку
      FormBorderStyle FixedSingle Размеры окна менять нельзя
    Label Text Employee:  
    ComboBox (Name) cbEmployees  
      DropDownStyle DropDownList Текст в поле списка редактировать нельзя
    ListBox (Name) lstOrders  
      Dock Bottom  

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

  • Создайте для события SelectedIndexChanged объекта cbEmployees обработчик и его же присоедините к событию BindingContextChanged для начального заполнения списка lstOrders
  • Добавьте в класс Form1 метод LoadTables(), который загружает таблицы Employees и Orders из БД с помощью динамически созданного поставщика данных и SQL-запроса, а также связывает раскрывающийся список с таблицей Employees
  • Полный код файла Form1 для проекта 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.Collections;
        
    namespace WinForms5
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
        
                LoadTables();
            }
        
            //***********************************************************
            // Строка соединения с абсолютным путем к БД, определяемым сборкой
            // Не используем !!!!!! Заменили объектом-построителем 
            String ConnectionString(String fileName)
            {
                string JetEngineString = @"Provider=Microsoft.Jet.OLEDB.4.0;"
                    + "Jet OLEDB:Engine Type=5;Data Source=";
                string pathToFile = Application.StartupPath.ToString() + "\\Data\\";
                return JetEngineString + pathToFile + fileName.Trim() + ".mdb";
            }
            //**********************************************************
        
            // Поле-ссылка на таблицу Orders
            DataTable tableOrders;
            void LoadTables()
            {
                // Используем построитель строки подключения 
                // для облегчения формирования оной!
                OleDbConnectionStringBuilder objConnectionStringBuilder =
                    new OleDbConnectionStringBuilder();
                objConnectionStringBuilder.Provider = "Microsoft.Jet.OLEDB.4.0";
                objConnectionStringBuilder.DataSource =
                    Application.StartupPath.ToString() + @"\Data\Northwind.mdb";
                objConnectionStringBuilder.PersistSecurityInfo = true;// Шифровать строку
        
                // Строка соединения с источником данных
                String strConnection = objConnectionStringBuilder.ToString();
                // Строка запроса 
                String strSQL = "SELECT EmployeeID, LTRIM(RTRIM(LastName)) + ', '"
                    + "+ LTRIM(RTRIM(FirstName)) AS FullName FROM Employees";
        
                // Создаем поставщик данных для таблицы Employees
                OleDbDataAdapter adapter = new OleDbDataAdapter(strSQL, strConnection);
        
                // Создаем и заполняем виртуальную таблицу 
                DataTable tableEmployees = new DataTable();
                adapter.Fill(tableEmployees);
                tableEmployees.TableName = "Employees";
        
                // Сортируем в прямом порядке: "FullName" или "FullName ASC"
                // Сортировка в обратном порядке: "FullName DESC";
                tableEmployees.DefaultView.Sort = "FullName ASC";
        
                // Связываем ComboBox с виртуальной таблицей
                //cbEmployees.DataSource = tableEmployees; //Связать таблицу несортированной 
                // Связать через DataView сортированной
                cbEmployees.DataSource = tableEmployees.DefaultView;
                cbEmployees.DisplayMember = "FullName";
                cbEmployees.ValueMember = "EmployeeID";
                cbEmployees.SelectedIndex = 0;
        
                // Заполняем виртуальную таблицу Orders всеми записями
                OleDbCommand selectCommand = new OleDbCommand(
                    "SELECT OrderID, EmployeeID, ShipAddress FROM Orders");
                selectCommand.Connection = new OleDbConnection(strConnection);
                adapter = new OleDbDataAdapter(selectCommand);
                tableOrders = new DataTable();
                adapter.Fill(tableOrders);
            }
        
            private void cbEmployees_SelectedIndexChanged(object sender, EventArgs e)
            {
                if (tableOrders == null)
                    return;
        
                // Выбираем строки по условию 
                String filterExpression = "EmployeeID = " + cbEmployees.SelectedValue;
                DataRow[] rows = tableOrders.Select(
                    filterExpression,   // Выражение для фильтра
                    "ShipAddress ASC");     // Поле сортировки по возрастанию
                    // "ShipAddress DESC" - сортировка по убыванию!!!
                // Очищаем список
                lstOrders.Items.Clear();
                // Заполняем список
                foreach (DataRow row in rows)
                {
                    lstOrders.Items.Add(
                        "(" + row["EmployeeID"] + ") "
                        + row["OrderID"].ToString()
                        + "\t" + row["ShipAddress"]);
                }
            }
        }
    }
  • Запустите проект - по выбранному в объекте cbEmployees сотруднику производится выборка закрепленных за ним заказов из таблицы Orders и заполнение списка новой информацией. Соединение с БД выполняется только один раз при формировании таблиц в кэше
  • Мы старались разнообразить код. Например, для формирования строки подключения мы воспользовались вспомогательным классом OleDbConnectionStringBuilder, а при создании адаптера применяли разные перегрузки его конструктора. Метод Select() объекта DataTable производит выборку строк по выражению, которое равноценно условию, применяемому в SQL-запросе после ключевого слова WHERE. Меняя это выражение можно получать различные представления объектной модели таблицы данных.

    Упражнение 6. Вычисляемые столбцы объектов DataTable

    Данные, которые хранятся в БД в табличной форме, представляют собой базовую информацию. Но некоторые данные, интересующие пользователя, можно получить путем выполнения определенных действий над исходными данными и показать результат в отдельном столбце таблицы представления. Для этого существуют различные варианты SQL-запросов, включающие в себя различные функции и агрегатные выражения. Например, команда

    SELECT EmployeeID, LastName + ', ' + FirstName AS FullName FROM Employees

    формирует в кэше из таблицы Employees два столбца, первый из которых представляет столбец EmployeeID исходной таблицы, а второй - объединение данные двух исходных столбцов под псевдонимом FullName.

    Команда

    SELECT ProductID, ProductName, UnitPrice + UnitPrice * ? AS (Total Price)
        FROM Products

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

    Выборку данных выполняет поставщик на стороне базы данных и в кэш поступают уже готовые данные. Дополнительный столбец пересылается в открытом потоке в память компьютера, что может перегружать трафик при большом количестве запросов и большом объеме загружаемых данных. В ADO.NET существуют средства выполнения подобной работы на стороне клиента, когда в компьютер поступают базовые столбцы таблицы и на лету к ним присоединяются вычислимые сторбцы, нужные пользователю для работы.

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

    Вычислимые столбцы нужно добавлять к объекту таблицы до заполнения ее данными из поставщика. Отдельные столбцы представляются в объектной модели таблицы классом DataColumn. Новый объект DataColumn добавляется в таблицу методом Add() свойства-коллекции DataTable.Columns.

    Добавляемый в таблицу объект столбца должен содержать имя столбца (свойство ColumnName ), тип данных (свойство DataType ) и выражение (свойство Expression ) при необходимости. Выражение используется для фильтрации строк, вычисления значений в столбцах на основании других столбцов или для создания агрегированного (объединенного) столбца. Эти данные передаются в конструктор при создании объекта столбца или сразу в одну из перегрузок метода Add() при добавлении в коллекцию столбцов таблицы.

    Типы данных, которые могут быть значениями свойства DataColumn.DataType, ограничиваются следующим списком пространства имен System:

    Boolean (bool) Decimal Int64 TimeSpan
    Byte (byte) Double (double) SByte UInt16
    Char (char) Int16 Single (float) UInt32
    DateTime Int32 (int) String UInt64

    Свойство DataColumn.DefaultValue показывает, какое значение имеет по умолчанию данный столбец. Если столбцу не присвоено никакого значения, а значение по умолчанию не определено, то столбец имеет значение System.DBNull. Поскольку типы данных реляционной таблицы БД и виртуальной таблицы объектной модели могут быть разными, рекомендуется явно привести тип столбца к нужному вычислимому типу. И это нужно сделать до загрузки объектной таблицы!!!

    Таблица DataTable, помимо прочего, имеет три коллекции в качестве свойств только для чтения:

  • Columns - содержит объекты столбцов вместе с их именами и типом
  • Rows - содержит фактические данные, упакованные в объектах строк
  • Constraints - содержит экземпляры классов ForeignKeyConstraint или UniqueConstraint, задающих свойства в отношении ключевых или уникальных значений полей
  • После добавления вычислимого столбца в коллекцию Columns объекта таблицы к нему можно обращаться в Columns по имени или порядковому номеру. Для иллюстрации сказанного выполните данное упражнение в следующем порядке:

  • Добавьте к решению проект оконного приложения с именем WinForms6 и назначьте его стартовым
  • Настройте пользовательский интерфейс в соответствии с таблицей
  • Компонент Свойство Значение
    Form Text Упражнение 6
      MaximizeBox False
    DataGridView (Name) dataGridView
      Dock Fill
      AutoSizeColumnsMode Fill
  • Заполните файл Form1.cs проекта WinForms6 следующим образом
  • 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.Collections;
        
    namespace WinForms6
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
        
                LoadProducts();
            }
        
            // Строка соединения к БД с абсолютным путем, определяемым сборкой
            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 LoadProducts()
            {
                // Формируем поставщик 
                OleDbCommand selectCommand = new OleDbCommand();
                selectCommand.CommandText = 
                    "SELECT ProductID, ProductName, UnitPrice AS Price FROM Products";
                selectCommand.Connection = new OleDbConnection(ConnectionString());
                selectCommand.CommandType = CommandType.Text;
                OleDbDataAdapter adapter = new OleDbDataAdapter(selectCommand);
                
                // Создаем объект таблицы
                DataTable tableProducts = new DataTable();  
              
                // Задаем тип столбца, подлежащего заполнению данными из БД
                // (ДО ЕГО ЗАПОЛНЕНИЯ!!!)
                tableProducts.Columns.Add("Price");
                tableProducts.Columns["Price"].DataType = typeof(System.Decimal);
        
                // Добавляем и настраиваем вычислимый столбец для величины наценки
                tableProducts.Columns.Add("Margin", typeof(System.Decimal));
                tableProducts.Columns[1].Expression = "Price * 0.086";
        
                // Добавляем  и настраиваем вычислимый столбец для потребительской цены
                DataColumn totalPrice = new DataColumn("Total Price");
                totalPrice.DataType = System.Type.GetType("System.Decimal");
                totalPrice.Expression = "Price + Margin";
                tableProducts.Columns.Add(totalPrice);
                
                // Заполняем таблицу
                adapter.Fill(tableProducts);
        
                // Связываем таблицу с элементом отображения
                dataGridView.DataSource = tableProducts;
        
                Object result = tableProducts.Compute("Sum(Margin)", null);
                MessageBox.Show(String.Format("Суммарная наценка на товар\n"
                    + "(по курсу 30 рублей за доллар): {0:C}",// Спецификатор денежного формата
                    Convert.ToDecimal(result) * 30.0m));
        
                result = tableProducts.Compute("Count(Price)", "Price < 10");
                MessageBox.Show(String.Format("Количество товара с ценой меньше 10$: {0} шт.", result));
            }
        }
    }
  • Запустите приложение - результат будет таким
  • Упражнение 7. Работа с объектом DataView

    Объект DataView выполняет представление одних и тех же данных загруженной таблицы БД в нескольких разных форматах по принципу "Один документ - много представлений". DataView работает только с таблицей DataTable и готовит для отображения все ее столбцы без исключения. В присоединенном к таблице объекте DataView данные можно сортировать или задавать некоторые условия выборки строк представляемой таблицы. DataView физически не включает в себя данные отображаемой таблицы, он только изменяет их представление в элементе отображения "на лету".

    Можно для одной таблицы заготовить несколько объектов DataView, каждый со своими настройками, и поочередно связывать их с элементом отображения. При этом в памяти компьютера будет находиться только одна копия данных, что существенно экономит ресурсы. Сам экземпляр объекта DataView можно настроить так, чтобы он запрещал редактирование данных связанной с ним таблицы, хотя в другом объекте такие ограничения на ту же самую таблицу могут отсутствовать. Операцию вставки, обновления или удаления данных в связанной таблице разрешают (запрещают) через свойства AllowNew, AllowEdit, AllowDelete. Если флаг AllowNew разрешает вставку, то новая строка добавляется в DataView методом AddNew().

    При заполнении таблицы DataTable программно или данными БД строки отображаются в том порядке, в котором они были вставлены в таблицу или извлечены из БД. С любой таблицей уже связан один объект DataView, ссылка на который носит имя свойства DefaultView только для чтения. Через этот объект-представление мы можем сортировать представление данных таблицы и связывать элемент отображения не с самой таблицей, а с ее отсортированным представлением - объектом DefaultView.

    Если встроенного представления нам мало, можно создать и настроить еще ряд объектов DataView и по мере надобности подключать их к свойству DataSource элемента отображения данных. Связанную с элементом отображения таблицу сортировать нельзя, как и сам связанный с таблицей элемент отображения. Но если между таблицей и элементом отображения имеется посредник DataView, то фильтрацию и сортировку отображаемых данных исходной таблицы можно выполнять через него.

    У каждого элемента DataRow есть свойство RowState, в котором ведется история состояния строки при ее редактировании. Это свойство автоматически принимает одно из значений перечисления DataRowState при выполнении действий над строкой:

  • Detached - строка только что создана методом DataTable.NewRow(), но еще не добавлена в таблицу методом DataTable.Rows.Add()
  • Added - строка добавлена в коллекцию таблицы, но не помечена как окончательно принятая один из методов: DataRow.AcceptChanges(), DataTable.AcceptChanges(), DataSet.AcceptChanges()
  • Deleted - строка помечена как удаленная из таблицы методом DataRow.Delete(), но не принята окончательно методом AcceptChanges()
  • Modified - строка была изменена, но еще не принята
  • Unchanged - строка не изменилась со времени последнего вызова AcceptChanges()
  • Объектная модель таблицы хранит несколько версий одной и той же строки. Эти версии строки зависят от того, применялся или нет к ней метод AcceptChanges(). Значения версий определяются перечислением DataRowVersion и могут иметь значения: Current, Default, Original, Proposed. Какие версии имеет строка, можно проверить с помощью ее метода DataRow.HasVersion(DataRowVersion), например, следующий код распечатает все существующие версии указанной строки

    void CheckVersion(DataRow row)
            {
                if (row.HasVersion(DataRowVersion.Current))
                    Console.WriteLine("Версия Current");
                if (row.HasVersion(DataRowVersion.Default))
                    Console.WriteLine("Версия Default");
                if (row.HasVersion(DataRowVersion.Original))
                    Console.WriteLine("Версия Original");
                if (row.HasVersion(DataRowVersion.Proposed))
                    Console.WriteLine("Версия Proposed");
            }

    Указанные версии строки связаны с ее редактированием и вызовом метода AcceptChanges() объектов DataRow, DataTable или DataSet следующим образом:

  • После вызова метода DataRow.BeginEdit(), если значение строки будет изменено, станут доступными версии Current и Proposed (текущий и предложенный)
  • После вызова метода DataRow.CancelEdit() будет удалена версия Proposed
  • После вызова метода DataRow.EndEdit() версия Proposed становится версией Current
  • После вызова метода DataRow.AcceptChanges() версия Proposed становится версией Current ; версия Original не изменяется
  • После вызова метода DataTable.AcceptChanges() версия Original становится идентичной версии Current
  • После вызова метода DataTable.RejectChanges() (- отклонить изменения) версия Original становится версией Current
  • Объект DataView имеет свойства, позволяющие управлять представляемыми данными:

  • RowFilter - позволяет задать критерий выбора представляемых строк, например
    RowFilter="Price < 10"; 
      RowFilter="LastName LIKE ' W%' "; 
      RowFilter="ProductID = 1"
  • RowStateFilter - позволяет выбирать строки, находящиеся в определенном состоянии. Значение этого свойства определяется перечислением System.Data.DataViewRowState:
  • Added - новые строки
  • CurrentRows - текущие строки, включая неизмененные, новые и модифицированные
  • Deleted - строки, помеченные как удаленные
  • ModifiedCurrent - ближайшая модифицированная версия данных, полученная из ModifiedOriginal
  • ModifiedOriginal - предыдущая версия данных, из которой получена версия ModifiedCurrent
  • None - пустое значение
  • OriginalRows - первоначальные строки, загруженные из БД
  • Unchanged - строка не менялась
  • Например, следующая функция настраивает DataView на представление строк таблицы предыдущего или следующего состояния модификации

    DataView ViewChanges(DataTable table, bool viewCurrent)
            {
                DataView view = table.DefaultView;
        
                if (viewCurrent == true)
                    view.RowStateFilter = DataViewRowState.ModifiedCurrent;
                else
                    view.RowStateFilter = DataViewRowState.ModifiedOriginal;
        
                return view;
            }
  • Sort - это свойство указывает порядок сортировки представляемых данных, например Sort="FullName ASC" ; Sort="FullName DESC" ; - сортируют в прямом и обратном порядке представление таблицы по полю FullName
  • Объект DataView имеет два метода поиска данных для выборки в представление, оба из которых осуществляют поиск в отсортированном (в свойстве Sort ) столбце по заданному критерию:

  • DataView.Find() - ищет первое вхождение строки в DataView и возвращает номер строки, или -1
  • DataView.FindRows() - ищет массив строк и возвращает его, или пустой массив
  • В данном упражнении элементы отображения мы свяжем не напрямую с таблицей, заполненной данными, а с промежуточным объектом DataView, в котором легко можно менять представления данных пользователю. В коде попробуем продемонстрировать основные возможности объекта DataView. В режиме проектирования интерфейса установим простейшие настройки его элементов, а остальное выполним динамически в коде приложения.

  • Добавьте в решение новый проект с именем WinForms7 и оформите интерфейс пользователя в соответствии с таблицей
  • Объект Свойство Значение Пояснения
    Form Text Упражнение 7 Заголовок окна
      MaximizeBox False Запретить системную кнопку
    Panel Dock Top Верхний контейнер для Label и ComboBox
    Panel Dock Fill Нижний контейнер для DataGrid
    Label Text Letter: Объекты Label и ComboBox поместите в верхний контейнер Panel
    ComboBox (Name) cbLetters Для размещения букв латинского алфавита
      FlatStyle Flat Плоский стиль элемента
    Label Text Column:  
    ComboBox (Name) cbColumns Для размещения имен столбцов
    DataGrid (Name) dataGrid Объект DataGrid поместите в нижний контейнер Panel
      AllowSorting False Запретить сортировку столбцов через DataGrid
      CaptionText Представления таблицы Customers Заголовок DataGrid
      Dock Fill Развернуть на весь контейнер Panel
      ReadOnly True Запретить редактирование данных
  • Модифицируйте файл Form1.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 WinForms7
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
        
                // Подписка на события ComboBox общего обработчика
                cbLetters.SelectedIndexChanged += Combo_SelectedIndexChanged;
                cbColumns.SelectedIndexChanged += Combo_SelectedIndexChanged;
        
                // Загрузки объектов данными
                LoadCustomers();
                LoadLetters();
                LoadColumns();// Вызывать после загрузки данных LoadCustomers()
            }
        
            // Строка соединения к БД с абсолютным путем, определяемым сборкой
            String ConnectionString()
            {
                // Используем построитель строки подключения 
                OleDbConnectionStringBuilder objConnectionStringBuilder =
                    new OleDbConnectionStringBuilder();
                objConnectionStringBuilder.Provider = "Microsoft.Jet.OLEDB.4.0";
                objConnectionStringBuilder.DataSource =
                    Application.StartupPath.ToString() + @"\Data\Northwind.mdb";
        
                return objConnectionStringBuilder.ToString();
            }
        
            DataTable tableCustomers;// Поле, видимое в методах
            void LoadCustomers()
            {
                // Формируем поставщик 
                OleDbCommand selectCommand = new OleDbCommand();
                selectCommand.CommandText =
                    "SELECT * FROM Customers";// Будем читать все столбцы
                selectCommand.Connection = new OleDbConnection(ConnectionString());
                selectCommand.CommandType = CommandType.Text;// По умолчанию
                OleDbDataAdapter adapter = new OleDbDataAdapter(selectCommand);
        
                // Создаем объект таблицы
                tableCustomers = new DataTable();
        
                // Заполняем таблицу
                adapter.Fill(tableCustomers);
                // Адресуемся к встроенному в таблицу представлению
                DataView defaultView = tableCustomers.DefaultView;
        
                // Связываем встроенное представление с элементом отображения
                dataGrid.DataSource = defaultView;
            }
        
            void LoadLetters()
            {
                // Добавили первый элемент списка
                cbLetters.Items.Add("All");
        
                // Дополняем список буквами алфавита
                for (char ch = 'A'; ch <= 'Z'; ch++)
                {
                    cbLetters.Items.Add(ch);
                }
        
                // Устанавливаем на первый индекс 
                cbLetters.SelectedIndex = 0;
            }
        
            void LoadColumns()
            {
                // Создаем временную таблицу для имен столбцов
                DataTable tableTmp = new DataTable();
                // Добавляем один столбец для хранения имен столбцов 
                tableTmp.Columns.Add("ColumnName", typeof(String));
                // Заполняем временную таблицу именами столбцов исходной таблицы
                for (int i = 0; i < tableCustomers.Columns.Count; i++)
                {
                    tableTmp.Rows.Add(tableCustomers.Columns[i].ColumnName);
                }
                // Создаем представление по единственному столбцу
                DataView view = new DataView(tableTmp);
        
                // Связываем представление с элементом отображения
                cbColumns.DataSource = view;
                cbColumns.DisplayMember = "ColumnName";
        
                // Ищем нужное имя и позиционируем список
                view.Sort = "ColumnName";// Сортируем для списка имен столбцов
                int selectedIndex = view.Find("CustomerID");// По отсортированному можно
                cbColumns.SelectedIndex = selectedIndex;// Позиционируем программно на CustomerID
            }
        
            private void Combo_SelectedIndexChanged(object sender, EventArgs e)
            {
                // Еще не созданы
                if (cbLetters == null || cbColumns == null)
                    return;
                // Еще ничего не выбрано
                if (cbLetters.SelectedIndex == -1 || cbColumns.SelectedIndex == -1)
                    return;
        
                // Извлекаем символ из cbLetters
                String letter = cbLetters.SelectedItem.ToString();
                if (letter == "All")
                    letter = String.Empty;
        
                // Извлекаем из cbColumns, связанного с DataView, выбранное имя столбца 
                Object obj = cbColumns.SelectedItem;
                String columnName = ((DataRowView)obj).Row["ColumnName"].ToString();
        
                // Создаем для таблицы Customers объект представления и настраиваем его
                DataView view = new DataView(tableCustomers);
                view.RowFilter = "CustomerID LIKE '" + letter + "%'";
                view.Sort = columnName;
        
                // Связываем настроенное представление с объектом отображения
                dataGrid.DataSource = view;
            }
        }
    }
  • Запустите приложение и проверьте его функциональность
  • Вернуться к учебному плану