- •А.А. Волосевич
- •4. БАзы данных и технология ado.Net 3
- •4. БАзы данных и технология ado.Net
- •4.1. Архитектура ado.Net
- •4.2. Соединение с базой данных
- •4.3. Выполнение команд и запросов к базе данных
- •4.4. Параметризированные запросы
- •4.5. Чтение данных и объект DataReader
- •4.6. Рассоединенный набор данных
- •4.7. Объект класса DataColumn – колонка таблицы
- •4.8. Объекты класса DataRow – строки таблицы
- •4.9. Работа с объектом класса DataTable
- •4.10. Схема данных и типизированные dataset
- •4.11. Навигация, Поиск и фильтрация данных в DataSet
- •4.12. Класс DataView
- •4.13. Заполнение Рассоединенного набора данных
- •4.14. СиНхронизация набора данных и базы
- •4.15. Работа с транзакциями
4.10. Схема данных и типизированные dataset
Описав в предыдущих параграфах основные компоненты набора данных, рассмотрим класс DataSet более подробно. Начнем с таблицы свойств класса.
Таблица 11
Свойства класса DataSet
Имя свойства |
Описание |
CaseSensitive |
Определяет, учитывается ли регистр при поиске строк |
DataSetName |
Строка с именем набора данных |
EnforceConstraints |
Определяет, обеспечивает ли DataSet выполнение определенных на нем ограничений |
ExtendedProperties |
Коллекция пользовательских свойств набора данных |
HasErrors |
Указывает, содержит ли набор данных ошибки |
Locale |
Свойство имеет тип CultureInfo и определяет региональные параметры, используемые набором данных при сравнении строк |
Relations |
Коллекция отношений, связывающих таблицы из набора данных |
RemotingFormat |
Позволяет указать формат данных при сериализации DataSet – бинарный или XML |
Tables |
Возвращает коллекцию таблиц набора данных |
Большинство свойств в особых пояснениях не нуждается, ибо, по сути, является аналогом свойств таблицы, но для всего набора данных. Свойство Relations содержит коллекцию связей между таблицами и является часть описания схемы данных. Если значение свойства EnforceConstraints установить в false, то при загрузке информации в DataSet данные не будут проверяться на соответствие ограничениям. Это повышает производительность.
В табл. 12 перечислены методы набора данных:
Таблица 12
Методы класса DataSet
Имя метода |
Описание |
AcceptChanges() |
Метод фиксирует все изменения данных, которые были проделаны с момента предыдущего вызова AcceptChanges() |
Clear() |
Уничтожаются все строки всех таблиц набора данных |
Clone() |
Метод клонирует структуру набора и возвращает пустой набор |
Copy() |
Метод клонирует и структуру, и данные набора |
GetChanges() |
Возвращает новый DataSet с идентичной схемой, содержащий измененные строки и таблицы оригинального объекта DataSet |
GetXml() |
Возвращает содержимое объекта DataSet в виде XML-строки |
GetXmlSchema() |
Возвращает схему объекта DataSet в виде XML-строки |
HasChanges() |
Возвращает логическое значение, указывающее, содержат ли строки из состава DataSet отложенные изменения |
Merge() |
Осуществляет слияние данных из другого объекта DataSet, DataTable или массива объектов DataRow и данных текущего объекта DataSet |
ReadXml() |
Читает содержимое DataSet в XML-формате из файла, Stream, TextReader или XmlReader |
ReadXmlSchema() |
Работает как ReadXml(), но читает только схему DataSet |
RejectChanges() |
Метод отменяет изменения, которые еще не зафиксированы вызовом AcceptChanges() |
Reset() |
Восстанавливает оригинальное состояние DataSet |
WriteXml() |
Записывает содержимое DataSet в XML-формате в файл, Stream, TextWriter или XmlWriter |
WriteXmlSchema() |
Работает как WriteXml(), но записывает только схему DataSet |
Рассмотрим, как вручную задать схему набора данных. Напомним, что правильная схема включает тип и имя отдельных столбцов таблицы, ограничения на столбцы и связи между таблицами. Будем создавать схему для таблиц Artists и Disks.
Вначале создадим объекты, соответствующие столбцам и таблицам, и поместим столбцы в таблицы:
var Artists = new DataTable("Artists");
var id_artists = new DataColumn
{
ColumnName = "id",
DataType = typeof(int),
AllowDBNull = false
};
var name = new DataColumn
{
ColumnName = "name",
DataType = typeof(string),
AllowDBNull = false
};
Artists.Columns.Add(id_artists);
Artists.Columns.Add(name);
var Disks = new DataTable("Disks");
var id_disks = new DataColumn
{
ColumnName = "id",
DataType = typeof(int),
AllowDBNull = false
};
var title = new DataColumn
{
ColumnName = "title",
DataType = typeof(string),
MaxLength = 50,
AllowDBNull = false
};
var artist_id = new DataColumn("artist_id", typeof(int));
var release_year = new DataColumn
{
ColumnName = "release_year",
DataType = typeof(string),
MaxLength = 4
};
Disks.Columns.Add(id_disks);
Disks.Columns.Add(title);
Disks.Columns.Add(artist_id);
Disks.Columns.Add(release_year);
Перейдем к работе с таблицами. Как и в реляционных база данных, один или несколько столбцов таблицы DataTable могут исполнять роль первичного ключа. Первичный ключ должен быть уникальным в пределах таблицы. Свойство таблицы PrimaryKey служит для получения или установки массива столбцов, формирующих первичный ключ. Если столбец является частью первичного ключа, его свойство AllowDBNull автоматически устанавливается в false. Установим первичные ключи наших таблиц:
Artists.PrimaryKey = new[] { id_artists };
Disks.PrimaryKey = new[] { id_disks };
Таблица поддерживает свойство Constraints – набор ограничений таблицы. Значением данного свойства является коллекция объектов класса Constraint. Класс Constraint – абстрактный базовый класс, имеющий два производных класса: ForeignKeyConstraint и UniqueConstraint.
Класс UniqueConstraint – это класс, при помощи объектов которого реализуется концепция уникальности значений полей строки. Основными свойствами этого класса является массив столбцов Columns, имя ограничения ConstraintName и булево свойство IsPrimaryKey, которое показывает, представляет ли данное ограничение первичный ключ таблицы. Ограничение вида UniqueConstraint автоматически добавляется в таблицу при создании первичного ключа. Это же ограничение появляется, если в таблицу добавляется столбец с установленным свойством Unique.
Так как в нашем примере мы уже создали первичные ключи таблиц, то их коллекции Constraints не пусты. Изменим имя ограничения в одной из таблиц и добавим ограничение для столбца name таблицы Artists, полагая значения в этом столбце уникальными:
Artists.Constraints[0].ConstraintName = "Primary Key";
var uc = new UniqueConstraint("Unique Name", new[] { name });
Artists.Constraints.Add(uc);
Класс ForeignKeyConstraint служит для описания внешних ключей таблицы. Его основные свойства перечислены в табл. 13.
Таблица 13
Основные свойства класса ForeignKeyConstraint
Имя свойства |
Описание |
AcceptRejectRule |
Определяет, каскадируются ли результаты вызова методов AcceptChanges() и RejectChanges() родительского объекта DataRow в дочерние строки |
Columns |
Столбцы дочерней таблицы, составляющие ограничение |
ConstraintName |
Имя ограничения |
DeleteRule |
Определяет, каскадируются ли удаление родительского объекта DataRow в дочерние строки |
ExtendedProperties |
Набор динамических свойств |
RelatedColumns |
Столбцы родительской таблицы, составляющие ограничение |
RelatedTable |
Родительская таблица ограничения |
Table |
Дочерняя таблица ограничения |
UpdateRule |
Управляет каскадированием изменений родительской строки в дочерние строки |
Свойства AcceptRejectRule, DeleteRule и UpdateRule управляют порядком каскадирования изменений родительской строки в дочерние строки. Свойство AcceptRejectRule принимает значение из одноименного перечисления. Значение этого свойства по умолчанию – None: вызов метода AcceptChanges() или RejectChanges() объекта DataRow не сказывается на дочерних строках последнего. Если задать свойству AcceptRejectRule значение Cascade, изменения каскадируются в дочерние строки, определенные объектом ForeignKeyConstraint.
Свойства DeleteRule и UpdateRule функционируют аналогичным образом, но принимают значения из перечисления Rule. Значение этих свойств по умолчанию – Cascade, т. е. изменения родительской строки каскадируются в дочерние строки. Например, при вызове метода Delete() родительского объекта DataRow вы неявно вызываете и метод Delete() его дочерних строк. Точно так же, редактируя значение поля ключа родительского объекта DataRow, вы неявно изменяете содержимое соответствующих полей дочерних строк. Если каскадировать изменения не требуется, задайте свойствам DeleteRule и UpdateRule значение None. Можно также задать им значение SetNull или SetDefault. В первом случае при изменении или удалении содержимого родительской строки соответствующим полям дочерних строк задаются значения NULL, а во втором – их значения по умолчанию.
Обычно нет необходимости создавать объекты класса ForeignKeyConstraint вручную. Они генерируются автоматически при добавлении связи между таблицами. Связь между таблицами представлена объектом класса DataRelation. Большинство свойств DataRelation доступно только для чтения. Задать их значение можно средствами конструкторов объекта DataRelation. В табл. 14 перечислены наиболее часто используемые свойства.
Таблица 14
Свойства класса DataRelation
Имя свойства |
Описание |
ChildColumns |
Свойство возвращает массив, содержащий объекты DataColumn из дочернего объекта DataTable |
ChildKeyConstraint |
Указывает ограничение FOREIGN KEY в дочерней таблице |
ChildTable |
Указывает дочернюю таблицу связи |
DataSet |
Набор данных, в котором находится объект DataRelation |
ExtendedProperties |
Набор динамических свойств |
Nested |
Логическое значение. Указывает, нужно ли преобразовывать дочерние строки в дочерние элементы при записи содержимого DataSet в XML-файл |
ParentColumns |
Родительские столбцы, определяющие отношение |
ParentKeyConstraint |
Указывает ограничение UNIQUE в родительской таблице |
ParentTable |
Указывает родительскую таблицу |
RelationName |
Строка с именем отношения |
При создании объекта DataRelation следует указать его имя, чтобы объект удалось найти в наборе; кроме этого, необходимо указать родительский и дочерний столбцы, на которых будет основано отношение. Чтобы упростить создание связей, класс DataRelation предоставляет отдельные конструкторы, принимающие как объекты DataColumn, так и массивы таких объектов.
Вернемся к нашему примеру. Создадим рассоединенный набор данных и поместим в него таблицы:
var CD_Rent = new DataSet("CD_Rent");
CD_Rent.Tables.Add(Artists);
CD_Rent.Tables.Add(Disks);
Между таблицами Artists и Disks существует связь по внешнему ключу. А именно, таблица Disks является дочерней для таблицы Artists, так как значения поля artist_id – это значения первичного ключа таблицы Artists. Создадим объект DataRelation, описывающий эту связь:
// используем простой конструктор, который устанавливает
// имя отношения, родительский и дочерний столбцы отношения
var Artists_to_Disks = new DataRelation("Artists_to_Disks",
id_artists, artist_id);
// добавим отношение в набор данных
CD_Rent.Relations.Add(Artists_to_Disks);
После выполнения данного кода коллекция Constraints объекта Artists будет содержать два отношения с именами Primary Key и Unique Artist Name (оба – класса UniqueConstraint). Коллекция Constraints объекта Disks будет также содержать два отношения. Одно – с именем Constraint1 типа UniqueConstraint, второе – с именем Artists_to_Disks типа ForeignKeyConstraint. Создание схемы данных можно считать завершенным. При желании можно добавить обработчики событий в наши таблицы.
При помощи метода WriteXmlSchema() созданную схему можно сохранить в XML-файле (правильным расширеним файла является *.xsd, так как схема сохраняется в виде XSD-описания).
Платформа .NET поддерживает типизированные наборы данных. Типизированный набор данных – это класс, который является наследником класса DataSet. В отличие от обычного DataSet, в типизированном наборе для доступа к отдельным компонентам служат свойства. Имя свойства – это имя элемента DataSet (например, имя таблицы); тип свойства – класс-наследник стандартного класса из DataSet.
Создать типизированный DataSet можно несколькими способами. В Visual Studio для этого используется специальный мастер (wizard). В состав .NET Framework SDK входит утилита xsd.exe, формирующая типизированные наборы данных на основании XSD-файла со схемой набора.
Если использовать утилиту xsd.exe для схемы из предыдущих примеров, получим класс CD_Rent, обладающий типизированными свойствами для таблиц, и классы ArtistsDataTable и DisksDataTable, наследуемые от DataTable. Теперь работать с набором данных можно следующим образом. Вместо кода, подобного следующему,
var s = (string)CD_Rent.Tables["Artists"].Rows[0]["name"];
можно писать такой код:
var s = CD_Rent.Artists[0].name;
Обратите внимание:
-
Таблица представлена свойством Artists.
-
Для доступа к определенной строке таблицы используется индексатор, примененный к свойству Artists.
-
Тип строки – ArtistsRow. Это позволяет обратиться к полю строки как к свойству и не выполнять приведение типов.
Подытожим: типизированные DataSet позволяют установить контроль правильности кода во время компиляции. Это способно упростить разработку приложений и сделать использование типов более безопасным.