Rambler's Top100
Главная
Новости
Статьи
Форумы
Книги
Коды
Сообщество
Блоги
О нас
 

Логин

Email:
  Пароль:

Войти
Зарегистрироваться
Забыл пароль

Поиск

 Искать :
 
Вперед

Работа с данными. Вчера, сегодня, завтра...

Работа с данными. Вчера, сегодня, завтра...

Автор: Serg Vorontsov aka V©R©N  (voron@aspnetmania.com)
Опубликовано: 20 March 2002
Уровень: Учебник

Версия для печати

Проголосовало 96 читателей
Средняя оценка 4.33

Все течет все изменяется.....

Введение

Не хотелось бы делать очередное глобальное сравнение двух различных технологий, двух различных подходов и что самое важное двух эпох в методах доступа к данным. Этими сравнениями пестрят все настоящие книги по ADO.Net. Но, к сожалению, для того, что бы действительно по настоящему понять, что по настоящему отличает технологии ADO и ADO.Net мне все же придется сделать краткий экскурс в эту область. И особенно это будет полезно для тех, кто имеет опыт разработок под ADO / OLE DB.

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

Я постараюсь привести основную сводную таблицу различий, для того чтобы не уходить глубоко в сравнение. И далее, по мере надобности, буду указывать отличия в случае необходимости. Итак, что мы имеем? А имеем мы следующую таблицу основных различий:

Опция

ADO

ADO.Net

Хранение в памяти

Использование Recordset для связывания одной или объединений нескольких таблиц имитирующей одну таблицу.

Использование Dataset как объекта-контейнера для одной или коллекции таблиц являющейся фактически snapshot БД со всеми условиями и связями.  

Чтение данных

Загрузка таблиц в объект Recordset (forward & read only) последовательное и только чтение.

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

Связи между таблицами

Данные группируются из нескольких таблиц посредством команды SQL: JOIN

Организовываются связи между DataTable внутри DataSet при помощи объекта DataRalations

Доступ к данным

Объект Connection был связан с базой данных посредством OLE DB провайдера. Recordset обычно использовал объект Connection для возможности работы с БД.

Использование Managed Provider для связи с базой данных. Managed Provider работает напрямую с SQL Server 2000 Api. С остальными  источниками данных работает через Ole Db провайдера.

Отсоединенные данные

(Disconnected Data)

Работа с данными велась преимущественно с подключенным соединением. Единственным способом для работы с отключенными данными было использование прочитанных данных,  но без возможности изменения и добавления.

Абсолютно новая идеология. Полностью отсоединенная модель данных со своей локальной (in-memory data cache) моделью данных. Связь с основной базой через объект DataAdapter, который позволяет синхронизировать данные с основной БД. Данная модель позволяет помимо того, что  проверять, какие данные были изменены локально, но и были ли изменены данные на сервере за время работы.

Работа с отсоединенными данными.

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

В DataSet модели имеется возможность сериализации данных через XML. Этот формат позволяет сохранить не только данные, но и схему данных. Кроме того, у DataSet для сериализации-десериализации существует специальное множество методов дающих возможность делать это крайне эффективно и легко. Кроме того, предложенный транспорт прекрасно работает  в условиях firewalls (HTTP) защищенных сетей, потому что этот формат построен на текстовой основе.

Масштабируемость

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

Схема с отсоединенным набором данных напротив предполагает минимальные затраты ресурсов сервера БД, ввиду того что нет необходимости держать открытым соединения длительное время. Тем самым, работа с отсоединенными данными на текущий момент имеет очень большие перспективы для распределенных приложений.

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

В данном проекте мы увидим применение на практике таких объектов доступа и управления данными, как:

  • SqlConnection
  • SqlCommand
  • SqlParameter
  • SqlDataAdapter
  • SqlCommandBuilder

И все таки практика.

Давайте попытаемся перейти от теоретических доводов к практическим. Что предлагает нам Visual Studio .Net для удобства работы с данными!? Для работы с компонентами данных имеется несколько вариантов. Мы можем вызвать комплексный визард доступа к БД (Data Form Wizard), который идет в стандартной поставке Visual Studio .Net, но я думаю, что к его рассмотрению мы вернемся чуть позже, ввиду того, что нам нужно будет рассмотреть его более детально. Он состоит из многих этапов, к которым мы пока еще не готовы, а они буду очень важны для перехода к понятиям типизированных датасетов. Я думаю, что этот визард будет рассмотрен в последующих статьях, в которых мы будем рассматривать понятие типизированного датасета.

А на текущий момент мы пойдем немного более простым путем, в котором сможем сделать несколько немногочисленных действий осознанно, что очень важно. Давайте рассмотрим более детально схему взаимодействия классов Ado.Net при подключении к базе данных.


Рис.1

Из схемы видно что первоначально мы должны создать объект Connection. Давайте возьмем для примеров объекты SQL. Принцип работы с OleDB и ODBC объектами мало, чем отличается от SQL.

Объект Connection в нашем случае будет реализовывать класс SqlConnection. Я думаю, настал момент для создания тестового проекта, который мы будем постепенно наращивать. В примерах для данной статьи я буду использовать тестовую базу данных Nortwind.

Создайте новый проект Winforms Application и дайте ему название DataWorking.


Рис.2

Далее нажмите «Ok». После чего, я надеюсь, вы получите вот такой код.

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;

namespace DataWorking
{
  /// 
  /// Summary description for Form1.
  /// 
  public class Form1 : System.Windows.Forms.Form
  {
    /// 
    /// Required designer variable.
    /// 
    private System.ComponentModel.Container components = null;

    public Form1()
    {
      //
      // Required for Windows Form Designer support
      //
      InitializeComponent();
      //
      // TODO: Add any constructor code after InitializeComponent call
      //
    }

    /// 
    /// Clean up any resources being used.
    /// 
    protected override void Dispose( bool disposing )
    {
      if( disposing )
      {
        if (components != null) 
        {
          components.Dispose();
        }
      }
      base.Dispose( disposing );
    }

    #region Windows Form Designer generated code
    /// 
    /// Required method for Designer support - do not modify
    /// the contents of this method with the code editor.
    /// 
    private void InitializeComponent()
    {
      // 
      // Form1
      // 
      this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
      this.ClientSize = new System.Drawing.Size(656, 266);
      this.Name = "Form1";
      this.Text = "Form1";

    }
    #endregion

    /// 
    /// The main entry point for the application.
    /// 
    [STAThread]
    static void Main() 
    {
      Application.Run(new Form1());
    }
  }
}

Я намеренно привел весь стартовый код проекта в статье, для того чтобы потом было легче по нему ориентироваться. Итак, начнем изменения. Исходя из рисунка 1 мы должны создать объект Connection для чего мы воспользуемся экземпляром класса SqlConnection. Это можно сделать несколькими способами: один с помощью дизайнера другой при помощи рук. Давайте, совместим полезное с приятным. В дизайнере мы открываем окно View>ToolBox и уже в окне ToolBox переходим к закладке Data. На что еще хотелось бы обратить Ваше внимание так это на то, что Ваша форма должна быть видимой в окне дизайнера иначе окно ToolBox не даст Вам возможности выбрать ни одного контрола, что логично.


Рис.3

И перетаскиваем компонент SqlConnection на форму. После чего мы видим компонент SqlConnection на поле дизайнера. На текущий момент в нашем проекте добавился следующий код:

...
  public class Form1 : System.Windows.Forms.Form
  {
    private System.Data.SqlClient.SqlConnection sqlConnection;
    /// 
    /// Required designer variable.
    /// 
    private System.ComponentModel.Container components = null;

    public Form1()
...

и в еще одном месте:

...
  private void InitializeComponent()
  {
    this.sqlConnection = new System.Data.SqlClient.SqlConnection();
...

Дизайнер вставил свой код для создания объекта SqlConnection. Первые замечания по тому что мы видим: в объявлении экземпляра класса он вставил полный путь к классу т.е.: System.Data.SqlClient.SqlConnection. На практике же это не очень удобно, поэтому пользуются директивой using System.Data.SqlClient; которая позволяет подставить неймспейс для класса чтобы не указывать в дальнейшем полный путь к классу, т.е. в этом случае вам оставалось бы написать только: this.sqlConnection = new SqlConnection();

И еще, метод private void InitializeComponent() создается каркасом приложения специально для того чтобы в него помещал свой код дизайнер и потому при любом изменении в дизайнере форм он ПОЛНОСТЬЮ обновляется – потому я бы не рекомендовал Вам помещать какой либо свой код в тело этого метода.

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

Переходим в окно дизайнера и по щелчку правой клавиши мышки, предварительно выбрав компонент уже размещенный нами и называемый sqlConnection1, вызываем окно Properties для данного компонента. Тут мы производим следующий манипуляции: переименовываем объект sqlConnection и вызываем визард для построения строки подключения к базе Nordwind.


Рис.4

Заполняем поля визарда согласно рисунка 4 и проверяем соединение, нажав на кнопку Test Connection. Если все сделано правильно, то мы должны получить окошко об успешном тестировании.

После выполнения этих операций мы получаем очередные изменения кода:

  ...
  private void InitializeComponent()
  {
    this.sqlConnection = new System.Data.SqlClient.SqlConnection();
    // 
    // sqlConnection
    // 
    this.sqlConnection.ConnectionString = "data source=.;initial catalog=Northwind;" +
        " integrated security=SSPI;persist security" +
        " info=False;workstation id=VORON;packet size=4096";
      // 
  ...

Это стандартная строка для подключения через Sql Server Data Provider.

Согласно общей схеме подключения к данным в Ado.Net следующим нашим шагом должно стать создание классов SqlCommand, работающих через наш уже созданный объект SqlConnection. Но поскольку на текущий момент мы пользуемся визардом, мы сразу начнем создавать объект типа SqlDataAdapter. Когда мы будем использовать визард по созданию и конфигурированию SqlDataAdapter, он создаст нам столько объектов SqlCommand сколько мы определим в его свойствах, но естественно это не может быть больше четырех предопределенных: Select, Insert, Update и Delete. Давайте вернемся к рисунку 1 и посмотрим, какую роль выполняют классы SqlCommand. Разнести команды четырех типов для каждого объекта было очень важным и нужным решением, потому что вы можете определить абсолютно разные алгоритмы загрузки, сохранения, вставки и удаления данных. Для примера, загрузка объекта может проводится через вьювер базы данных а сохранение данных через хранимую процедуру. А удаление может быть реализовано простым запросом на Т-Sql. И хотя визард н позволяет конфигурировать классы таким гибким образом, ничто не помешает в дальнейшем сделать вам это «руками». Итак, после небольшого отступления, продолжим наши упражнения. Создаём экземпляр SqlDataAdapter. Для этого повторяем уже известные нам действия: перетаскиваем объект SqlDataAdapter из панели ToolBox->Data на форму дизайнера:


Рис.5

После того как вы отпустите кнопку мышки, у вас отобразится первое окно визарда создания и конфигурирования SqlDataAdaptera:


Рис.6

Следующий шаг, который предоставляет нам визард, будет выбор подключения к БД:


Рис.7

Обратите внимание, что этап создания нового объекта connection можно возложить и на визард по созданию SqlDataAdapter, и вам было бы предложена та же форма конфигурирования, которую мы рассмотрели выше. Но в данной ситуации, мы откажемся от этапов по созданию и конфигурированию адаптера с помощью визарда, ввиду того, что будет сгенерированно много исходного кода, который бы хотелось разобрать поэтапно и более подробно. Кроме того, на данной форме по логике должен быть доступен созданный нами ранее объект SqlConnection, но почему то в списке существующих connection визарда его не видно. Почему? Это вопрос не ко мне, а к представителям Microsoft. :) Издержки окружения так сказать.

Вообще, справедливо говоря, существует довольно много этапов по созданию и конфигурированию данных – например простое перетягивание нужной Вам таблицы из окна Serer Explorer на форму дизайнера. В таком случае у вас сразу будет сконфигурирована целая группа объектов: SqlConnection, SqlDataAdapter, SqlCommand и после вам останется только создать объект DataSet и у вас уже будет готов полный доступ к нужной вам таблице.. Но это мы чуть опередили события, а пока мы пойдем своим, более сложным путем для того, что бы понять и разобраться более подробно в том, что потом будет возможно делаться на полном «автомате». Но как известно – визард не отличается «умом и сообразительностью» и потому вам регулярно придется что то править в коде который он сгенерирует.

Вернемся к нашему проекту и посмотрим на изменения кода, которые произошли:

  ...
public class Form1 : System.Windows.Forms.Form
  {
    private System.Data.SqlClient.SqlConnection sqlConnection;
    private System.Data.SqlClient.SqlCommand sqlSelectCommand1;
    private System.Data.SqlClient.SqlCommand sqlInsertCommand1;
    private System.Data.SqlClient.SqlCommand sqlUpdateCommand1;
    private System.Data.SqlClient.SqlCommand sqlDeleteCommand1;
    private System.Data.SqlClient.SqlDataAdapter sqlDataAdapter1;
    /// 
    /// Required designer variable.
    /// 
  ...
    private void InitializeComponent()
    {
      this.sqlConnection = new System.Data.SqlClient.SqlConnection();
      this.sqlSelectCommand1 = new System.Data.SqlClient.SqlCommand();
      this.sqlInsertCommand1 = new System.Data.SqlClient.SqlCommand();
      this.sqlUpdateCommand1 = new System.Data.SqlClient.SqlCommand();
      this.sqlDeleteCommand1 = new System.Data.SqlClient.SqlCommand();
      this.sqlDataAdapter1 = new System.Data.SqlClient.SqlDataAdapter();
      // 
      // sqlConnection
      // 
      this.sqlConnection.ConnectionString = "data source=.;initial catalog=Northwind;" +
        " integrated security=SSPI;persist security" +
        " info=False;workstation id=VORON;packet size=4096";
      // 
      // sqlDataAdapter1
      // 
      this.sqlDataAdapter1.DeleteCommand = this.sqlDeleteCommand1;
      this.sqlDataAdapter1.InsertCommand = this.sqlInsertCommand1;
      this.sqlDataAdapter1.SelectCommand = this.sqlSelectCommand1;
      this.sqlDataAdapter1.UpdateCommand = this.sqlUpdateCommand1;
...

Мы видим, что после создания мы получили экземпляры классов command и adapter. Но пока это только «каркас». Они пусты – давайте продолжим настройку adapter в окне дизайнера.

В окне дизайнера мы видим два объекта: сконфигурированный объект connection и не сконфигурированный adapter. Давайте посмотрим на Properties для adapter. Для этого вы можете нажать комбинацию клавиш Alt+Enter предварительно выбрав объект или выбрать окно Properties Window, опять таки предварительно выбрав нужный вам объект (в данном случае sqlDataAdapter1). Честно говоря, я бы не советовал вам активировать опцию Autohide у окна Properties - впоследствии вам не всегда удастся «вытащить» это окно с закладки.

Что мы видим в окне Properties:


Рис.8

А видим мы опции adapter -а. :) Сейчас нас больше всего интересует его объекты command. Начнем конфигурирование с самого обязательного объекта command – SelectCommand. Это единственный объект, который требует обязательного заполнения для работы adapter.

Для начала я предлагаю убирать цифру «1» в названиях переменных, созданных визардом. А то уж очень синтетические получатся названия :). Итак переименовываем объект slqDataAdapter1 в slqDataAdapter. То же проделаем для классов command

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

      // 
      // sqlSelectCommand
      // 
      this.sqlSelectCommand.Connection = this.sqlConnection;

Далее переходим к свойству CommandText для конфигурирования доступа к таблице БД. В данной случае мы можем написать обыкновенный запрос в виде строки T-Sql. Но у нас под рукой оказывается весьма полезный иногда инструментарий Sql Query Builder, вызываемый по нажатию кнопки данного свойства в окне свойств. Давайте посмотрим на него повнимательней:

Данный "построитель запросов" позволяет создавать запросы подобно Access-овским визардам. Воспользуемся им. Выберем табличку Customers из БД. И выберем все поля для запроса. Нажмем "OK" и опять посмотрим на изменения произведенные в коде нашего проекта:

// 
// sqlSelectCommand
// 
this.sqlSelectCommand.CommandText = "SELECT Customers.* FROM Customers";
this.sqlSelectCommand.Connection = this.sqlConnection;

Пока все получается достаточно красиво и аккуратно. В принципе этого достаточно, но я предлагаю сконфигурировать еще и объект sqlUpdateCommand. Для чего? В дальнейшем я хочу показать как ведется работа с «отцепленными» данными. Но пока не будем отвлекаться и продолжим конфигурирование аналогично sqlSelectCommand. Вы можете написать строку «UPDATE Customers SET CompanyName = @ComapnyName WHERE (CustomerID = @origCustomerID)» прямо в строку свойства и потом вызвать query builder для просмотра того что получилось. А должно было получится следующее:

Нажимаем Ok и переходим к просмотру кода. Но что мы видим?! Визард настолько умен что даже предлагает добавить нам остальные колонки для Update, переживая о том что часть информации может быть утеряна. Это приятно и неожиданно. Но мы сознательно пойдем на это. Для того чтобы реально убедиться, что есть возможность работать разными способами с загрузкой и сохранением. Итак, все таки давайте взглянем на изменения кода:

// 
// sqlUpdateCommand
// 
this.sqlUpdateCommand.CommandText = "UPDATE Customers SET CompanyName = @ComapnyName" +
    " WHERE (CustomerID = @origCustomerID)";
this.sqlUpdateCommand.Connection = this.sqlConnection;
this.sqlUpdateCommand.Parameters.Add(new System.Data.SqlClient.SqlParameter("@ComapnyName", 
    System.Data.SqlDbType.NVarChar, 40, "CompanyName"));
this.sqlUpdateCommand.Parameters.Add(new System.Data.SqlClient.SqlParameter("@origCustomerID", 
    System.Data.SqlDbType.NVarChar, 5, System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), 
    ((System.Byte)(0)), "CustomerID", System.Data.DataRowVersion.Original, null));

Вот теперь стало поинтересней. Кроме свойств самого sqlUpdateCommand мы также сгенерировали коллекцию параметров, которые участвуют в нашем запросе. Мало того, визард даже указал направление «условного» параметра @origCustomerID. System.Data.ParameterDirection.Input – это как раз и описывает направление параметра. А также его значение: System.Data.DataRowVersion.Original. Т.е. задавая данный параметр для SqlParameter, мы указываем, что хотим передать ему неизмененное – оригинальное значение поля CustomerID для данной записи.

Описание каждого параметра для SqlParameter я оставлю для МСДН, а на текущий момент все, что нам нужно знать для конфигурирования SqlParameter я описал. Да, может быть еще стоит упомянуть о System.Data.SqlDbType.NVarChar – SqlDBType описывает типы, которые присутствуют в БД. Для каждого типа connection присутствует своя коллекция, например для OleDb (OleDbType) и т.д.

Теперь вы можете визуально конфигурировать параметры для каждого command. В нашем случае для sqlUpdateCommand свойство Parameters выглядит следующим образом.

Теперь преодолеем следующий шаг по конфигурированию доступа к БД. Это генерация датасета (DataSet).

Что такое датасет в отсоединенной модели данных, такой как Ado.Net? Можно сравнить ее с горячей копией фрагмента БД. Датасет может хранить в себе все связи, которые присутствуют в основной БД. Мало того он не просто хранит, а активно их проверяет! Вообще модель с отсоединенными данными требует, ну как бы это сказать, поворота в мышлении во взглядах на работу с данными. Я лично считаю предложенную модель революцией в доступе к данным. У меня есть возможность на практике проверять новую технологию на большой БД (около 42 Гб) с большим количеством подключений и достаточно напряженным ритмом транзакций. До 500 документов в течении 5 минут. И я вам скажу следующее, что на старой технологии (Клиент Access/Server MS SQL) железо в виде четырехпроцессорного Xeon с 2 Гб оперативной памяти все-таки чувствует себя ...хм... скажем так: неуверенно, когда наши аналитики начинают проводить анализ ну скажем за период в месяц. Точнее серверу то все равно, он просто делает свою работу, но вот остальные пользователи делать больше ничего не могут, потому что старая технология предполагает постоянный коннект и что самое печальное постоянный «рефреш» данных. Но новая технология кардинально меняет подход. Пользователь подключается к БД, получает свой, нужный ему, объем данных с сервера и отключается. И полностью прекращает работу с данными сервера. Он получает локальную БД! Вы скажете, а как же так!? А если пользователю нужно получить объем данных превышающий размер кеша памяти?! Да действительно – когда данные начнут кешироватся на диск будет не очень приятно. Но я могу вам сказать, что если вы решили получить на клиента такой большой объем данных одновременно то это не очень правильно спланированная система. Почему? Да потому что пользователь все равно не сможет работать с таким большим объемом данных одновременно. Ему нужно будет делать выборки, а если он будет делать выборки то лучше это все-таки передать под управление серверу, и передавать только конечный набор.

Но мы отвлеклись от основного процесса создания проекта. Говорить о преимуществах Ado.Net вообще и работы с датасетом в частности можно довольно долго. Давайте вернемся к созданию нашего проекта.

Итак, наша задача стоит в том, чтобы сгенерировать датасет для нашего проекта.

В окне дизайнера мы видим экземпляр объекта sqlDataAdapter, нажмите на нем правой клавишей мышки:

Вы должны будете увидеть контекстное меню, в котором есть несколько интересных пунктов. Configure Data Adapter позволит сконфигурировать экземпляры классов command уже после того как объект будет создан. Preview Data позволит вам посмотреть данные в том виде, в котором у вас сконфигурирован Select для данного адаптера. Там же вы можете задавать тестовые значения для параметров (если они у вас есть в запросе) и нажав кнопку Fill DataSet вы можете увидеть, как будет происходить выборка данных. Эта опция действительно полезна, потому что она визуально отображает те данные, которые вы получите в результате выполнения команды Fill у adapter -а. Теперь перейдем к пункту Generate DataSet, который нужен нам для генерации датасет. Нажатие мышкой вызовет следующую реакцию системы: Вам будет предложено выбрать существующий датасет, если он уже присутствует в системе или предложит создать новый. Так как у нас пока нет датасета, мы будем создавать новый экземпляр:

Назовем его myDataSet. Птичка на checkbox даст возможность видеть в дальнейшем созданный датасет в окне дизайнера. Тут же мы видим информацию о том, какая таблица будет добавленна в датасет. Как вы понимаете, из сказанного мной выше, в датасет есть возможность включать несколько таблиц одновременно.

Итак продолжаем наши манипуляции. После нажатия кнопки Ok мы видим, как в дизайнере отобразился экземпляр объекта DataSet – myDataSet1. Вызовем окно Properties и изменим предложенное имя на myDataSet. И давайте перейдем к коду для того, чтобы посмотреть какие изменения произвел наш дизайнер:

  ...
  public class Form1 : System.Windows.Forms.Form
  {
    ...
    private System.Data.SqlClient.SqlDataAdapter sqlDataAdapter;
    private DataWorking.myDataSet myDataSet;
    /// 
  ...
  ...
private void InitializeComponent()
  {
    ...
    this.sqlDataAdapter = new System.Data.SqlClient.SqlDataAdapter();
    this.myDataSet = new DataWorking.myDataSet();
    ((System.ComponentModel.ISupportInitialize)(this.myDataSet)).BeginInit();
  ...
  // 
  // myDataSet
  // 
  this.myDataSet.DataSetName = "myDataSet";
  this.myDataSet.Locale = new System.Globalization.CultureInfo("ru-RU");
  this.myDataSet.Namespace = "http://www.tempuri.org/myDataSet.xsd";
  ...

    // 
    // Form1
    // 
    this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
    this.ClientSize = new System.Drawing.Size(528, 309);
    this.Name = "Form1";
    this.Text = "Form1";
    ((System.ComponentModel.ISupportInitialize)(this.myDataSet)).EndInit();
  }
  #endregion
  ...

Вот что добавил визард: объект типа DataSet, инициализировав его некоторые свойства, и еще интересные строки с BeginInit и EndInit в конце. Для чего они нужны? Эти строки не дают возможности изменения данных датасета, для инициализации данных и запрещают работу с ним для других компонентов в течении блока Begin … End. В основном это делает только дизайнер – вы же эту операцию в «ручном» режиме будете использовать крайне редко. Но все-таки будете :).

Теперь рассмотрим свойства установленные дизайнером датасету по умолчанию.

Свойство namespace говорит нам о том, что оно используется в случае, когда нам необходимо сохранить или прочитать документ из XML формата используя функции встроенные для датасета методы ReadXml, WriteXml, ReadXmlShema или WriteXmlShema.

Свойство Locale описывает локаль для данного датасета и берется она дизайнером из локальных установок компьютера. Необходимость ее объясняется сортировкой и некоторыми другими спецификами работы с данными датасета.

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

В данном случае идеально подходит компонент DataGrid из стандартной поставки VS .Net. Давайте создадим этот объект.

Я предлагаю сделать это исключительно «руками», для того чтобы попрактиковаться в работе с кодом.

Итак, давайте из окна дизайнера перейдем в окно кода.

Во первых хочу сразу обратить ваше внимание на то, очень не рекомендуется писать свой код в стандартный метод InitializeComponent() класса. Не зря он заключен в служебные теги #region - #endregion чтобы по умолчанию содержание данного метода было скрыто от программиста. Проблема в том, что при любом изменении в дизайнере этот метод полностью переписывается системой и весь код, внесенный Вами, будет утерян. Поэтому мы создадим свой метод инициализации компонентов и назовем его InitComponent():

     #region Personal init componets
    /// 
    /// Personal init componets
    /// 
    private void InitComponent() 
    {
    }
    #endregion Personal init componets

Вставим это код в наш проект, а так же добавим в конструктор следующий код:

    public Form1()
    {
      //
      // Required for Windows Form Designer support
      //
      InitializeComponent();
      InitComponent();
    }

Теперь мы получили свой «конструктор» компонентов. Давайте создадим и инициализируем объект DataGrid.

При наборе пользуйтесь системой IntelliSence от Microsoft она заметно облегчит вам жизнь.

А также пользуйтесь комментариями самодокументирования, впоследствии вы это оцените - поверьте мне. Начинаются они вводом трех символов «/». Я возможно в дальнейших статьях опишу, все теги документирования, но в данной ситуации на этом не хочу заострять внимание. Скажу лишь, что в дальнейшем вы можете видеть эти коментарии в IntelliSence системе:

Согласитесь что это очень важно в разработке больших проектов с большим количеством разработчиков.

Для инициализации и создания объекта DataGrid вы должны вставить следующий код:

  ...
public class Form1 : System.Windows.Forms.Form
  {
    ...
    private DataWorking.myDataSet myDataSet;
    // User define variable
    /// 
    /// Основной грид формы
    /// 
    private DataGrid myDataGrid;
    ...
    #region Personal init componets
    /// 
    /// Personal init componets
    /// 
    private void InitComponent() 
    {
      myDataGrid = new DataGrid();

      myDataGrid.DataSource = myDataSet;
      myDataGrid.Location = new System.Drawing.Point(0, 8);
      myDataGrid.Size = new System.Drawing.Size(536, 272);

      this.Controls.AddRange(new System.Windows.Forms.Control[] {
                                      this.myDataGrid});

    }
    #endregion Personal init componets

В свойствах нашего грида мы указали данные, и размер на экране. Свойство DataSource в принципе, может поддерживать любые коллекции, в которых реализован интерфейс IList.

Теперь мы можем откомпилировать наш проект и после запуска получим следующую картинку:

Так как DataGrid может поддерживать весь DataSet, а он, как известно, может содержать в себе несколько таблиц, мы видим их иерархию. И нажимая на интересующую нас таблицу попадаем внутрь ее.

Но почему же у нас не загружены данные?

А потому что мы забыли вставить вызов метода адаптера Fill. Это, кстати, самая первая и классическая ошибка новичков, она попадает в FAQ по работе с Ado.Net первой (а для Asp.Net, кстати, второй классической ошибкой после невызова метода Fill adapter-а идет DataBinding данных :) )

Давайте, исправим нашу ошибку.

…
  private void InitComponent() 
  {
    myDataGrid = new DataGrid();

    myDataGrid.DataSource = myDataSet;
    myDataGrid.Location = new System.Drawing.Point(0, 8);
    myDataGrid.Size = new System.Drawing.Size(536, 272);

    this.Controls.AddRange(new System.Windows.Forms.Control[] {
      this.myDataGrid});

    sqlDataAdapter.Fill(myDataSet);

Итак – компиляция. Вот что мы должны получить в результате:

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

Изменения! Вот мы и подошли к теме изменения. Очень интересно посмотреть на то, как мы будем обращаться с данными. Для этого я предлагаю запустить SQL Profiler чтобы увидеть всю картину работы с сервером. На скриншоте видно что какие бы я изменения не дела в гриде произошло всего лишь одно единственное событие загрузки данных через команду Fill DataAdapter-а.

Но нам бы очень хотелось сохранить наши изменения проделанные нами в данных. Как нам это сделать!? Нам нужно вызвать команду Update у того же Датаадаптера.

Давайте внесем изменения в код.

  /// 
  /// Clean up any resources being used.
  /// 
  protected override void Dispose( bool disposing )
  {
    if( disposing )
    {
      if (components != null) 
      {
        components.Dispose();
      }
    }
    base.Dispose( disposing );
    
    sqlDataAdapter.Update(myDataSet);
  }

Скомпилируем и теперь посмотрим на работу нашего грида. Внесем небольшие изменения в данные:

(Небольшие для того чтобы в Profiler мы могли их увидеть и разобраться)

И мы видим что сервер выполнил всего лишь две команды:

exec sp_executesql N'SELECT Customers.* FROM Customers'
go
exec sp_reset_connection
go
exec sp_executesql N'UPDATE Customers SET CompanyName = @ComapnyName WHERE (CustomerID = @origCustomerID)', N'@ComapnyName nvarchar(40),@origCustomerID nvarchar(5)', @ComapnyName = N'Alfreds Futterkiste 1', @origCustomerID = N'ALFKI'
go

Он изменил те данные, которые мы подменили. Но поле ComapnyName было учтено в UpdateCommand adapter-а. А что будет если мы изменим, например поле ContactName? И вот Profiler выдает результат:

exec sp_executesql N'UPDATE Customers SET CompanyName = @ComapnyName WHERE (CustomerID = @origCustomerID)', N'@ComapnyName nvarchar(40),@origCustomerID nvarchar(5)', @ComapnyName = N'Alfreds Futterkiste 1', @origCustomerID = N'ALFKI'

Вот оно – нет сохранения поля! Гибкость сохранения и загрузки данных. Оно может стать вашим совершенным оружием, но и обернуться против вас врагом!!

Стоит так же упомянуть об альтернативном способе создания экземпляров SqlCommand – автоматической генерации с помощью класса SqlCommandBuilder, так не любимой в среде программистов. Если у вас нет сложного механизма для загрузки и сохранения данных, вы можете использовать SqlCommandBuilder для автоматического построения Delete, Insert и Update экземпляров SqlCommand. Внесем очередные изменения в код:

Для начала закомментируйте инициализацию обьекта UpdateCommand:

...
  // 
  // sqlSelectCommand
  // 
  this.sqlSelectCommand.CommandText = "SELECT Customers.* FROM Customers";
  this.sqlSelectCommand.Connection = this.sqlConnection;
  /*
  // 
  // sqlUpdateCommand
  // 
  this.sqlUpdateCommand.CommandText = "UPDATE Customers SET CompanyName = @ComapnyName 
    WHERE (CustomerID = @origCustomer" +"ID)";
  this.sqlUpdateCommand.Connection = this.sqlConnection;
  this.sqlUpdateCommand.Parameters.Add(new System.Data.SqlClient.SqlParameter("@ComapnyName", 
    System.Data.SqlDbType.NVarChar, 40, "CompanyName"));
  this.sqlUpdateCommand.Parameters.Add(new System.Data.SqlClient.SqlParameter("@origCustomerID", 
    System.Data.SqlDbType.NVarChar, 5, System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), 
    ((System.Byte)(0)), "CustomerID", System.Data.DataRowVersion.Original, null));
      */
...
    private void InitializeComponent()
    {
...
      */
      // 
      // sqlDataAdapter
      // 
//      this.sqlDataAdapter.DeleteCommand = this.sqlDeleteCommand;
//      this.sqlDataAdapter.InsertCommand = this.sqlInsertCommand;
      this.sqlDataAdapter.SelectCommand = this.sqlSelectCommand;
//      this.sqlDataAdapter.UpdateCommand = this.sqlUpdateCommand;
      // 

...

И добавим следующий код:

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data.SqlClient;

namespace DataWorking

...

    private DataGrid myDataGrid;
    /// 
    /// Объект для автоматического построения комманд Insert,
    /// Update и Delete 
    /// 
    private SqlCommandBuilder sqlBuild;
    /// 
    /// Required designer variable.
    /// 
    private System.ComponentModel.Container components = null;
...
  private void InitComponent() 
  {
    sqlBuild = new SqlCommandBuilder(sqlDataAdapter);
    myDataGrid = new DataGrid();
    myDataGrid.DataSource = myDataSet;

...

А теперь проверим результат. Для этого сделаем изменения в нескольких полях грида:

Вот результат трассировки при помощи профайлера:

exec sp_executesql N'SELECT Customers.* FROM Customers'
go
exec sp_reset_connection
go
exec sp_executesql N' SET FMTONLY OFF; SET NO_BROWSETABLE ON; SET FMTONLY ON;SELECT Customers.* FROM Customers'
go
exec sp_executesql N'UPDATE Customers SET CompanyName = @p1 , ContactName = @p2 WHERE ( (CustomerID = @p3) AND ((CompanyName IS NULL AND @p4 IS NULL) OR (CompanyName = @p5)) AND ((ContactName IS NULL AND @p6 IS NULL) OR (ContactName = @p7)) AND ((ContactTitle IS NULL AND @p8 IS NULL) OR (ContactTitle = @p9)) AND ((Address IS NULL AND @p10 IS NULL) OR (Address = @p11)) AND ((City IS NULL AND @p12 IS NULL) OR (City = @p13)) AND ((Region IS NULL AND @p14 IS NULL) OR (Region = @p15)) AND ((PostalCode IS NULL AND @p16 IS NULL) OR (PostalCode = @p17)) AND ((Country IS NULL AND @p18 IS NULL) OR (Country = @p19)) AND ((Phone IS NULL AND @p20 IS NULL) OR (Phone = @p21)) AND ((Fax IS NULL AND @p22 IS NULL) OR (Fax = @p23)) )', N'@p1 nvarchar(4000),@p2 nvarchar(4000),@p3 nchar(4000),@p4 nvarchar(4000),@p5 nvarchar(4000),@p6 nvarchar(4000),@p7 nvarchar(4000),@p8 nvarchar(4000),@p9 nvarchar(4000),@p10 nvarchar(4000),@p11 nvarchar(4000),@p12 nvarchar(4000),@p13 nvarchar(4000),@p14 nvarchar(4000),@p15 nvarchar(4000),@p16 nvarchar(4000),@p17 nvarchar(4000),@p18 nvarchar(4000),@p19 nvarchar(4000),@p20 nvarchar(4000),@p21 nvarchar(4000),@p22 nvarchar(4000),@p23 nvarchar(4000)', @p1 = N'Alfreds Futterkiste', @p2 = N'Maria Anders 1', @p3 = N'ALFKI', @p4 = N'Alfreds Futterkiste 1', @p5 = N'Alfreds Futterkiste 1', @p6 = N'Maria Anders', @p7 = N'Maria Anders', @p8 = N'Sales Representative', @p9 = N'Sales Representative', @p10 = N'Obere Str. 57', @p11 = N'Obere Str. 57', @p12 = N'Berlin', @p13 = N'Berlin', @p14 = NULL, @p15 = NULL, @p16 = N'12209', @p17 = N'12209', @p18 = N'Germany', @p19 = N'Germany', @p20 = N'030-0074321', @p21 = N'030-0074321', @p22 = N'030-0076545', @p23 = N'030-0076545'
go

Теперь вы сами можете оценить, почему не очень рекомендуют использовать SqlCommandBuilder для автоматического построителя Command. Ответ виден из трассировки: очень громоздкие операции.. Но надежные, как все от Microsoft ! :)

Итак, для сегодняшнего проекта достаточно. Я думаю, я дал вам сегодня возможность с чего либо начать при работе с данными.

В дальнейшем я буду уходить все глубже и глубже в объекты доступа к данным. Мы пройдем по типизированным датасет – механизмом, очень используемым на практике, TableMapping, SqlException и прочим очень интересным вещам.

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

С уважением, Сергей Воронцов.


Текст примеров данной статьи можно выкачать здесь


Вернуться к статьям в категории ADO.NET
Вернуться к списку категорий
Перейти к форуму ADO.NET
 
Наш Киев

Apartments for Rent

Rambler's Top100
Рейтинг@Mail.ru
Идея: Dimon aka Manowar Программирование: Dimon aka Manowar Дизайн: Dan Lebedev
Хостинг от компании Parking.ru
Карта сайта