Опубликован: 05.08.2010 | Уровень: специалист | Доступ: платный
Самостоятельная работа 6:

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

Упражнение 2. Добавление и удаление строк в таблице

В данном упражнении рассмотрим возможности инфраструктуры 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;
            }
        }
    }
}
Алексей Бабушкин
Алексей Бабушкин

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

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Text;

using System.Windows.Forms;

using Microsoft.Xna.Framework.Graphics;

   

namespace Application1

{

    public partial class MainForm : Form

    {

        // Объявим поле графического устройства для видимости в методах

        GraphicsDevice device;

   

        public MainForm()

        {

            InitializeComponent();

   

            // Подпишемся на событие Load формы

            this.Load += new EventHandler(MainForm_Load);

   

            // Попишемся на событие FormClosed формы

            this.FormClosed += new FormClosedEventHandler(MainForm_FormClosed);

        }

   

        void MainForm_FormClosed(object sender, FormClosedEventArgs e)

        {

            //  Удаляем (освобождаем) устройство

            device.Dispose();

            // На всякий случай присваиваем ссылке на устройство значение null

            device = null;       

        }

   

        void MainForm_Load(object sender, EventArgs e)

        {

            // Создаем объект представления для настройки графического устройства

            PresentationParameters presentParams = new PresentationParameters();

            // Настраиваем объект представления через его свойства

            presentParams.IsFullScreen = false; // Включаем оконный режим

            presentParams.BackBufferCount = 1;  // Включаем задний буфер

                                                // для двойной буферизации

            // Переключение переднего и заднего буферов

            // должно осуществляться с максимальной эффективностью

            presentParams.SwapEffect = SwapEffect.Discard;

            // Устанавливаем размеры заднего буфера по клиентской области окна формы

            presentParams.BackBufferWidth = this.ClientSize.Width;

            presentParams.BackBufferHeight = this.ClientSize.Height;

   

            // Создадим графическое устройство с заданными настройками

            device = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, DeviceType.Hardware,

                this.Handle, presentParams);

        }

   

        protected override void OnPaint(PaintEventArgs e)

        {

            device.Clear(Microsoft.Xna.Framework.Graphics.Color.CornflowerBlue);

   

            base.OnPaint(e);

        }

    }

}

Выбрасывается исключение:

Невозможно загрузить файл или сборку "Microsoft.Xna.Framework, Version=3.0.0.0, Culture=neutral, PublicKeyToken=6d5c3888ef60e27d" или один из зависимых от них компонентов. Не удается найти указанный файл.

Делаю все пунктуально. В чем может быть проблема?

Dmitriy Ivanchenko
Dmitriy Ivanchenko
Украина, Кировоград, Виктория-П, 2011
Татьяна Ковалюк
Татьяна Ковалюк
Украина, Киев, Киевский политехнический институт, 1974