Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых
[Назад] [Содержание] [Дальше]

Визуальное проектирование приложений C#


А.В. Фролов, Г.В. Фролов

Глава 8. Приложения с базами данных.. 3

Методы доступа к СУБД.. 3

Вызов программных интерфейсов. 3

Прямой вызов программного интерфейса СУБД.. 3

Использование программного интерфейса ODBC.. 3

Объектные интерфейсы СУБД.. 4

Интерфейс Remote Data Object 4

Интерфейс OLE DB.. 4

Интерфейс ActiveX Data Objects. 4

Метод доступа ADO .NET. 5

Многоуровневые системы.. 5

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

Распределенная обработка данных и XML. 6

Провайдеры данных для управляемого кода. 6

Работа с объектами DataSet.. 6

Приложение DataSetApp. 6

Добавление элемента управления DataSet 6

Настройка свойств элемента управления DataSet 8

Создание таблиц. 9

Создание столбцов. 11

Первичный ключ. 17

Элемент управления DataGrid.. 18

Добавление объекта DataGrid в проект приложения DataSetApp. 18

Настройка внешнего вида окна элемента управления DataGrid. 22

Рамка вокруг окна. 22

Заголовок окна. 22

Форматирование содержимого окна. 22

Сортировка. 23

Запрет редактирования. 23

Выбор цвета. 23

Набор стилей оформления TableStyles. 23

Программный код. 26

Создание элемента управления DataGrid. 26

Настройка свойств. 26

Добавление таблицы стилей. 26

Работа с DataSet при помощи методов и свойств.. 28

Добавление новой строки. 28

Удаление строки. 29

Приложение PhoneBookApp. 30

Создание проекта приложения. 31

Элементы управления для работы со списком имен и фамилий. 31

Элементы управления для работы со списком телефонов. 32

Создание таблиц базы данных. 33

Таблица Contacts. 33

Таблица Phones. 34

Связывание таблиц Contacts и Phones. 36

Проектирование кода для управления базой данных. 38

Добавление нового контакта. 38

Выбор записи в списке имен и фамилий. 39

Редактирование записей таблицы Contacts. 40

Удаление записей таблицы Contacts. 41

Добавление номера телефона. 42

Удаление номера телефона. 42

Изменение номера телефона. 43

Обновление списка контактов. 43

Обновление списка телефонов. 44

Фильтр для метода Select.. 45

Перегруженные методы Select 45

Краткое описание синтаксиса строки фильтра.. 45

Значения. 46

Операторы.. 46

Функции. 46

 

Глава 8. Приложения с базами данных

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

В [1] мы рассматривали различные способы интеграции автономных и Web-приложений с системой управления базами данных (СУБД) Microsoft SQL Server. Эта СУБД очень популярна на платформе ОС Microsoft Windows, особенно при создании промышленных информационно-справочных систем и различного рода систем автоматизации.

Что же касается приложений C# и платформы Microsoft .NET, то для них компания Microsoft разработала новейший метод доступа ActiveX Data Objects .NET (ADO .NET), наилучшим образом соответствующий нуждам современных приложений. Однако прежде чем приступить к рассказу об ADO .NET, мы сделаем краткий обзор других методов доступа, применяемых в работе с СУБД Microsoft SQL Server, а также с СУБД других типов.

Методы доступа к СУБД

На сегодняшний день разработаны сотни (если не больше) СУБД различных типов, способные работать на компьютерах любой мощности — от майнфреймов до карманных компьютеров и компьютеров, встроенных в мобильные телефоны и другие устройства. К счастью, в этих СУБД и в операционной системе ОС Microsoft Windows предусмотрено относительно небольшое количество универсальных методов доступа, с помощью которых приложения могут быть интегрированы с базами данных.

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

Вызов программных интерфейсов

Здесь мы рассмотрим интерфейсы двух типов. Это программные интерфейсы, предназначенные для прямого вызова СУБД, а также универсальный интерфейс ODBC.

Прямой вызов программного интерфейса СУБД

Как правило, СУБД любого типа, предназначенная для работы на платформе Microsoft Windows, предоставляет в распоряжение программиста интерфейс API, с помощью которого программа может выполнять все необходимые операции с базами данных. Физически этот интерфейс обычно реализован с помощью библиотек динамической компоновки DLL, экспортирующих функции доступа к СУБД.

В частности, Microsoft SQL Server предоставляет разработчикам приложений программный интерфейс  DB Library —естественный интерфейс данной СУБД, реализованный как набор функций.

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

 Кроме того, технология прямого вызова программного интерфейса СУБД недоступна для разработчиков Web-приложений, использующих так называемые серверные сценарии JavaScript и VB Script (читайте об этом подробнее в [1]).

Использование программного интерфейса ODBC

Программный интерфейс ODBC, как и только что упомянутые интерфейсы прямого вызова СУБД также выполнен в виде набора функций. Это ограничивает его применение в Web-приложениях [1].

Однако интерфейс ODBC, созданный специально для доступа к реляционным базам данных, универсален. Это единый интерфейс, позволяющий приложениям работать с СУБД всех типов, для которых имеется так называемый драйвер ODBC.

Используя ODBC, программист может не заботиться о деталях внутреннего устройства и особенностях естественного интерфейса различных СУБД, т.к. драйвер ODBC полностью скрывает от него эти детали. В результате программы, обращающиеся к базам данных, становятся менее зависимыми от этих баз данных. К сожалению, отличия в реализации драйверов ODBC различных СУБД не всегда позволяют добиться полной независимости программ от типа СУБД.

Объектные интерфейсы СУБД

По мере развития ОС Microsoft Windows и СУБД, на смену программным интерфейсам пришли объектные интерфейсы, основанные на использовании модели компонентных объектов Component Object Model (COM).

Объекты COM можно представить себе как набор интерфейсов, через которые можно получить доступ к свойствам и методам объекта. Если Вы знакомы с классами, интерфейсами и свойствами языка C#, то эти понятия Вам тоже знакомы. Хотя с появлением платформы .NET технология COM становится устаревшей (или, как говорят, унаследованной), до сих пор она интенсивно применяется как в самой ОС Microsoft Windows, так и в приложениях, создаваемых для этой ОС. При необходимости дополнительную информацию о создании объектов COM и ActiveX (также созданных на базе COM) Вы сможете найти в [1], [5] и [6].

В этом разделе мы рассмотрим объектные интерфейсы Remote Data Object (RDO), OLE DB и ActiveX Data Objects (ADO).

Интерфейс Remote Data Object

Объектный интерфейс RDO был разработан для упрощения доступа к серверу СУБД Microsoft SQL Server из приложений Microsoft Visual Basic и Visual Basic for Applications. Он реализует все возможности интерфейса ODBC, однако для его использования приложению не требуется напрямую вызывать какие-либо программные интерфейсы СУБД или ODBC.

Интерфейс OLE DB

Объектный интерфейс OLE DB представляет собой открытый стандарт, предназначенный для универсального доступа приложений к базам данных. В отличие от интерфейса ODBC и RDO, интерфейс OLE DB позволяет приложениям обращаться не только к реляционным БД, но и к нереляционным, таким, например, как серверы почты, базы данных для мэйнфреймов с методами доступа IMS, VSAM и т. д.

Интерфейс OLE DB состоит из трех компонентов: провайдера (provider), потребителя (consumer) и служебного компонента, выполняющего обработку и передачу данных.

В роли потребителя могут выступать приложения. Задача провайдера OLE DB — реализация интерфейса OLE DB. В составе OLE DB поставляются провайдеры для интерфейсов ODBC, для текстовых файлов и некоторые другие. Пользуясь провайдером ODBC, потребители интерфейса OLE DB могут получить доступ к базам данных через драйвер ODBC.

Интерфейс ActiveX Data Objects

Упомянутый выше объектный интерфейс OLE DB не реализует механизм автоматизации, в результате чего этот метод не подходит для создания Web-приложений, основанных на серверных сценариях JavaScript и VB Script [1].

Объектный интерфейс ActiveX Data Objects (ADO) построен на основе интерфейса OLE DB. При этом интерфейс OLE DB обеспечивает универсальный доступ к данным с помощью провайдеров, таких как Microsoft OLE DB Provider для ODBC (MSDASQL) или Microsoft OLE DB Provider для SQL Server (SQLOLEDB).

Благодаря тому, что объекты ADO реализуют средства автоматизации, интерфейс ADO доступен из приложений, составленных с применением целого спектра инструментальных средств, таких, как серверный сценарии ASP, C++, Visual Basic, Visual Basic for Applications, Java и т. д.

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

Вот типичный сценарий работы приложения с базой данных посредством интерфейса ADO:

·         установка соединения;

·         подготовка команды и параметров;

·         выполнение команды;

·         обработка результатов выполнения команды;

·         закрытие соединения;

·         обработка ошибок

Прежде чем обращаться к базе данных, приложение должно установить соединение с сервером базы данных. При этом требуется указать имя источника данных Data Source Name (DSN) или информацию об источнике данных, такую как имя драйвера, имя сервера, пароль и т.д.

После установки соединения приложение должно подготовить объект-команду, записав в его свойства команды, необходимые для доступа к данным (например, строки языка SQL). Приложение может передать вместе с командой параметры. Входные параметры позволяют передавать информацию в хранимые процедуры СУБД Microsoft SQL Server, а выходные — принимать информацию из хранимой процедуры.

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

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

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

Метод доступа ADO .NET

Рассмотренные выше методы доступа с программными и объектными интерфейсами больше всего подходят для создания так называемых клиент-серверных приложений. Такие приложения обычно открывают соединение с базой данных в начале своей работы, а закрывают — при ее завершении. Если пользователей много, то каждый из них будет во время своей работы держать как минимум одно соединение с сервером СУБД (даже во время обеденного перерыва, если клиентская программа запускается на целый день). Это отнимает немало ресурсов сервера и приводит к необходимости приобретения большого количества серверных лицензий.

Многоуровневые системы

С появлением Web-приложений, интегрированных с базами данных, получили развитие так называемые многоуровневые системы. В этих системах клиент (в роли которого выступает обычный браузер, такой, например, как Microsoft Internet Explorer) обращается к СУБД не напрямую, а через Web-сервер.

Такое обращение начинается с того, что бразуер направляет запрос к Web-серверу (например, для выборки данных из базы данных или обновления базы данных). Далее Web-сервер действует следующим образом:

·         открывает соединение с СУБД;

·         выполняет запрос, обращаясь к базе данных;

·         закрывает соединение с базой данных;

·         отправляет результат запроса в браузер

Браузер получает результат обработки запроса в виде текстового документа HTML и отображает его в своем окне.

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

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

Метод доступа ADO .NET, доступный приложениям на платформе Microsoft .NET, позволяет создавать разновидность многоуровневых систем — так называемые рассоединенные (disconnected) системы.

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

Данные, извлеченные из сервера СУБД методом ADO .NET, сохраняются в объекте класса DataSet. Этот объект может хранить в себе одновременно несколько таблиц данных, в том числе связанных между собой (related tables), а также ограничения (constraints). В частности, можно переписать в созданный локально объект DataSet содержимое всей базы данных, расположенной на сервере, если в этом возникнет необходимость.

Вот возможная схема взаимодействия клиента с сервером в рассоединенной системе, реализованной с использованием метода доступа ADO .NET:

·         открытие соединения с сервером СУБД;

·         отправка запроса к базе данных;

·         закрытие соединения;

·         обработка данных, полученных  в виде объекта класса DataSet;

·         открытие соединения с сервером СУБД;

·         обновление базы данных с использованием содержимого объекта класса DataSet;

·         закрытие соединения

Распределенная обработка данных и XML

Если нужно создать информационную систему с распределенной обработкой данных, встает вопрос организации взаимодействия между серверами и клиентами такой системы. Метод доступа ADO позволяет организовать такую обработку средствами COM, однако этот способ имеет определенные недостатки. Эти недостатки проявляются в тех случаях, когда нужно объединить узлы системы при помощи каналов Интернета.

Дело в том, что корпоративные интрасети, подключенные к Интернету, обычно защищаются брандмауэром (firewall), открывающим доступ только для определенных портов TCP/IP и для определенных протоколов передачи данных. Обычно открывается только порт 80, предназначенный для работы с Web-серверами посредством протокола HTTP, а также порты протоколов SMTP, POP3 и IMAP, с помощью которых осуществляется передача электронной почты. Эти ограничения обычно несовместимы с системами удаленной обработки, реализованными с использованием модели COM.

Что же касается ADO .NET, то этот метод доступа допускает представление данных в формате XML (хорошее описание XML Вы найдете в [9]). При этом данные могут передаваться с использованием протокола HTTP, что позволяет объединять информационные системы каналами Интернета, даже если эти системы защищены брандмауэрами.

Провайдеры данных для управляемого кода

Программный компонент, называемый провайдером данных (data provider) выступает в качестве моста между приложением и источником данных. В его задачу входит извлечение данных из источника, а также обновление источника данных.

Для приложений, содержащих управляемый код и предназначенных для платформы Microsoft .NET, компания Microsoft разработала три провайдера данных. Это SQL Server .NET Data Provider, OLE DB .NET Data Provider и ODBC .NET Data Provider. Первые два из них входят в состав среды исполнения Microsoft .NET Framework, а третий можно загрузить с Web-сайта компании Microsoft по адресу http://msdn.microsoft.com/downloads.

Если Ваше приложение C# должно работать с сервером Microsoft SQL Server версии 7.0 или более новой версии, максимальная производительность будет достигнута при использовании провайдера данных SQL Server .NET Data Provider. К сожалению, специализированных провайдеров для прямого доступа из управляемого кода к СУБД других типов пока не существует.

Что же касается провайдера OLE DB .NET Data Provider, то он пригодится Вам для доступа к базам данных Microsoft Access и другим СУБД, для которых реализованы провайдеры OLE DB.

В том случае, когда единственно возможный способ интеграции приложения и СУБД заключается в использовании драйвера ODBC, можно воспользоваться провайдером ODBC .NET Data Provider.

Работа с объектами DataSet

Как мы уже говорили, объекты DataSet могут содержать в себе отдельные и связанные реляционные таблицы и ограничения. Этот объект, наряду с другими объектами нужными для работы с базами данных, определен в пространстве имен System.Data.

Заметим, что Вы можете создавать приложения с объектами DataSet, которые не взаимодействуют ни с какими серверами баз данных. Более того, изучение ADO .NET мы начнем именно с таких приложений, дополнив их в дальнейшем средствами интеграции с сервером Microsoft SQL Server.

Приложение DataSetApp

Мы подготовили приложение DataSetApp специально для изучения элемента управления DataSet, а также еще одного элемента управления с названием DataGrid. Последний чрезвычайно удобен для отображения содержимого таблиц баз данных в формах приложений с графическим пользовательским интерфейсом, и потому представляет для нас особый интерес.

Объект класса DataSet позволяет создавать наборы реляционных таблиц в оперативной памяти. Эти наборы впоследствии могут быть использованы для инициализации или обновления баз данных, а также экспортированы в формате XML.

Добавление элемента управления DataSet

С помощью мастера проектов создайте приложение DataSetApp, как Вы это делали раньше. Далее откройте в инструментальной панели Toolbox вкладку Data (рис. 8-1).

Рис. 8-1. Вкладка Data инструментальной панели Toolbox

Перетащите из этой вкладки в окно дизайнера форм приложения значок элемента управления DataSet. На экране появится диалоговое окно Add Dataset, показанное на рис. 8-2.

Рис. 8-2. Выбор типа объекта DataSet

В этом окне необходимо выбрать типизированный или не типизированный набор данных. Типизированный набор данных создается на базе ранее определенного в проекте набора данных, имеющего схему XML Schema (файл с расширением .xsd). Для нашего проекта выберите не типизированный набор данных, отметив флажок Untyped dataset, а затем щелкните кнопку OK.

В результате в проект будет добавлен элемент управления класса DataSet с идентификатором dataSet1.

Рис. 8-3. В проект добавлен компонент DataSet

Если после выполнения этой процедуры посмотреть исходный текст нашего приложения, то в нем появится поле dataSet1 для хранения идентификатора компонента, а также код инициализации компонента, приведенный ниже:

 

private System.Data.DataSet dataSet1;

this.dataSet1 = new System.Data.DataSet();

((System.ComponentModel.ISupportInitialize)
  (this.dataSet1)).BeginInit();

//
// dataSet1
//
this.dataSet1.DataSetName = "NewDataSet";
this.dataSet1.Locale = new System.Globalization.CultureInfo("ru-RU");

Как видите, объект DataSet создается с помощью конструктора класса System.Data.DataSet, не имеющего параметров.

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

На данном этапе приложение устанавливает только два свойства — имя набора данных DataSetName и сведения о национальной культуре (локализации) Locale. Подробнее о национальных культурах читайте в [1].

Настройка свойств элемента управления DataSet

Для настройки свойств элемента управления DataSet выделите элемент dataSet1 мышью и воспользуйтесь окном редактирования свойств Properties, показанным на рис. 8-4.

Рис. 8-4. Свойства элемента управления DataSet

Рассмотрим наиболее важные свойства элемента управления DataSet.

Свойство DataSetName задает имя элемента управления. По умолчанию оно указано как NewDataSet, но Вы можете изменить его. Для нашего приложения, играющего роль заготовки простейшего органайзера, измените это имя на OrganizerDataSet.

Свойство Locale, как мы уже говорили, задает национальную культуру, для которой создается приложение. Выбор значения для этого свойства имеет критическое значение для правильного выполнения таких операций с базами данных, как, например, сортировка. К счастью, в списке значений для этого свойства присутствует большое количество национальных культур, в том числе и российская культура (рис. 8-5).

Рис. 8-5. Выбор значения для свойства Locale

Свойство Tables определяет набор таблиц, представленных данным элементом управления DataSet, а свойство Relations — отношения между этими таблицами (если они определены).

Установив значение свойства EnforceConstraints, равным False, можно отменить ограничения, заданные для таблиц набора данных DataSet.

Создание таблиц

Теперь нам нужно добавить в набор данных DataSet две таблицы. Первая таблица будет называться Contacts. Она предназначена для хранения имен и фамилий Ваших знакомых и партнеров. Вторая таблица с названием Phones будет хранить номера телефонов людей, чьи имена и фамилии хранятся в таблице Contacts.

Для создания таблиц отредактируйте свойство Tables при помощи редактора Tables Collection Editor. На рис. 8-6 мы показали окно этого редактора после создания таблиц Contacts и Phones.

Рис. 8-6. Редактор набора таблиц

Добавить новую таблицу можно при помощи кнопки Add, а удалить — при помощи кнопки Remove.

Каждая таблица имеет набор свойств, которые можно редактировать в правой части окна Tables Collection Editor. В частности, свойство Columns задает столбца таблицы, а свойство Constarints — ограничения.

В свойстве TableName таблицы контактов Contacts Вам необходимо задать название таблицы, как это показано на рис. 8-6. Измените также имя таблицы (в свойстве Name), заменив его именем contactsDataTable.

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

Рассмотрим программный код, создаваемый дизайнером форм при добавлении таблиц.

Идентификаторы таблиц хранятся в полях класса Form1 с именами contactsDataTable (таблица Contacts) и phonesDataTable (таблица Phones):

private System.Data.DataTable contactsDataTable;
private System.Data.DataTable phonesDataTable;

Как объекты наши две таблицы создаются при помощи конструкторов класса System.Data.DataTable:

this.contactsDataTable = new System.Data.DataTable();
this.phonesDataTable = new System.Data.DataTable();

Далее с помощью метода ISupportInitialize.BeginInit обеим таблицам передается сигнал о начале инициализации:

((System.ComponentModel.ISupportInitialize)
  (this.contactsDataTable)).BeginInit();

((System.ComponentModel.ISupportInitialize)
  (this.phonesDataTable)).BeginInit();

Созданные таблицы добавляются к элементу управления dataSet1 при помощи метода AddRange:

this.dataSet1.Tables.AddRange(
  new System.Data.DataTable[]
  {
     this.contactsDataTable,
     this.phonesDataTable
  }
);

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

Заметим, что этот фрагмент кода, точно также как и другие фрагменты кода, описанные в этом разделе, создаются автоматически мастером форм, поэтому Вам не придется писать его вручную. Однако изучение кода поможет Вам глубже разобраться в приемах работы с элементом управления DataSet.

Создание столбцов

Для добавления столбцов в таблицу, а также для редактирования свойств столбцов открывается редактор Columns Collection Editor. На рис. 8-7 мы показали его окно после добавления в таблицу Contacts строк id, Имя и Фамилия.

Рис. 8-7. Столбец id таблицы Contacts

Добавление столбцов в таблицу выполняется при помощи кнопки Add, а удаление — кнопкой Remove.

Добавив три столбца, отредактируйте их свойства в правой половине окна редактора Columns Collection Editor.

Первый столбец с именем id предназначен для хранения уникального идентификатора записей таблицы Contacts. Запишите это имя в свойства Caption и ColumnName. Первое из этих свойств задает заголовок столбца, используемый при визуальном отображении, а второй — имя столбца.

Так как столбец id хранит номера, то в свойстве DataType, определяющем тип данных столбца, необходимо выбрать тип System.Int32. Возможные значения свойства DataType показаны на рис. 8-8. Как видите, здесь присутствуют все типы данных, допустимые в программах C#.

Рис. 8-8. Возможные значения свойства DataType

Чтобы столбец id мог хранить уникальные идентификаторы строк, необходимо выполнить некоторую дополнительную настройку его свойств.

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

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

Если записать в свойство AutoIncrement константу True, при добавлении в таблицу новых строк текущее значение, хранящееся в столбце, будет автоматически увеличиваться на константу, записанную в свойстве AutoIncrementStep. Начальное значение для самой первой строки задается свойством AutoIncrementSeed.

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

И, наконец, для простоты составления программ мы заменили имя столбца dataColumn1 именем idDataColumn.

Свойство DefaultValue позволяет автоматически задавать произвольно заданные значения ячеек для добавляемых строк.

При помощи свойства Expression можно создавать так называемые агрегированные столбцы (aggregate column). Ячейки таких столбцов могут содержать значения, вычисленные на базе значений из других ячеек.

Если столбец предназначен для хранения текстовых строк, то при помощи свойства MaxLength Вы сможете ограничить максимальную длину этих строк в символах. При этом значение –1 используется для снятия данного ограничения.

На рис. 8-9 мы показали настройку свойств строки Имя таблицы Contacts.

Рис. 8-9. Столбец Имя таблицы Contacts

Обратите внимание, что в свойстве DataType мы указали тип данных System.String, т.к. этот столбец хранит текстовые строки.

Чтобы не допустить создания записей с пустым именем, мы установили значение свойства AllowDBNull равным False.

Заголовок столбца, а также имя столбца, задаваемые, соответственно, свойствами Caption и ColumnName, мы заменили строкой Имя. Идентификатор столбца указан как fnDataColumn.

Остальные свойства столбца Имя мы оставили без изменения. Аналогичные настройки были выполнены и для столбца Фамилия. Идентификатор этого столбца — lnDataColumn.

Теперь мы займемся созданием таблицы Phones, хранящей номера телефонов. Свойства этой таблицы показаны на рис. 8-10.

Рис. 8-10. Свойства таблицы Phones

Задайте имя таблицы Phones как phonesDataTable.

Далее Вам необходимо создать в таблице Phones поля id и Телефон. Первое из этих полей будет содержать уникальный идентификатор записей таблицы, а второй — номера телефонов.

На рис. 8-11 мы показали, как нужно настроить свойства столбца id таблицы Phones.

Рис. 8-11. Столбец id таблицы Phones

Здесь мы задали свойства столбца аналогично тому, как это было сделано для столбца id таблицы Contacts, рассмотренной выше.

Что же касается столбца Телефон, то настройка его свойств аналогична настройке свойств столбцов Имя и Фамилия таблицы Contacts (рис. 8-12).

Рис. 8-12. Строка Телефон таблицы Phones

Для хранения ссылок на столбцы наших таблиц дизайнер форм создает в классе Form1 необходимое количество полей класса System.Data.DataColumn:

private System.Data.DataColumn idDataColumn;
private System.Data.DataColumn fnDataColumn;
private System.Data.DataColumn lnDataColumn;

private System.Data.DataColumn idPhonesDataColumn;
private System.Data.DataColumn phonesDataColumn;

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

Например, в таблицах Contacts и Phones имеются столбцы id, предназначенные для хранения уникальных идентификаторов строк. Для того чтобы избежать конфликта имен, мы использовали разные идентификаторы столбцов. В первом случае столбец id имеет идентификатор idDataColumn, а во втором — idPhonesDataColumn.

Столбцы создаются при помощи конструктора класса System.Data.DataColumn, не имеющего параметров:

this.idDataColumn = new System.Data.DataColumn();
this.fnDataColumn = new System.Data.DataColumn();
this.lnDataColumn = new System.Data.DataColumn();

 

this.idPhonesDataColumn = new System.Data.DataColumn();
this.phonesDataColumn = new System.Data.DataColumn();

Рассмотрим теперь процедуру настройки свойств столбцов. Для столбцов таблицы Contacts эта настройка выполняется следующим образом:

//
// idDataColumn
//
this.idDataColumn.AllowDBNull = false;
this.idDataColumn.AutoIncrement = true;
this.idDataColumn.Caption = "id";
this.idDataColumn.ColumnName = "id";
this.idDataColumn.DataType = typeof(int);
this.idDataColumn.ReadOnly = true;
//
// fnDataColumn
//
this.fnDataColumn.AllowDBNull = false;
this.fnDataColumn.Caption = "Имя";
this.fnDataColumn.ColumnName = "Имя";
//
// lnDataColumn
//
this.lnDataColumn.AllowDBNull = false;
this.lnDataColumn.Caption = "Фамилия";
this.lnDataColumn.ColumnName = "Фамилия";

Как видите, программа задает значения соответствующих свойств при помощи оператора присваивания. Обратите внимание, что свойство DataType, определяющее тип данных, задается явным образом только для столбца id, в котором хранятся числовые значения. Для других столбцов используется тип данных по умолчанию — текстовые строки класса string.

Столбцы таблицы Phones инициализируются аналогичным образом:

//
// idPhonesDataColumn
//
this.idPhonesDataColumn.AllowDBNull = false;
this.idPhonesDataColumn.AutoIncrement = true;
this.idPhonesDataColumn.ColumnName = "id";
this.idPhonesDataColumn.DataType = typeof(int);
this.idPhonesDataColumn.ReadOnly = true;
//
// phonesDataColumn
//
this.phonesDataColumn.AllowDBNull = false;
this.phonesDataColumn.ColumnName = "Телефон";

Созданные столбцы добавляются в соответствующие таблицы при помощи метода AddRange:

this.contactsDataTable.Columns.AddRange(
  new System.Data.DataColumn[]
  {
     this.idDataColumn,
     this.fnDataColumn,
     this.lnDataColumn
  }
);

this.phonesDataTable.Columns.AddRange(
  new System.Data.DataColumn[]
  {
     this.idPhonesDataColumn,
     this.phonesDataColumn
  }
);

При добавлении столбцов в таблицу ссылки на эти столбцы сохраняются в свойстве Columns таблицы.

Так как столбцы id в обеих таблицах должны хранить уникальные значения, мы добавили ограничение System.Data.UniqueConstraint:

this.contactsDataTable.Constraints.AddRange(
  new System.Data.Constraint[]
  {
     new System.Data.UniqueConstraint("Constraint1",
       new string[]
       {
          "id"
       }, true)
  }
);

this.phonesDataTable.Constraints.AddRange(
  new System.Data.Constraint[]
  {
     new System.Data.UniqueConstraint("Constraint1",
       new string[]
       {
          "id"
       }, true)
  }
);

Набор ограничений, налагаемых на столбцы таблицы, хранятся в свойстве Constraints.

Первичный ключ

Как правило, в каждой таблице создается так называемый первичный ключ (primary key), однозначно идентифицирующий строки таблицы. В наших таблицах роль первичного ключа выполняет столбец id, предусмотренный в таблицах Contacts и Phones.

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

Выбор первичного ключа при редактировании свойства PrimaryKey осуществляется при помощи набора флажков, каждый из которых соответствует одному столбцу таблицы (рис. 8-13).

Рис. 8-13. Первичный ключ в таблице Contacts

Чтобы сделать столбец id первичным ключом, необходимо отметить флажок id.

На рис. 8-14 мы показали выбор первичного ключа для таблицы Phones.

Рис. 8-14. Первичный ключ в таблице Phones

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

Ниже мы привели фрагменты программного кода, задающего для таблиц Contacts и Phones первичные ключи:

this.contactsDataTable.PrimaryKey =
  new System.Data.DataColumn[]
  {
     this.idDataColumn
  };

this.phonesDataTable.PrimaryKey =
  new System.Data.DataColumn[]
  {
     this.idPhonesDataColumn
  };

Как видите, при этом в свойство PrimaryKey записывается ссылка на массив идентификаторов столбцов таблицы, составляющих первичный ключ.

Элемент управления DataGrid

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

Работать с элементом управления DataGrid достаточно просто. Если Вы подготовили набор таблиц в виде объекта DataSet, то для отображения содержимого этих таблиц достаточно подключить к этому набору объект DataGrid, выполнив необходимую настройку свойств. При этом элемент управления DataGrid автоматически обеспечит Ваше приложение возможностью просматривать и редактировать набор таблиц, созданных в рамках объекта DataSet. Образно говоря, элементы управления DataGrid и DataSet как бы созданы друг для друга.

Добавление объекта DataGrid в проект приложения DataSetApp

Созданное в предыдущем разделе приложение DataSetApp поможет нам изучить основные возможности элемента управления DataGrid.

Перетащите значок элемента управления DataGrid из вкладки Windows Forms инструментальной панели Toolbox в окно формы приложения DataSetApp. Далее Вам нужно будет настроить некоторые свойства элемента управления DataGrid, пользуясь для этого окном Properties системы Microsoft Visual Studio .NET (рис. 8-15).

Рис. 8-15. Настройка свойств элемента управления DataGrid

Прежде всего,  подключите элемент управления DataGrid к набору таблиц dataSet1, отредактировав для этого свойство DataSource (значение свойства DataMember пока задавать не надо).

Кроме этого, задайте значение свойства CaptionText, задающего текст заголовка таблицы, в виде строки «Контакты». Шрифт этого заголовка можно выбрать при помощи свойства CaptionFont.

На рис. 8-16 мы показали вид главного окна приложения DataSetApp после выполнения всех этих операций, а также после добавления полей и кнопки, с помощью которой можно будет добавлять новые строки в таблицу Contacts.

Рис. 8-16. Элемент управления DataGrid подключен к набору данных

Если щелкнуть мышью флажок, показанный на этом рисунке в виде квадратика со знаком плюс, то в окне появится список таблиц, созданных в наборе dataSet1. Этот список мы показали на рис. 8-17 в раскрытом виде.

Рис. 8-17. Раскрыт список таблиц

Как видите, элементы этого списка отображены в виде ссылок, напоминающих ссылки, располагаемые на Web-страницах. Если щелкнуть мышью, например, ссылку Contacts, в окне элемента управления DataGrid будет показано содержимое таблицы Contacts.

Первоначально таблица Contacts будет отображаться пустой, поэтому Вы увидите в ней только одну строку, отмеченную звездочкой. При помощи мыши и клавиатуры Вы сможете добавить к таблице необходимое количество строк (рис. 8-18).

Рис. 8-18. Просмотр и редактирование содержимого таблицы Contacts

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

Напомним, что за это отвечает свойство столбца с именем ReadOnly. Для столбцов id обеих наших таблиц это свойство имеет значение true, поэтому Вы не сможете вручную изменить содержимое ячеек данных столбцов.

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

Ненужные строки можно удалить при помощи клавиши Delete, предварительно выделив из левой клавишей мыши. Чтобы выделить сразу несколько строк, при выделении держите в нажатом состоянии клавишу Shift.

Обратите внимание на кнопку со стрелкой, расположенную в правой части заголовка окна элемента управления DataGrid. С ее помощью Вы сможете вернуться назад к просмотру списка столбцов, показанного на рис. 8-17, а затем выбрать для просмотра таблицу Phones (рис. 8-19).

Рис. 8-19. Просмотр и редактирование содержимого таблицы Phones

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

К счастью, эту операцию выполнить очень легко. Достаточно задать значение для свойства DataMember, выбрав его из списка. В нашем случае для выбора необходимо отметить в списке строку Contacts, как это показано на рис. 8-20.

Рис. 8-20. Выбор таблицы для отображения в окне элемента управления DataGrid

Как нетрудно убедиться (рис. 8-21), после подобной настройки свойства DataMember в окне элемента управления DataGrid будет отображаться содержимое только одной таблицы. Кроме того, из заголовка этого окна исчезнет кнопка, предназначенная для просмотра списка таблиц.

Рис. 8-21. Теперь отображается только таблица Contacts

Настройка внешнего вида окна элемента управления DataGrid

Многочисленные свойства элемента управления DataGrid позволяют изменять внешний вид его окна, приводя его в соответствие с дизайном Вашего приложения.

Рамка вокруг окна

С помощью свойства BorderStyle программист может задать внешний вид рамки вокруг окна элемента управления DataGrid.

По умолчанию используется трехмерная рамка фиксированного размера (которой соответствует константа Fixed3D). Ее можно заменить одномерной рамкой FixedSingle или убрать совсем, присвоив свойству BorderStyle значение None.

Заголовок окна

Как мы уже говорили, свойство CaptionText задает текст заголовка таблицы. При необходимости Вы можете изменить шрифт заголовка, отредактировав свойство CaptionFont.

Форматирование содержимого окна

Установив значение свойства FlatMode, равным true, можно добиться отображения содержимого окна DataGrid в плоском виде.

Рис. 8-22. Результат изменения значения свойства FlatMode

Свойства Font и HeaderFont позволяют, соответственно, изменять шрифт, применяемый в окне элемента управления DataGrid, и в заголовках столбцов.

Если изменить значение свойства GridLineStyle на None, столбцы и строки таблицы не будут отделяться друг от друга линиями.

Сортировка

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

Запрет редактирования

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

Выбор цвета

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

Рис. 8-23. Свойства, определяющие цвета визуального интерфейса элемента управления DataGrid

Выделяя по очереди эти свойства в окне Properties, Вы сможете прочитать их детальное описание. Например, свойство GridLineColor позволяет задать цвет линий, отделяющих строки и столбцы таблицы, а свойство ForeColor задает цвет текста.

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

Набор стилей оформления TableStyles

Редактируя свойство TableStyles, можно задать стили оформления таблиц, определенных в элементе управления DataGrid. Давайте создадим разные стили оформления для разных столбцов таблицы Contacts.

Прежде всего, отредактируйте свойство TableStyles, добавив в него один стиль dataGridTableStyle1 с помощью редактора стилей (рис. 8-24).

Рис. 8-24. Редактирование набора стилей оформления

Как обычно, кнопка Add позволяет добавить новый стиль, а кнопка Remove — удалить его.

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

Так как мы создавали стиль dataGridTableStyle1 для таблицы Contacts, укажите имя этой таблицы в свойстве MappingName.

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

Далее необходимо создать три стиля оформления для столбцов id, Имя и Фамилия. Это делается путем редактирования свойства GridColumnStyles. Для редактирования вызывается специальный редактор стилей столбцов, окно которого показано на рис. 8-25.

Рис. 8-25. Стили оформления столбца id

При помощи свойства HeaderText задайте заголовок столбца. Первый столбец должен иметь заголовок Идентификатор, второй — Имя, и третий — Фамилия.

Выбирая по очереди созданные стили, свяжите каждый из них с соответствующим столбцом таблицы Contacts и отредактируйте свойства стиля. Для связывания стиля со столбцом таблицы отредактируйте свойство MappingName, как это показано на рис. 8-26.

Рис. 8-26. Привязка стиля к столбцу id

Выполните операцию привязки для всех столбцов таблицы Contacts.

Редактируя стиль столбца, Вы можете задать выравнивание текста внутри столбца (свойство Alignment), задать заголовок столбца HeaderText и ширину столбца Width.

По умолчанию при заполнении новой строки в ней отображается текст (null). Замените его для второго и третьего столбца на (имя) и (фамилия), соответственно. В результате пользователь увидит подсказку, показанную на рис. 8-27. Эта подсказка поможет ему ввести нужные данные.

Рис. 8-27. Подсказка для ввода данных

Программный код

В предыдущих разделах мы рассказали Вам о том, как можно создавать элементы управления DataGrid визуальными средствами проектирования, предусмотренным специально для этой цели в системе Microsoft Visual Studio .NET. Теперь настало время рассмотреть программный код, который создается при этом дизайнером форм.

Создание элемента управления DataGrid

Элемент управления DataGrid создается при помощи конструктора класса System.Windows.Forms.DataGrid, как это показано ниже:

private System.Windows.Forms.DataGrid dataGrid1;
this.dataGrid1 = new System.Windows.Forms.DataGrid();

Программа сохраняет идентификатор созданного элемента управления в поле dataGrid1.

Настройка свойств

Рассмотрим программный код, который создается для настройки свойств элемента управления DataGrid.

Свойство DataSource задает привязку элемента управления DataGrid к набору данных DataSet, содержащему таблицы с данными:

this.dataGrid1.DataSource = this.dataSet1;

Свойство DataMember привязывает наш элемент управления к таблице Contacts:

this.dataGrid1.DataMember = "Contacts";

Следующие строки задают шрифт для заголовка и текста:

this.dataGrid1.CaptionFont =
  new System.Drawing.Font("Arial", 8.25F,
  System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point,
   ((System.Byte)(204)));

this.dataGrid1.Font =
  new System.Drawing.Font("Microsoft Sans Serif", 8.25F,
  System.Drawing.FontStyle.Regular,
  System.Drawing.GraphicsUnit.Point, ((System.Byte)(204)));

Шрифт указывается в виде ссылки на вновь созданный объект класса System.Drawing.Font. В качестве первого параметра конструктору этого класса передается название шрифта, в качестве второго — размер шрифта в единицах, передаваемый посредством четвертого параметра. В нашем случае размер шрифта указывается в пунктах.

Третий параметр задает стиль шрифта, а пятый — номер кодовой страницы Unicode. Значение 204 соответствует кириллической кодовой странице.

Заголовок таблицы задается при помощи свойства CaptionText:

this.dataGrid1.CaptionText = "Контакты";

Значения других свойств определяются следующим образом:

this.dataGrid1.HeaderForeColor =
  System.Drawing.SystemColors.ControlText;
this.dataGrid1.Location = new System.Drawing.Point(32, 96);
this.dataGrid1.Name = "dataGrid1";
this.dataGrid1.Size = new System.Drawing.Size(288, 208);
this.dataGrid1.TabIndex = 0;

Добавление таблицы стилей

Таблица стилей создается для нашего элемента управления DataGrid как объект класса System.Windows.Forms.DataGridTableStyle:

this.dataGridTableStyle1 =
  new System.Windows.Forms.DataGridTableStyle();

В процессе инициализации приложения эта таблица стилей подключается к элементу управления DataGrid при помощи метода AddRange:

this.dataGrid1.TableStyles.AddRange(
  new System.Windows.Forms.DataGridTableStyle[]
  {
     this.dataGridTableStyle1
  }
);

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

Для каждого столбца мы создаем свой стиль, который затем подключается к таблице стилей dataGridTableStyle1 методом AddRange:

this.dataGridTextBoxColumn1 =
  new System.Windows.Forms.DataGridTextBoxColumn();

this.dataGridTextBoxColumn2 =
  new System.Windows.Forms.DataGridTextBoxColumn();

this.dataGridTextBoxColumn3 =
  new System.Windows.Forms.DataGridTextBoxColumn();

this.dataGridTableStyle1.GridColumnStyles.AddRange
(
  new System.Windows.Forms.DataGridColumnStyle[]
  {
     this.dataGridTextBoxColumn1,
     this.dataGridTextBoxColumn2,
     this.dataGridTextBoxColumn3
  }
);

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

this.dataGridTableStyle1.DataGrid = this.dataGrid1;

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

//
// dataGridTextBoxColumn2
//
this.dataGridTextBoxColumn2.Format = "";
this.dataGridTextBoxColumn2.FormatInfo = null;
this.dataGridTextBoxColumn2.HeaderText = "Имя";
this.dataGridTextBoxColumn2.MappingName = "Имя";
this.dataGridTextBoxColumn2.NullText = "(имя)";
this.dataGridTextBoxColumn2.Width = 75;
//
// dataGridTextBoxColumn3
//
this.dataGridTextBoxColumn3.Format = "";
this.dataGridTextBoxColumn3.FormatInfo = null;
this.dataGridTextBoxColumn3.HeaderText = "Фамилия";
this.dataGridTextBoxColumn3.MappingName = "Фамилия";
this.dataGridTextBoxColumn3.NullText = "(фамилия)";
this.dataGridTextBoxColumn3.Width = 75;
//
// dataGridTextBoxColumn1
//
this.dataGridTextBoxColumn1.Format = "";
this.dataGridTextBoxColumn1.FormatInfo = null;
this.dataGridTextBoxColumn1.HeaderText = "Идентификатор";
this.dataGridTextBoxColumn1.MappingName = "id";
this.dataGridTextBoxColumn1.ReadOnly = true;
this.dataGridTextBoxColumn1.Width = 75;

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

К счастью, дизайнер форм выполняет за нас всю черновую работу по созданию и сопровождению программного кода, отвечающего за инициализацию и настройку свойств элементов управления DataGrid и DataSet. В этом и заключается одно из преимуществ использования такого инструментального средства, как Microsoft Visual Studio .NET.

Работа с DataSet при помощи методов и свойств

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

Добавление новой строки

Для того чтобы добавить новую строку в таблицу набора данных DataSet, Вы вначале должны создать эту строку как объект класса DataRow, а затем добавить ее в набор строк таблицы при помощи метода DataTable.Rows.Add.

Ниже мы показали фрагмент кода, добавленного в конструктор класса Form1, и предназначенного для вставки в таблицу Contacts двух новых строк:

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

  //
  // TODO: Add any constructor code after InitializeComponent call
  //

  DataRow row = contactsDataTable.NewRow();
  row["Имя"] = "Иван";
  row["Фамилия"] = "Петров";
  contactsDataTable.Rows.Add(row);

  DataRow row1 = contactsDataTable.NewRow();
  row1["Имя"] = "Петр";
  row1["Фамилия"] = "Сидоров";
  contactsDataTable.Rows.Add(row1);
}

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

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

В результате после инициализации приложения в таблице Contacts появятся две строки (рис. 8-28).

Рис. 8-28. Две строки были добавлены в момент инициализации приложения

Теперь давайте задействуем поля ввода Имя и Фамилия, а также кнопку Добавить, расположенные в верхней части окна нашего приложения (рис. 8-28).

Создайте следующий обработчик события Click для кнопки Добавить:

private void button1_Click(object sender, System.EventArgs e)
{
  DataRow row = contactsDataTable.NewRow();
  row["Имя"] = textBoxFName.Text;
  row["Фамилия"] = textBoxLName.Text;
  contactsDataTable.Rows.Add(row);
}

Здесь мы предполагаем, что идентификатор поля Имя хранится в textBoxFName, а поля Фамилия — в textBoxLName.

Удаление строки

Вы можете удалять выделенные строки таблицы при помощи клавиши Delete. Однако есть и другая возможность — использование метода DataTable.Rows.Delete.

Добавьте в главное окно нашего приложения кнопку Удалить (рис. 8-29). Эта кнопка будет удалять запись, выделенную в окне элемента управления DataGrid.

Рис. 8-29. Добавлена кнопка для удаления выделенной строки

Добавьте для этой кнопки следующий обработчик событий:

private void button2_Click(object sender, System.EventArgs e)
{
  try
  {
  contactsDataTable.Rows[dataGrid1.CurrentCell.RowNumber].Delete();
  contactsDataTable.AcceptChanges();
  }
  catch(Exception ex)
  {
  }
}

Здесь мы используем свойство DataGrid.CurrentCell.RowNumber для получения номера строки, выделенной пользователем в таблице. Кстати, если пользователь выделил столбец, то его номер можно получить при помощи свойства DataGrid.CurrentCell.ColumnNumber.

Что же касается метода AcceptChanges, то его применение требует некоторых пояснений.

Дело в том, что при удалении строки методом DataTable.Rows.Delete строка фактически не удаляется, а только отмечается для удаления. Ее еще можно восстановить методом DataTable.Rows.RejectChanges. И только после вызова метода AcceptChanges удаление становится окончательным.

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

Приложение PhoneBookApp

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

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

Окно приложения PhoneBookApp показано на рис. 8-30.

Рис. 8-30. Окно приложения PhoneBookApp

В верхней части этого окна расположены элементы управления, предназначенные для просмотра и редактирования списка имен и фамилий, а в нижней — для просмотра и редактирования номеров телефонов.

Создание проекта приложения

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

Элементы управления для работы со списком имен и фамилий

В верхнюю часть окна нужно поместить два поля ввода текстовой информации с метками Имя и Фамилия. Первое из них должно иметь идентификатор fnTextBox, а второе — lnTextBox.

Для просмотра списка имен и фамилий, хранящихся в нашей базе данных, добавьте в окно приложения элемент управления ListView с идентификатором listView1. Отредактировав свойство Columns, добавьте в него три столбца, как это показано на рис. 8-31.

Рис. 8-31. Столбцы элемента управления listView1

Для первого столбца задайте в свойстве Text заголовок Имя, для второго — Фамилия, и, наконец, для третьего — id.

Назначение столбцов Имя и Фамилия очевидно — в них будут отображаться имена и фамилии людей, чьи телефонные номера хранятся в базе данных. Что же касается столбца id, то он предназначен для отображения уникальных идентификаторов строк в таблице Contacts, хранящей имена и фамилии.

Задайте идентификаторы столбцов Имя и Фамилия равными, соответственно, fnColumnHeader и lnColumnHeader.

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

Операции добавления, изменения и редактирования имен и фамилий мы будем выполнять при помощи кнопок Добавить, Изменить и Удалить. Перетащите значки этих кнопок из инструментальной панели Microsoft Visual Studio .NET и расположите их справа от списка просмотра имен и фамилий.

Элементы управления для работы со списком телефонов

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

Это поле редактирования текстовой информации с меткой Телефон, поле класса Label для просмотра имени и фамилии человека, телефоны которого в настоящий момент просматриваются или редактируются, список для просмотра телефонов, а также кнопки Добавить, Изменить и Удалить. С помощью кнопок пользователь сможет отредактировать список телефонов.

Поле редактирования номера телефона Телефон должно иметь идентификатор phoneTextBox. Задайте этот идентификатор, отредактировав соответствующим образом свойство Name.

Что касается поля Label для просмотра имени и фамилии человека, список телефонов которого просматривается или редактируется, то оно должно иметь идентификатор currentContactLabel. Настройте для него свойство BorderStyle, выбрав значение Fixed3D, а также свойство TextAlign, задав выравнивание MiddleLeft. Вы можете также изменить шрифт, которым будет отображаться имя и фамилия, при помощи свойства Font.

Далее Вам необходимо добавить в окно приложения еще один элемент управления ListView, предназначенный для отображения списка телефонов. Этот элемент управления должен иметь идентификатор listView2.

Переключите список listView2 в режим отображения детализированной таблицы, назначив свойству View значение Details, а также добавьте три столбца с названиями Телефон, id и idContacts (рис. 8-32).

Рис. 8-32. Столбцы элемента управления listView2

Столбец Телефон предназначен для отображения номеров телефона человека, имя которого выделено в верхнем списке. Что же касается столбцов id и idContacts, то первый из них предназначен для отображения уникальных идентификаторов записей в таблице Phones, хранящей номера телефонов, а второй — для хранения внешних ключей таблицы Contacts. Обе эти таблицы мы рассмотрим ниже в этой главе.

Создание таблиц базы данных

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

Для того чтобы создать базу данных, перетащите из инструментальной панели Toolbox системы Microsoft Visual Studio .NET в окно дизайнера форм значок элемента управления DataSet. Соответствующий компонент будет иметь идентификатор dataSet1.

Далее Вам нужно будет добавить в базу данных две таблицы, отредактировав для этого свойство Tables.

Таблица Contacts

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

Рис. 8-33. Свойства таблицы Contacts

Создайте в таблице Contacts три столбца id, Имя и Фамилия, как это показано на рис. 8‑34.

Рис. 8-34. Столбцы таблицы Contacts

Столбец id предназначен для хранения уникальных числовых идентификаторов строк таблицы. О настройке его свойств мы рассказывали при изучении приложения DataSetApp. Столбцы Имя и Фамилия предназначены для хранения текстовых строк и не имеют никаких особенностей.

Создав таблицу Contacts, отметьте столбец id как ключевой столбец, отредактировав соответствующим образом свойство таблицы PrimaryKey.

Таблица Phones

В таблице Phones мы будем хранить номера телефонов. Эта таблица должна иметь идентификатор phonesDataTable.

Создав таблицу, добавьте к ней три столбца id, idContacts и Телефон (рис. 8-35).

Рис. 8-35. Столбцы таблицы Phones

Столбец id должен играть роль первичного ключа (такого же, как и в таблице Contacts). Настройка свойств этого столбца показана на рис. 8-35.

Столбец Телефон хранит телефонные номера в виде обычных текстовых строк и не имеет никаких особенностей.

Что же касается столбца idContacts, то он хранит так называемые внешние ключи (foreign keys) таблицы Contacts. Таким образом, каждая строка таблицы Phones с номером телефона содержит идентификатор строки таблицы Contacts с именем и фамилией владельца этого телефона.

Очевидно, что если у человека имеется несколько телефонных номеров, то в таблице Phones для него будет создано несколько строк с одинаковыми значениями в ячейках столбца idContacts.

На рис. 8-36 мы показали настройку свойств для столбца idContacts.

 

Рис. 8-36. Свойства столбца idContacts таблицы Phones

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

Мы не разрешаем хранение в столбце idContacts пустых значений, т.к. если в базе данных есть телефон, то он обязательно должен кому-нибудь принадлежать. Поэтому мы приравниваем значение свойства AllowDBNull константе false.

Связывание таблиц Contacts и Phones

Элемент управления DataSet имеет свойство Relations, специально предназначенное для создания отношений между таблицами, входящими в набор данных.

Отредактируйте свойство Relations. Когда редактор отношений запускается в первый раз для набора данных, в котором еще нет отношений, на экране появляется диалоговое окно Relation, показанное на рис. 8-37.

Рис. 8-37. Создание нового отношения

В поле Name Вы можете задать имя для создаваемого отношения. В приложении PhoneBookApp мы оставили имя отношения, оставленное по умолчанию.

При создании нового отношения Вам нужно выбрать родительскую таблицу в списке Parent table и дочернюю таблицу в списке Child table. В нашем случае роль родительской таблицы будет играть таблица Contacts, а в роли дочерней — таблица Phones.

В списке Columns нужно указать связываемые поля. Мы привязываем столбец id родительской таблицы Contacts  (выбирая его в списке Key Columns) к полю idContacts дочерней таблицы Phones (выбирая его в списке Foreign Key Columns).

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

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

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

Если указать значение None, то изменения в родительской таблице не будут отражаться на дочерней таблице.

Список Delete rule задает режим удаления записей дочерней таблицы при удалении записи родительской таблицы.

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

Когда хотя бы одно отношение создано, то попытка редактирования свойства Relations приводит к появлению на экране редактора набора отношений Relations Collection Editor, показанного на рис. 8-38.

Рис. 8-38. Просмотр и редактирование списка отношений

Кнопки Add и Remove позволяют, соответственно, добавить новые отношения или удалить существующие. Для того чтобы изменить параметры отношения, его нужно выделить в списке Members, а затем щелкнуть кнопку Edit.

Проектирование кода для управления базой данных

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

Добавление нового контакта

Добавление нового контакта происходит тогда, когда пользователь щелкает кнопку Добавить, расположенную в верхней части главного окна приложения (рис. 8-30). Предварительно пользователь должен ввести имя и фамилию человека в полях Имя и Фамилия, соответственно.

Вам необходимо подготовить для только что упомянутой кнопки Добавить следующий программный код:

private void button1_Click(object sender, System.EventArgs e)
{
  if(fnTextBox.Text != "" && lnTextBox.Text != "")
  {
     DataRow row = contactsDataTable.NewRow();
     row["
Имя"] = fnTextBox.Text;
     row["
Фамилия"] = lnTextBox.Text;
     contactsDataTable.Rows.Add(row);

     listUpdate();
     phonesUpdate();
  }
}

Здесь мы вначале убеждаемся в том, что пользователь ввел как имя, так и фамилию. Для этого мы сравниваем содержимое свойства Text полей редактирования fnTextBox и lnTextBox с пустой строкой. Если какое-либо поле пустое, обработчик события завершает свою работу без выполнения других дополнительных действий.

В том случае, когда пользователь ввел все необходимые данные, обработчик события создает новую строку в таблице Contacts:

DataRow row = contactsDataTable.NewRow();

Для этого используется метод DataTable.NewRow.

На следующем этапе введенные строки имени и фамилии записываются в соответствующие столбцы строки:

row["Имя"] = fnTextBox.Text;
row["Фамилия"] = lnTextBox.Text;

Подготовленная таким способом строка добавляется в таблицу:

contactsDataTable.Rows.Add(row);

Далее наш обработчик события последовательно вызывает два метода с именами listUpdate и phonesUpdate. Первый из них отображает текущее содержимое таблицы Contacts в окне верхнего списка, а второй — список телефонов человека, имя которого было выбрано в верхнем списке. Мы вернемся к детальному описанию этих функций немного позже.

Выбор записи в списке имен и фамилий

Для отслеживания контактов, выбираемых пользователем в верхнем списке, мы предусмотрели обработчик события listView1_SelectedIndexChanged:

private void listView1_SelectedIndexChanged(object sender,
  System.EventArgs e)
{
  try
  {
     int currentRowIndex = listView1.SelectedItems[0].Index;

     DataRow row = contactsDataTable.Rows[currentRowIndex];

     fnTextBox.Text = row["Имя"].ToString();
     lnTextBox.Text = row["Фамилия"].ToString();
     currentContactId = (int)row["id"];

     currentContactLabel.Text = fnTextBox.Text + " " +
       lnTextBox.Text;
  }
  catch(Exception ex)
  {
  }

  phonesUpdate();
}

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

int currentRowIndex = listView1.SelectedItems[0].Index;

Далее он извлекает соответствующую строку из таблицы Contacts, а также получает значения столбцов извлеченной строки:

fnTextBox.Text = row["Имя"].ToString();
lnTextBox.Text = row["
Фамилия"].ToString();
currentContactId = (int)row["id"];

Имя и фамилия при этом записывается в поля редактирования fnTextBox и lnTextBox для того чтобы пользователь смог просмотреть их и при необходимости отредактировать.

Что же касается идентификатора строки id, то он сохраняется в поле currentContactId  класса Form1. Создайте это поле следующим образом:

private int currentContactId;

Дополнительно обработчик события listView1_SelectedIndexChanged записывает имя и фамилию текущего контакта в поле, расположенное непосредственно над списком телефонов (чтобы пользователю было ясно, чьи телефоны отображаются в этом списке):

currentContactLabel.Text = fnTextBox.Text + " "
  + lnTextBox.Text;

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

Редактирование записей таблицы Contacts

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

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

private void button2_Click(object sender, System.EventArgs e)
{
  if(fnTextBox.Text != "" && lnTextBox.Text != "")
  {
     string filter = "id='" + currentContactId.ToString() + "'";
     DataRow[] contactsRows = contactsDataTable.Select(filter);

     contactsRows[0].BeginEdit();
     contactsRows[0]["
Имя"] = fnTextBox.Text;
     contactsRows[0]["
Фамилия"] = lnTextBox.Text;
     contactsRows[0].EndEdit();

     contactsDataTable.AcceptChanges();

     listUpdate();
  }
}

Получив управление, наш обработчик убеждается в том, что пользователь задал не непустую строку имени или фамилии. Если это так, обработчик начинает процедуру обновления таблицы Contacts.

Для этого он вначале делает выборку из таблицы Contacts с помощью специального фильтра и метода DataTable.Select. Текстовая строка фильтра, задающая условие выборки, передается методу DataTable.Select в качестве параметра. Она определяет условие отбора строк из таблицы. Таким образом, метод DataTable.Select напоминает по своему назначению оператор SELECT языка запросов SQL, хотя, конечно, он не обладает всей мощностью этого оператора.

Метод Select возвращает записи в виде массива строк класса DataRow, которые можно обрабатывать в цикле или индивидуально с указанием индекса.

В нашем случае строка фильтра имеет следующий вид:

id=’<содержимое поля currentContactId>’

Напомним, что поле currentContactId класса Form1 содержит текущий идентификатор строки таблицы Contacts, выделенной пользователем в списке имен и фамилий. Это поле обновляется обработчиком событий listView1_SelectedIndexChanged, когда пользователь выделяет новую строку в упомянутом списке.

Так как нам нужно обновить имя и фамилию человека, чье имя выделено в списке, то из таблицы Contacts требуется выбрать такие строки, содержимое столбца id которых равно текущему значению поля currentContactId. В общем случае таких строк может быть несколько. Мы, однако, будем изменять только первую строку.

Вот как формируется и применяется строка фильтра в нашем приложении:

string filter = "id='" + currentContactId.ToString() + "'";
DataRow[] contactsRows = contactsDataTable.Select(filter);

Обратите внимание, что значение, которому должно быть равно содержимое столбца id, заключено в одинарные кавычки.

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

contactsRows[0].BeginEdit();
contactsRows[0]["
Имя"] = fnTextBox.Text;
contactsRows[0]["
Фамилия"] = lnTextBox.Text;
contactsRows[0].EndEdit();

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

Далее мы вызываем метод AcceptChanges, который вносит сделанные изменения в таблицу Contacts, и обновляем содержимое списка контактов при помощи метода listUpdate:

contactsDataTable.AcceptChanges();
listUpdate();

Удаление записей таблицы Contacts

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

private void button3_Click(object sender, System.EventArgs e)
{
  try
  {
     string filter = "idContacts='" +
       listView1.SelectedItems[0].Tag.ToString() + "'";

     DataRow[] phones = phonesDataTable.Select(filter);

     for(int i = 0; i < phones.Length; i++)
     {
       if(phones.Length != 0)
       {
          phones[i].Delete();
       }
     }
     phonesDataTable.AcceptChanges();

     contactsDataTable.Rows[
       listView1.SelectedItems[0].Index].Delete();

     contactsDataTable.AcceptChanges();
  }
  catch(Exception ex)
  {
  }

  listUpdate();
  phonesUpdate();
}

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

contactsDataTable.Rows[listView1.SelectedItems[0].Index].Delete();
contactsDataTable.AcceptChanges();

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

При связывании таблиц Contacts и Phones мы выбрали в списке Update rule значение Cascade, задающее режим автоматического внесения изменений в дочернюю таблицу при изменении записей родительской таблицы(рис. 8-37). Поэтому при удалении строк родительской таблицы происходит обновление соответствующих строк дочерней таблицы.

Тем не менее, для примера мы приводим фрагмент кода, который удаляет все строки таблицы Phones, связанные с удаляемой строкой таблицы Contacts:

string filter = "idContacts='" +
  listView1.SelectedItems[0].Tag.ToString() + "'";

DataRow[] phones = phonesDataTable.Select(filter);

for(int i = 0; i < phones.Length; i++)
{
  if(phones.Length !=
0)
  {
     phones[i].Delete();
  }
}

phonesDataTable.AcceptChanges();

Как видите, мы здесь вначале с помощью фильтра отбираем нужные записи из таблицы Phones, а потом удаляем их при помощи известного Вам метода Delete. Метод AcceptChanges завершает процедуру удаления.

Добавление номера телефона

Выделив в верхнем списке имя человека, пользователь может добавить для него один или несколько телефонов. Эта операция выполняется при помощи кнопки Добавить, расположенной возле списка телефонов.

Вот обработчик событий для этой кнопки:

private void button4_Click(object sender, System.EventArgs e)
{
  if(phoneTextBox.Text != "")
  {
     try
     {
       DataRow rowPhone = phonesDataTable.NewRow();
       rowPhone["idContacts"] = currentContactId;
       rowPhone["
Телефон"] = phoneTextBox.Text;
       phonesDataTable.Rows.Add(rowPhone);
     }
     catch (Exception ex)
     {
     }

     phonesUpdate();
  }
}

Здесь мы вначале проверяем, что поле нового телефона phoneTextBox не пустое. Если это так, то мы создаем в таблице Phones новую строку как объект класса DataRow, вызывая для этого метод phonesDataTable.NewRow.

Номер добавляемого телефона мы сохраняем в столбце Телефон. Что же касается столбца idContacts, то мы записываем в него идентификатор строки таблицы Contact. Это та самая строка, которая содержит имя, выбранное пользователем в списке контактов.

Заполненная строка добавляется в таблицу Phones методом phonesDataTable.Rows.Add.

И, наконец, после добавления строки наш обработчик событий обновляет содержимое списка телефонов в окне программы методом phonesUpdate.

Удаление номера телефона

Для удаления номера телефона предназначена кнопка Удалить, расположенная возле списка телефонов.

Вот обработчик событий для этой кнопки:

private void button6_Click(object sender, System.EventArgs e)
{
  try
  {
     string filter =
       "id='" + listView2.SelectedItems[0].Tag.ToString() + "'";

     DataRow[] phones = phonesDataTable.Select(filter);

     for(int i = 0; i < phones.Length; i++)
     {
       if(phones.Length != 0)
       {
          phones[i].Delete();
       }
     }
  }
  catch (Exception ex)
  {
  }

  phonesDataTable.AcceptChanges();
  phonesUpdate();
}

Прежде всего, наш обработчик определяет идентификатор строки таблицы Phones, подлежащей удалению. Напомним, что этот идентификатор хранится в столбце id упомянутой таблицы. При формировании  списка listView2 идентификаторы соответствующих строк сохраняются в свойствах Tag элементов этого списка.

Чтобы стереть строку, мы создаем фильтр filter, при помощи которого мы сможет получить первую строку из списка выделенных строк. Далее при помощи метода phonesDataTable.Select мы выделяем нужную строку и затем удаляем ее методом Delete.

Окончательные изменения в таблицу Phones вносятся методом phonesDataTable.AcceptChanges, после чего методом phonesUpdate выполняется обновление списка телефонов.

Изменение номера телефона

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

private void button5_Click(object sender, System.EventArgs e)
{
  if(phoneTextBox.Text != "")
  {
     try
     {
       string filter = "id='" +
          listView2.SelectedItems[0].Tag.ToString() + "'";
       DataRow[] phones = phonesDataTable.Select(filter);

       phones[0].BeginEdit();
       phones[0]["
Телефон"] = phoneTextBox.Text;
       phones[0].EndEdit();
     }
     catch (Exception ex)
     {
     }

     phonesDataTable.AcceptChanges();
     phonesUpdate();
  }
}

Если строка нового номера телефона phoneTextBox не пуста, то обработчик события button5_Click выделяет с помощью фильтра нужную строку в таблице Phones и затем изменяет содержимое ячейки в столбце Телефон.

Данная операция выполняется аналогично операции изменения содержимого строк таблицы Contacts, описанной ранее в разделе «Редактирование записей таблицы Contacts».

Обновление списка контактов

При описании исходных текстов приложения PhoneBookApp мы неоднократно упоминали метод listUpdate, обновляющий содержимое списка listView1 из таблицы Contacts. Вот его исходный текст:

public void listUpdate()
{
  listView1.Items.Clear();

  foreach(DataRow row in contactsDataTable.Rows)
  {
     ListViewItem lvi = new ListViewItem(row["Имя"].ToString());
     lvi.SubItems.Add(row["Фамилия"].ToString());
     lvi.SubItems.Add(row["id"].ToString());
     lvi.Tag = row["id"].ToString();
     listView1.Items.Add(lvi);
  }
}

Перед обновлением метод listUpdate очищает список, вызывая для этого метод listView1.Items.Clear.

Далее в цикле извлекаются все строки из таблицы Contacts. Внутри тела цикла каждая строка таблицы сохраняется в переменной row класса DataRow.

После этого мы создаем новый элемент списка lvi класса ListViewItem, записывая в него имя человека. Фамилия, а также идентификатор записи добавляются в элемент списка при помощи метода lvi.SubItems.Add.

Кроме этого, для каждого элемента списка мы сохраняем в свойстве Tag идентификатор строки таблицы, взятый из столбца id. Этот столбец является первичным ключом таблицы Contacts. Таким образом, каждый элемент списка listView1 хранит всю информацию соответствующей ему строки таблицы Contacts.

Для добавления строки в список мы вызываем метод listView1.Items.Add.

Обновление списка телефонов

Список телефонов listView2 отображает номера только одного человека, имя которого было выделено пользователем в списке контактов listView1. Для решения этой задачи мы создали в классе Form1 метод phonesUpdate:

public void phonesUpdate()
{
  listView2.Items.Clear();

  string filter =
     "idContacts='" + currentContactId.ToString() + "'";

  DataRow[] phones = phonesDataTable.Select(filter);

  if(phones.Length != 0)
  {
     for(int i = 0; i < phones.Length; i++)
     {
       ListViewItem lvi =
          new ListViewItem(phones[i]["
Телефон"].ToString());

       lvi.SubItems.Add(phones[i]["id"].ToString());
       lvi.SubItems.Add(phones[i]["idContacts"].ToString());
       lvi.Tag = phones[i]["id"];
       listView2.Items.Add(lvi);
     }
  }
}

Прежде всего, список телефонов очищается методом listView2.Items.Clear.

Далее наш обработчик должен отобрать в таблице Phones строки, соответствующие телефонам выделенного контакта. Идентификатор этого контакта в таблице Contacts хранится в поле currentContactId.

Отбор строк выполняется с помощью фильтра и метода Select, как это показано ниже:

string filter = "idContacts='" + currentContactId.ToString() + "'";
DataRow[] phones = phonesDataTable.Select(filter);

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

Обратите внимание — мы сохраняем в свойстве Tag элементов списка идентификаторы строк таблицы Phones (т.е. содержимое первичного ключа из столбца id).

Фильтр для метода Select

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

Перегруженные методы Select

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

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

Вариант метода Select с двумя параметрами позволяет дополнительно задать столбец, по которому нужно выполнять сортировку, а также порядок сортировки (прямой или обратный).

Пусть, например, для Интернет-магазина, торгующего книгами, мы создали таблицу покупок Orders. В этой таблице есть столбец seriesID, хранящий идентификатор серии книг (таких как «Шаг за шагом», «Самоучитель» и т.д.), а также столбец Price, в котором хранится стоимость книг.

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

DataTable booksTable = DataSet1.Tables["Orders"];

string strExpr = "seriesID='567'";
string strSort = "Price DESC";

DataRow[] foundRows = booksTable.Select(strExpr, strSort);

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

Существует и еще один перегруженный вариант метода Select с тремя параметрами. Первый из этих параметров задает фильтр, второй — столбец и порядок сортировки, а третий — состояние отбираемых строк таблицы.

Строки таблицы могут находиться в одном из нескольких состояний, описанных в перечислении DataViewRowState (табл. 8-1).

Таблица 8-1. Перечисление DataViewRowState

Константа

Состояние строки таблицы

Added

Новая строка

CurrentRows

Текущая неизмененная, новая или измененная строка

Deleted

Удаленная строка

ModifiedCurrent

Текущая версия модифицированной строки

ModifiedOriginal

Исходная версия модифицированной строки

None

Нет состояния

OriginalRows

Исходная или удаленная строка

Unchanged

Неизмененная строка

Краткое описание синтаксиса строки фильтра

В этом разделе мы приведем краткое описание синтаксиса строки фильтра. Более подробную информацию Вы найдете в справочной документации.

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

Если в имени столбца используются специальные символы, их нужно снабжать префиксом в виде обратного слеша, например: \t (символ табуляции), \n (новая строка), \r (перевод каретки).

Вот еще список специальных символов:

~()#\/=><+-*%&|^'"[]

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

Значения

Текстовые значения в операторах отношения необходимо заключать в одинарные кавычки.
Числовые значения указываются без кавычек. Что же касается дат, то их нужно заключать в пару символов #, например:

"ChangeDate > #3/30/62#"

При использовании такого условия будут отобраны строки, содержащие в столбце ChangeDate даты после 30 марта 1962 года.

Операторы

При составлении условия допускается использовать логические операторы AND, OR и NOT. Например:

(AuthorName = 'Иванов' OR BookName = 'Кобол') AND BookPrice >= 20

Для сравнения числовых величин можно использовать следующие операторы:

·        

·        

·         <=

·         >=

·         <> 

·         =

Последние два оператора из этого списка подходят для работы с текстовыми строками.

Оператор IN позволяет проверить содержимое столбца  на принадлежность к заданному списку значений.

С помощью оператора LIKE и символов шаблона * и % можно отбирать строки аналогично тому, как это делается в языке Transact-SQL (имеется в виду параметр LIKE оператора SELECT).

Числовые величины можно складывать, вычитать, умножать и делить, а также находить остаток от деления (выполнять деление по модулю). Для этого используется тот же набор операторов, что и в языке С#:

·         + (сложение)

·         - (вычитание)

·         * (умножение)

·         / (деление)

·         % (деление по модулю)

Когда оператор + используется с текстовыми строками, он выполняет их слияние (конкатенацию).

Функции

Дополнительно в фильтрах можно использовать функции, имена которых перечислены в табл. 8-2.

Таблица 8-2. Функции для фильтра

Имя

Описание

CONVERT

Преобразование типов данных в один из типов, предусмотренных на платформе Microsoft .NET Framework

LEN

Определение длины текстовой строки

ISNULL

Проверка значения на равенство пустому значению

IIF

Возврат одного из значений в зависимости от результата вычисления заданного логического выражения

SUBSTRING

Извлечение подстроки из текстовой строки

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

Конечно, возможности метода Select ограничены по сравнению с языком SQL. Однако не следует забывать, что набор данных DataSet дается Вам бесплатно вместе со средой исполнения Microsoft .NET Framework. Создавая объекты DataSet в своих приложениях, Вы можете интегрировать их с базами данных без необходимости установки на компьютере сервера SQL или таких систем SQL, как Microsoft Data Engine (MSDE).

Тем не менее, многие приложения должны быть интегрированы с «настоящими» СУБД, такими, например, как Microsoft SQL Server. В следующей главе нашей книги мы рассмотрим методики интеграции приложений C# с сервером Microsoft SQL Server.

[Назад] [Содержание] [Дальше]