Использование True DBgrid при создании приложений для управления базами данных
на Visual Studio.Net (часть 3) – использование C1DataObjects.
Введение в третью часть
В
примерах предыдущих двух частей для связи True DBGrid с
базой данных использовались компоненты C1DataExpress, которые являются «облегченной» версией компонентов доступа к данным и
чрезвычайно просты в применении. Такие компоненты идеально подходят для
создания локальных однопользовательских приложений.
Для
более сложных случаев ComponentOne
предоставляет компоненты доступа к
данным C1DataObjects, разработанные специально для работы в многоуровневых
многопользовательских приложениях. Эти компоненты также тесно интегрированы с NET.Remoting и могут обеспечивать работу в «отсоединенном» режиме. В состав C1DataObjects входят следующие компоненты:
·
C1DataTableSource
·
C1DataSet
·
C1DataSetLogic
·
C1DataView
·
C1SchemaDef
·
C1SchemaRef
·
C1TableLogic
Основные определения.
Я
уже сказал, что C1DataObjects специально предназначены для работы в
многоуровневых приложениях. В документации ComponentOne
определены три вида таких приложений:
- data library – приложение-источник данных;
- data client – клиентское приложение, использующее data library;
- direct client – клиентское приложение, работающее с базой данных напрямую.
Основным
компонентом, на базе которого строится вся схема доступа к данным -
C1SchemaDef. Он инкапсулирует «схему» базы данных, т.е. определения соединений,
датасетов, таблиц и представлений. C1SchemaDef связан с базой данных и может
обновлять свою структуру при обновлении самой БД, хотя обычно его бывает
достаточно создать его один раз.
C1SchemaDef
определяет структуру базы данных, но не содержит физических данных. Для этого
служит компонент C1DataSet, структуру которого и определяет C1SchemaDef.
C1DataSet инкапсулирует объекты DataTable, в которых и хранятся данные. C1DataSet способен
работать в «отсоединенном» режиме, без постоянной связи с базой данных, и
обновлять данные в пакетном режиме. Один компонент C1SchemaDef может
предоставлять «схему» нескольким C1DataSet. При этом не обязательно, чтобы
C1DataSet и определяющий его структуру C1SchemaDef находились на одной машине.
Компоненты C1.Data
имеют встроенные средства для работы в
сети.
Если
пользователю нужны данные в виде, не представленном в базе данных, к услугам
программиста компонент C1DataView, позволяющий строить представления на основе
C1DataSet. Компоненты C1DataSetLogic и C1TableLogic предназначены для
построения бизнес-логики приложения. Компонент C1DataTableSource применяется
на стороне клиента для работы в «виртуальном режиме» для ускорения отображения
таблиц, содержащих большое количество записей.
Теперь
можно построить схему доступа к данным с использованием компонентов
C1DataObjects. В случае direct
client приложения она будет выглядеть
следующим образом:
Рис
1.
В
случае многоуровневого приложения схема меняется:
Рис
2.
Как
видно из рисунка, приложение разделяется на две части. При этом на серверной
стороне остается компонент C1SchemaDef и вся бизнес-логика.
Компонент C1DataSet
и объекты представления данных остаются
на стороне клиента.
Замечательной
особенностью компонентов C1Data
является то, что в них средства для
построения многоуровневых приложений заложены изначально. Это позволяет во
многих случаях для клиент-серверных приложений обойтись без дополнительного
кода.
Построение приложения с
использованием C1DataObjects- создание схемы данных.
Для
начала построим direct client приложение. Используем базу данных Northwind.
Создадим
новый проект. На форме разместим:
-
три таблицы True DBGrid;
-
компонент C1SchemaDef;
-
компонент C1DataSet;
Рис
3.
Щелкаем
правой кнопкой мыши на компоненте c1SchemaDef1 и в
появившемся контекстном меню выбираем пункт Schema Designer. Откроется окно
дизайнера схемы:
Рис
4.
Теперь
нам необходимо создать схему нашего источника данных. Для этого существует две
возможности:
-
создать схему «вручную»;
-
импортировать структуру существующей базы данных.
Поскольку
мы строим приложение на основе реальной базы данных, выбираем второй путь. В дизайнере схемы идем в меню Schema -> Import database structure… Открывается мастер импорта:
Рис
5.
На
первой странице мастера предлагается подключиться к требуемой базе данных.
Подключение трудностей не вызывает.
На
второй странице мастера предлагается выбрать требуемые объекты базы данных:
Рис
6.
После
выбора требуемых объектов можно смело нажимать кнопку Finish, после
чего будет сгенерирована схема:
Рис
7.
Здесь
давайте немного остановимся и я подробнее расскажу о дизайнере схемы.
Как
видно на рисунке, дизайнер состоит из довольно большого количества окон. По
краям главной формы дизайнера расположены маленькие окошки, отображающие
объекты по категориям. Центральное большое окно по умолчанию отображает схему
данных в графическом виде. При выборе объекта в одном из боковых окошек в
центральном окне появляется новая вкладка, содержащая подробные данные о
выбранном объекте и позволяющая редактировать его свойства.
Я
не буду здесь приводить скриншоты всех вкладок центрального окна и объяснять все
подробности. Мои статьи и так перегружены картинками, а настройки интуитивно
понятны. Остановлюсь только на некоторых тонкостях.
Работать
с объектами, отображаемыми в окнах можно либо с помощью встроенных в них
панелей инструментов, либо с помощью контекстного меню.
Давайте
рассмотрим окна и представленные в них объекты.
-
Окно Tables.
Здесь
отображаются входящие в схему таблицы. C1SchemaDef поддерживает два вида
таблиц:
·
Simple Table: простая таблица. Простые таблицы в свою очередь
могут быть трех типов: Bound (связанные с базой данных), SqlBased (на
базе Sql-запроса) и Unbound (несвязанные);
·
Composite Table:
композитная таблица, фактически представление, построенное на основе простых
таблиц.
Таблицы
схемы отличаются от таблиц базы данных. Они имеют целый набор дополнительных
свойств:
-
Вычисляемые поля (Calculations);
-
Ограничения значений полей (Constraints);
Ограничения
могут быть двух типов:
- на уровне полей (Field constraints);
- на уровне записей (Record
constraints).
-
Условия обновления таблицы. Имеет значения:
- Always;
- Never;
- IfPossible.
-
Окно Relations.
В
этом окне отображаются связи между таблицами. Если вы импортировали структуру
существующей БД, то вместе с другими объектами будут импортированы и связи между
таблицами. Связи можно удалять, редактировать и создавать новые.
-
Окно Connections.
Как
уже ясно из названия, в этом окне отображаются соединения с БД. Одна схема
может содержать несколько подключений к разным источникам. В схеме и датасетах
возможно совместное использование таблиц и/или представлений из разных
источников и установка связей меду ними.
-
Окно DB Tables.
В
этом окне отображаются таблицы (и/или представления) базы данных. Причем в этом
окне отображаются ВСЕ таблицы подсоединенной БД, а не только те, чья структура
импортирована в C1SchemaDef. При вызове дизайнера соединение с базой данных не
производится, и окно DB Tables пустое. Для того чтобы заполнить его, необходимо явно
дать команду на подключение к БД. Это окошко обладает замечательным свойством:
таблицы БД можно отсюда добавлять в схему либо методом «перетаскивания»
необходимой таблицы в окно Tables, либо командами Copy – Paste.
Теперь
давайте немного отвлечемся от окошек дизайнера, и рассмотрим работу с базами
данных. Для этого в дизайнере служит меню Schema.
Рис
8.
Команда
Connect to database… служит для подключения к БД.
Команда
Import database structure… мы с вами уже рассмотрели.
Команда
Add absent database objects… позволяет добавить к схеме уже существующие объекты.
Приведенные
команды служат для создания схемы. Однако вы не хуже меня знаете, как создается
БД – приложение. В процессе создания как сама программа, так и структура базы
данных меняется несколько раз. Для облегчения труда программиста служат
следующие команды меню Schema:
- Compare schema with database structure… - сравнивает структуру схемы со структурой базы
данных. При выборе этой команды запускается мастер, где можно настроить опции
сравнения. Результат сравнения выводится в окно Output.
Рис
9.
- Verify schema… Данная команда также производит сравнение схемы со структурой базы
данных. Однако, в отличие от предыдущей команды, схема сразу же изменяется в
соответствии со структурой БД. Команда работает следующим образом:
- сравниваются
объект схемы и соответствующий объект БД;
-
если выявлено различие, дизайнер проверяет возможность внесения изменений в
схему;
-
если изменения возможны, они вносятся в схему;
-
если изменения невозможны, в окно Output выводится соответствующее
сообщение;
-
дизайнер переходит к следующему объекту.
- Clear schema… Смысл этой команды ясен из названия.
Таким
образом дизайнер позволяет не только создать схему, но и поддерживать ее
актуальность в процессе разработки ПО.
Указанные
возможности синхронизации схемы и базы данных реализуемы и программно, так что
можно создавать приложения, у которых база данных изменяется прямо в процессе
их функционирования, и сразу же вносятся изменения в пользовательский
интерфейс. Так что можете создавать с помощью компонентов С1 конкурента 1С (вот
получился каламбур).
-
Окно DataSets.
Теперь
о том, ради чего все это делалось. Слева внизу расположено окно DataSets.
В начале работы оно пустое. Теперь нам необходимо в нашей схеме создать один
или несколько Data Set. Следует только помнить, что эти датасеты
«виртуальные», т. е. они не хранят данные, но определяют структуру «реальных»
датасетов.
Для
добавления Data Set следует нажать кнопку на панели инструментов либо соответствующую
команду контекстного меню. В центральном окне появляется новая панель:
Рис
10.
В
одной схеме можно создать произвольное количество датасетов. Датасет схемы
может определять структуру «реального» датасета, хотя это не обязательно. Вы
можете, например, «реальный» датасет программно переключать между
«виртуальными» датасетами в схеме, меняя его структуру.
Созданную
схему дизайнер позволяет сохранить на диске в виде файла, имеющего расширение
*.c1dscm. Сохраненный файл со схемой потом можно открыть и
использовать для дальнейшей разработки.
Теперь
последний пункт меню Schema: Options.
При
выборе этого пункта меню открывается одноименное окно. Нас интересует его
вторая вкладка:
Рис
11.
Здесь
можно выбрать, где должна располагаться схема в программе: непосредственно в самом
приложении, либо в виде отдельного *.dll – файла. Последняя
возможность очень важна, если вы хотите создать приложение, способное работать
с несколькими платформами БД.
Построение приложения с
использованием C1DataObjects- создание DataSet и
связь с данными.
Вернемся
к нашему примеру. Создадим в дизайнере датасет и добавим в него все три
выбранные нами таблицы (Employees, Orders, и Order Details). Переходим в главное окно Visual Studio и устанавливаем свойства c1DataSet1:
SchemaDef=c1SchemaDef1;
DataSetDef=DataSet
(где DataSet – определение датасета из нашей схемы).
Теперь
переходим к таблицам (True
DBGrid). Устанавливаем у всех трех таблиц
свойство
DataSource=c1DataSet1.
Со свойством DataMember дело обстоит сложнее. c1DataSet
предоставляет целый набор «членов данных». Их видно во вспомогательном
выпадающем окне, которое появляется в окне свойств:
Рис
12.
Из
рисунка видно, что генерируется два вида «членов данных»
1.
Члены данных, названия которых
повторяют названия соответствующих таблиц. Такие «члены данных» возвращают ВСЕ
записи соответствующей таблицы.
2.
Члены данных, чье название
начинается с символа подчеркивания «_». Такие «члены данных» предоставляют
иерархическую структуру данных с учетом связей между таблицами и текущей записи
родительской таблицы. В окне иерархия данных представлена в виде «дерева». В
коде уровни иерархии разделяются между собой знаком «.» (точка).
Теперь
будьте внимательны!
Первая
таблица: Employees. Здесь необходимо отобразить все данные таблицы. Но
поскольку мы хотим, чтобы в других таблицах отображались данные с учетом
записи, выбранной в родительской таблице, необходимо использовать структуру _ Employees. Для других таблиц необходимо будет использовать вложенные члены этой
же структуры.
Отступление: пишу эти
строки, и испытываю некоторое неудобство. Дело в том, что, например, в
английском языке существуют два четко различимых термина: «Table»
и «Grid», а на русский язык оба они переводятся
одинаково: «таблица». Надеюсь, читатель сможет разобраться, где имеется в виду
таблица БД, а где – отображение данных.
Устанавливаем
свойство DataMember:
- c1True DBGrid1: DataMember=_Employees;
- c1True DBGrid2: DataMember=_Employees.Employees – Orders;
- c1True DBGrid3: DataMember=_Employees.Employees - Orders.Orders -
Order Details;
Обновляем
поля. Можно запускать:
Рис
13.
При
перемещении по записям в таблице Employees будут автоматически обновляться записи в таблицах Orders
и Order Details, а при перемещении по записям в таблице Orders
будут обновляться записи в таблице Order Details.
В
случаях, когда перед отображением данных требуется их сортировка и отбор, можно
применять компонент c1DataView.
c1DataView предствляет
собой «промежуточное» звено между таблицей, содержащейся в DataSet и
True DBGrid (или любым другим контролом, поддерживающим связь с
данными).
Для
работы с c1DataView необходимо прежде всего установить его свойства DataSet (ссылка
на датасет, содержащий данные) и TableName
(имя таблицы в датасете). Свойств, ради
которых и применяется c1DataView
три: Sort, RowFilter и RowStateFilter. Их применение полностью совпадает с аналогичными
свойствами c1ExpressView, описанного в первой части статьи.
Построение приложения с использованием
C1DataObjects- добавление в клиентское приложение бизнес-логики.
Применение
бизнес-логики в приложении на базе C1 основано на перехвате событий
компонентов c1SchemaDef
и входящих в его состав таблиц. У самого
компонента c1SchemaDef события, связанные с обработкой данных недоступны.
Доступ к событиям обеспечивают компоненты c1DataSetLogic и c1TableLogic.
Для
добавления бизнес-логики к приложению проще всего поступить так:
Щелкаем
правой кнопкой мыши на компоненте c1SchemaDef, и в
открывшемся контекстном меню выбираем пункт «Create Business Logic Components…». При этом будут сгенерированы компоненты
бизнес-логики (по компоненту c1DataSetLogic для каждого из датасетов, и по компоненту c1TableLogic для каждой таблицы).
Рис
14.
В случае
если компоненты c1DataSetLogic
и/или c1TableLogic добавлены к проекту вручную, необходимо установить их свойства:
Для
c1DataSetLogic:
-
SchemaComponent;
- DataSetDef.
Для
c1TableLogic:
-
SchemaComponent;
-
Table.
Компоненты
c1DataSetLogic
и c1TableLogic предоставляют программисту возможность перехватывать огромное
количество событий, что позволяет очень гибко управлять работой вашего
приложения. Я не буду приводить список всех событий. Большинство из них
«парные», например:
- BeforeAddNew и AfterAddNew;
- BeforeFieldChange и AfterFieldChange;
- и т. д.
Многие
события имеют вообще «тонкую структуру», например, при редактировании ячейки
можно перехватить аж 6(шесть!) событий:
- BeforeBeginEdit;
- AfterBeginEdit;
- BeforeEndEdit;
- AfterEndEdit;
- BeforeCancelEdit;
- AfterCancelEdit.
Я
приведу только самый простой пример: у нас в таблице Orders есть поле
CustomerID. Сделаем так, чтобы вводились только заглавные буквы
(конечно же, приведенный способ не единственный). Пишем код:
C#:
private void
table_Orders_AfterFieldChange(object sender, C1.Data.FieldChangeEventArgs
e)
{
if
(e.Field.Name == "CustomerID")
e.NewValue = ((string)e.NewValue).ToUpper();
}
VB.NET:
Private Sub
table_Orders_AfterFieldChange(ByVal sender As Object, ByVal e As C1.Data.FieldChangeEventArgs)
Handles table_Orders.AfterFieldChange
If
e.Field.Name = "CustomerID" Then
e.NewValue = CStr(e.NewValue).ToUpper()
End If
End Sub
Промежуточный
вывод
Мы
с вами построили direct client
приложение. Но все, что было рассмотрено
выше, относится к любому «клиенту». Вы наверное уже успели оценить всю мощь
компонентов c1Data. Подождите, мы рассмотрели только простейший случай.
Идем дальше.
Использование
компонентов c1 для доступа к данным в «виртуальном режиме»
Базы
данных бывают разные… Некоторые из них содержат в своих таблицах очень много
данных. И если такую таблицу выводить в True DBGrid (как
впрочем, и в другие подобные контролы), то пауза перед отображением данных
будет слишком длинной. В таких случаях может помочь «виртуальный режим». Суть
его заключается в том, что компоненты начинают отображать данные, не дожидаясь
их полной загрузки.
Для
того чтобы почувствовать работу «виртуального» режима необходимо использовать
достаточно большую базу данных. Я использовал БД в одной из таблиц которой
примерно 1,6 млн. записей. Надеюсь, что читатель для повторения моих
экспериментов найдет что-нибудь подходящее. Создадим простое приложение для
отображения данных с использованием True DBGrid (можно использовать DataGrid из Windows Forms или др.) и свяжем его с данными используя c1SchemaDef и c1DataSet. Запустите приложение. Сколько ушло времени на
загрузку данных?
Для
реализации «виртуального» режима нам понадобится еще один компонент -
C1DataTableSource. Разместим его в приложении.
Рис
15.
Устанавливаем
свойства c1DataTableSourse1:
DataSet=c1DataSet1
TableView= «имя
таблицы»
Теперь
у c1TrueDBGrid1 устанавливаем в качестве источника данных c1DataTableSourse1.
Теперь
самое главное. Открываем дизайнер нашей схемы данных и выбираем для
редактирования нужный датасет. Виртуальный режим включается установкой его
свойства DataAccessMode:
Рис
16.
Данное
свойство может принимать следующие значения:
Static (по умолчанию)
|
Никакие данные не
передаются для отображения, пока не будут заполнены таблицы. Данные не
кешируются.
|
Virtual
|
Данные отображаются сегментами
фиксированного размера, количество загружаемых сегментов ограничено. Новые
сегменты подгружаются по мере необходимости. Неиспользуемые сегменты
выгружаются из кеша.
|
VirtualAutomatic
|
Аналогично Virtual, но количество сегментов в кеше не ограничено, и они не выгружаются
до закрытия формы. Новые сегменты подгружаются по мере необходимости.
|
VirtualUnlimited
|
В этом режиме количество
сегментов в кеше не ограничено, но в отличие от предыдущего режима они
подгружаются не по мере необходимости, а сразу же до полной выборки таблицы.
|
Вы
уже наверное поняли, что режимы в таблице расположены в порядке возрастания
требований к ресурсам компьютера.
Из Рис
16. видно, что размер сегмента и количество сегментов в кеше устанавливается
здесь же. Размер сегмента указывается в количестве записей.
Установите
виртуальный режим и запустите приложение. Вы будете приятно удивлены скоростью
его загрузки.
Теперь
попробуйте «пролистнуть» записи вниз с помощью вертикальной прокрутки. Обратите
внимание на ползунок. Вначале он будет перемещаться как обычно, но не дойдя до
конца полосы прокрутки остановится и через долю секунды «отскочит» назад. Это
происходит из-за «подгрузки» очередного сегмента данных. Будет меняться также
размер ползунка. Однако если теперь вы попробуете узнать количество записей, то
свойство Rows.Count
таблицы покажет размер сегмента данных
Использование
компонентов c1 для доступа к данным в «отсоединенном режиме»
Суть
«отсоединенного» режима в том, что вы подключаетесь к базе данных, и создаете у
себя ее локальную копию (или копию части БД), после чего соединение с БД можно
разорвать. Далее вы работаете со своей копией, после чего вновь подсоединяетесь
к БД и проводите обновление данных в пакетном режиме.
Компоненты
c1 реализуют отсоединенный режим с помощью
«динамического» подключения. Подключения, которые отображаются в
соответствующем окне дизайнера схемы, логично было бы назвать «статическими». «Динамические»
подключения создаются в runtime.
При
этом «статические» подключения из схемы удалять нельзя, поскольку у них имеется
еще одна функция: предоставлять схеме свои свойства.
Создадим
новый пример. Я назвал его DCTest. В папке проекта создадим папку под названием Data,
а в папке – две совершенно одинаковых базы данных db1.mdb и
db2.mdb, содержащих всего по одной таблице:
Рис
18.
Как
видно из рисунка, я заполнил таблицы кое-какими данными.
Главная
форма проекта казана на Рис 19:
Рис
19.
Добавим
к проекту компоненты C1SchemaDef
и C1DataSet и
построим приложение связав его например с db1.mdb.
Создаем
процедуру динамического подключения к БД: (C#)
private void SetConnection(string
dbName)
{
c1DataSet1.DynamicConnections.Clear();
Assembly assembly =
Assembly.GetEntryAssembly();
Uri uri = new Uri(assembly.CodeBase);
string
dir = Path.GetDirectoryName(uri.LocalPath );
DirectoryInfo dirInfo = new DirectoryInfo(dir);
while
(dirInfo.Parent != null)
{
if
(String.Compare(dirInfo.Name, "DCTest", true)
== 0)
{
dirInfo =
dirInfo.Parent;
break;
}
dirInfo = dirInfo.Parent;
}
Connection conn=new C1.Data.SchemaObjects.C1OleDbConnection();
conn.Name="Connection";
conn.ConnectionString="Provider=Microsoft.Jet.OLEDB.4.0;Data
Source=" + dirInfo.FullName + "\\Data\\" + dbName;
c1DataSet1.DynamicConnections.Add(conn);
}
Теперь
наполняем функциональностью обработчики нажатия кнопок
-
кнопка «Обновить»:
c1DataSet1.Update();
-
кнопка «Отсоединить БД»:
c1DataSet1.DynamicConnections.Clear();
-
кнопки «Подключить db1» и «Подключить db2»:
SetConnection("db1.mdb");
SetConnection("db2.mdb");
- кнопка «Очистить DataSet»:
c1DataSet1.Clear();
-
кнопка «Заполнить DataSet»:
c1DataSet1.Fill();
Запускаем
приложение и попробуем «поиграть» с кнопками, не забывая об их
функциональности. Выявляется следующее:
-
само по себе динамическое подключение не обновляет данные в C1DataSet.
Для этого необходимо явно дать команду Fill.
-
редактировать данные можно и в «отсоединенном» режиме, но их сохранение
возможно только при наличии связи с БД.
-
при попытке обновить данные в C1DataSet при отключенной базе данных происходит его очистка.
-
при попытке сохранить данные при отсоединенной БД ошибки не происходит, но данные
не сохраняются.
Таким
образом вы можете создавать приложения, изменяющие источники данных
непосредственно в процессе работы.
Выводы
к третьей части.
Как
можно понять из изложенного, компоненты C1.Data предоставляют
мощный и гибкий механизм доступа к данным. И одновременно они обладают
удивительной логичностью и ясностью. С их помощью вы сможете построить
высококачественные приложения для работы с БД.
Материала
оказалось очень много для одной заметки. И я решил изложить построение
многоуровневых приложений в виде отдельной статьи. Поэтому не прощаюсь.