
- •1. Язык программирования c# 3
- •2. Базовые элементы .Net Framework 67
- •3. ТЕхнология .Net Remoting 144
- •Введение
- •1. Язык программирования c#
- •1.1. Платформа .Net – обзор архитектуры
- •1.2. Язык c# - общие концепции синтаксиса
- •1.3. Система типов языка c#
- •1.4. Преобразования типов
- •1.5. Идентификаторы, ключевые слова и литералы
- •1.6. Объявление переменных, полей и констант
- •1.7. Выражения и операции
- •1.8. Операторы языка c#
- •1.9. Объявление и вызов методов
- •1.10. Массивы в c#
- •1.11. Работа с символами и строками в c#
- •1.12. Синтаксис объявления класса, Поля и методы класса
- •1.13. Свойства и индексаторы
- •1.14. Конструкторы класса и Жизненный цикл объекта
- •1.15. Наследование классов
- •1.16. Перегрузка операЦий
- •1.17. Делегаты
- •1.18. События
- •1.19. Интерфейсы
- •1.20. Структуры и перечисления
- •1.21. Пространства имен
- •1.22. Генерация и обработка исключительных ситуаций
- •1.23. Нововведения в языке c# 2.0
- •1.24. Обобщенные типы (generics)
- •2. Базовые элементы .Net Framework
- •2.1. Метаданные и механизм отражения
- •2.2. Пользовательские и встроенные атрибуты
- •2.3. Пространство имен system.Collections
- •2.4. Работа с файлами и директориями
- •2.5. Использование потоков данных
- •2.6. Сериализация
- •2.7. Сериализация объектов в нестандартном формате
- •2.8. Введение в xml
- •2.9. Работа с xml-документами в .Net framework
- •2.10. МНогопоточное программирование
- •2.11. Синхронизация потоков
- •2.12. Асинхронный вызов методов
- •2.13. Состав и взаимодействие сборок
- •2.14. Конфигурирование сборок
- •3. ТЕхнология .Net Remoting
- •3.1. Домены приложений
- •3.2. Архитектура .Net Remoting
- •3.3. Активация удаленных объектов и их время жизни
- •3.4. Программная настройка Remoting
- •3.5. Удаленные Объекты с клиентской активацией
- •3.6. Настройка Remoting при помощи конфигурационных файлов
- •3.7. Хостинг распределенных приложений
- •3.8. Объекты-сообщения
- •3.9. Пользовательские канальные приемники
- •4.1. Архитектура ado.Net
- •4.2. Учебная база cd Rent
- •4.3. Соединение с базой данных
- •4.4. Выполнение команд и запросов к базе данных
- •4.5. Чтение данных и объект DataReader
- •4.6. Параметризированные запросы
- •4.7. Рассоединенный набор данных
- •4.8. Заполнение Рассоединенного набора данных
- •4.9. Объект класса DataColumn – колонка таблицы
- •4.10. Объекты класса DataRow – строки таблицы
- •4.11. Работа с объектом класса DataTable
- •4.12. DataSet и схема рассоединенного набора данных
- •4.13. Типизированные DataSet
- •4.14. Поиск и фильтрация данных в DataSet
- •4.15. Класс DataView
- •4.16. СиНхронизация набора данных и базы
- •5.1. Архитектура и общие концепции asp.Net
- •5.2. Пример aspx-страницы. Структура страницы
- •5.3. Директивы страницы
- •5.4. Класс System.Web.Ui.Page. События страницы
- •5.5. Серверные элементы управления
- •5.6. Элементы управления Web Controls
- •5.7. Проверочные элементы управления
- •5.8. Списковые элементы управления
- •5.9. Связывание данных
- •5.11. Управление состояниями в web-приложениях
- •5.12. Кэширование
- •5.13. Безопасность в web-приложениях
- •5.14. Создание пользовательских элементов управления
- •Литература
4.7. Рассоединенный набор данных
ADO.NET предоставляет возможность работы с рассоединенным набором данных. Такой набор данных реализуется объектом класса DataSet (далее для краткости – просто DataSet). DataSet не зависит от поставщика данных, он универсален. Это реляционная структура, которая хранится в памяти. DataSet содержит набор таблиц (объектов класса DataTable) и связей между таблицами (объекты класса DataRelation). В свою очередь, отдельная таблица содержит набор столбцов (объекты класса DataColumn), строк (объекты класса DataRow) и ограничений (объекты наследников класса Constraint). Столбцы и ограничения описывают структуру отдельной таблицы, а строки хранят данные таблицы.
Рис. 12. Связи между классами набора данных
Технически, отдельные компоненты DataSet хранятся в специализированных коллекциях. Например, DataSet содержит коллекции Tables и Relations. Таблица имеет коллекции Columns (для колонок), Rows (для строк), Constraints (для ограничений), ParentRelations и ChildRelations (для связей таблицы). Любая подобная коллекция обладает набором сходных свойств и методов. Коллекции имеют перегруженные индексаторы для обращения к элементу по номеру и по имени, методы добавления, поиска и удаления элементов. Методы добавления перегружены и обеспечивают как добавление существующего объекта, так и автоматическое создание соответствующего объекта перед помещением в коллекцию.
Для набора данных DataSet введем понятие схемы данных. Под схемой будем понимать совокупность следующих элементов:
Имена таблиц;
Тип и имя отдельных столбцов таблицы;
Ограничения на столбцы таблицы такие как уникальность, отсутствие пустых значений, первичные и внешние ключи;
Связи между таблицами;
События набора данных и таблицы, которые происходят при работе со строками (аналоги триггеров в базах данных).
Схема данных может быть задана определена способами:
Вручную, путем создания и настройки свойств столбцов, таблиц, связей;
Автоматически, при загрузке данных в набор из базы;
Загрузкой схемы, которая была создана и сохранена ранее в XSD-файле.
Правильно созданная схема обеспечивает контроль целостности данных в приложении перед их загрузкой в базу. К сожалению, при загрузке данных из базы в пустой набор генерируется только часть схемы данных (в схеме будут отсутствовать связи между таблицами и события). Рекомендуется подход, при котором в пустом наборе программно создается полная схема, и только затем в этот набор производится считывание данных.
4.8. Заполнение Рассоединенного набора данных
Каждый поставщик данных содержит класс, описывающий адаптер данных (DataAdapter). В частности, поставщик для SQL Server имеет класс SqlDataAdapter. Адаптер данных является своеобразным мостом между базой данных и DataSet. Он позволяет записывать данные из базы в набор и производит обратную операцию. В принципе, подобные действия вполне осуществимы при помощи команд и ридеров. Использование адаптера данных – более унифицированный подход.
Основными свойствами адаптера являются SelectCommand, InsertCommand, DeleteCommand и UpdateCommand. Это объекты класса Command для выборки данных и обновления базы. При помощи метода адаптера Fill() происходит запись данных из базы в DataSet или таблицу, метод Update() выполняет перенос данных в базу.
В начале работы с адаптером его нужно создать и инициализировать свойства-команды1. Адаптер содержит несколько перегруженных конструкторов. Варианты вызова конструктора адаптера показаны в примере:
// 1. Обычный конструктор без параметров.
// Необходимо заполнить команды вручную
SqlDataAdapter da_1 = new SqlDataAdapter();
// 2. В качестве параметра конструктора – объект-команда
SqlCommand cmd = new SqlCommand("SELECT * FROM Disks");
SqlDataAdapter da_2 = new SqlDataAdapter(cmd);
// 3. Параметры: текст запроса для выборки и объект-соединение
SqlConnection con = new SqlConnection("Server=(local);" +
"Database=CD_Rent;Integrated Security=SSPI");
SqlDataAdapter da_3 = new SqlDataAdapter(
"SELECT * FROM Disks", con);
// 4. Параметры – строка запроса и строка соединения
string s = "SELECT * FROM Disks";
string c = "Server=(local);Database=CD_Rent;Integrated Security=SSPI";
SqlDataAdapter da_4 = new SqlDataAdapter(s, c);
Любой адаптер должен иметь ссылку на соединение с базой данных. Адаптер использует то соединение, которое задано в его объектах-коммандах.
Итак, адаптер создан. Теперь можно использовать его метод Fill() для заполнения некоторого набора данных:
DataSet ds = new DataSet();
// Строго говоря, метод Fill() - функция, возвращающая
// число строк (записей), добавленных в DataSet
da.Fill(ds);
Заметим, что вызов метода Fill() не нарушает состояние соединения с БД. Если соединение было открыто до вызова Fill(), то оно останется открытым и после вызова. Если соединение было не установлено, метод Fill() откроет соединение, произведет выборку данных и закроет соединение. Так же ведут себя и все остальные методы адаптера, работающие с базой.
Поведение адаптера при заполнении DataSet зависит от настроек адаптера и от наличия схемы в объекте DataSet. Пусть при помощи адаптера заполняется пустой DataSet. В этом случае адаптер создаст в DataSet минимальную схему, используя имена и тип столбцов из базы и стандартные имена для таблиц. В результате выполнения следующего кода в ds будет создана одна таблица с именем Table1.
string con_str = ". . .";
string cmd_text = "SELECT * FROM Disks";
SqlDataAdapter da = new SqlDataAdapter(cmd_text, con_str);
DataSet ds = new DataSet();
da.Fill(ds);
Команда выборки данных может быть настроена на получение нескольких таблиц. В следующем примере в пустой DataSet помещаются две таблицы:
string con_str = ". . .";
string cmd_text = "SELECT * FROM Disks;" +
"SELECT * FROM Artists";
SqlDataAdapter da = new SqlDataAdapter(cmd_text, con_str);
DataSet ds = new DataSet();
da.Fill(ds);
В наборе данных ds окажутся две таблицы с именами Table и Table1. Адаптер имеет свойство-коллекцию TableMappings, которое позволяет сопоставить имена таблиц базы и таблиц DataSet:
string cmd_text = "SELECT * FROM Disks;SELECT * FROM Artists";
SqlDataAdapter da = new SqlDataAdapter(cmd_text, con_str);
// Первая таблица будет в наборе данных называться Disks
da.TableMappings.Add("Table", "Disks");
// Вторая таблица будет называться Performers
da.TableMappings.Add("Table1", "Performers");
Любой элемент коллекции TableMappings содержит свойство ColumnMappings, которое осуществляет отображение имен столбцов:
DataTableMapping dtm;
dtm = da.TableMappings.Add("Table1", "Performers");
dtm.ColumnMappings.Add("id", "Performer_id");
dtm.ColumnMappings.Add("name", "Performer_name");
Предположим, что заполняемый набор данных уже обладает некой схемой. Адаптер содержит свойство MissingSchemaAction, значениями которого являются элементы одноименного перечисления. По умолчанию значение свойства – Add. Это означает добавление в схему новых столбцов, если они в ней не описаны. Возможными значениями являются также Ignore (игнорирование столбцов, не известных схеме) и Error (если столбцы не описаны в схеме, генерируется исключение).
Использование свойства MissingSchemaAction демонстрирует следующий код:
SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM Disks",
con);
// Пустой набор данных без схемы
DataSet ds = new DataSet();
// После заполнения в наборе будет схема,
// полученная по таблице базы данных
da.Fill(ds);
// сейчас будем "запихивать" в непустой набор новую таблицу
da = new SqlDataAdapter("SELECT * FROM Artists", con_str);
// 1 вариант. В таблице Table будет 13 записей
// (8 из Disks, 5 из Artists) и колонки: id, title, artist_id,
// release_year (из Disks), name (из Artists)
da.MissingSchemaAction = MissingSchemaAction.Add;
da.Fill(ds);
// 2 вариант. Получим таблицу из 13 записей, но с 4 столбцами
// (без столбца name)
da.MissingSchemaAction = MissingSchemaAction.Ignore;
da.Fill(ds);
// 3 вариант. Попытка заполнения вызовет исключение!
da.MissingSchemaAction = MissingSchemaAction.Error;
da.Fill(ds);
Если многократно выполненить метод Fill() с идентичной командой и набором данных, то в случае отсутствия в таблице DataSet первичного ключа, в таблицу заносится дублирующая информация. Если в таблице задан первичный ключ, произойдет обновление данных таблицы:
SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM Disks", con);
DataSet ds = new DataSet();
da.Fill(ds);
da.Fill(ds);
В результате выполнения этого кода в DataSet будет создана одна таблица, которая содержит два одинаковых множества записей. Важно понимать этот факт, так как таблица в наборе данных может содержать ограничения (первичный ключ, уникальные значения), которые будут нарушены.
Обсудим дополнительные возможности адаптера, связанные с заполнением DataSet. Существует перегруженный вариант метода Fill(), который возвращает диапазон записей:
// Первый параметр – целевой набор DataSet,
// второй – номер стартовой записи (нумерация с нуля),
// третий – количество записей,
// четвертый – имя таблицы в целевом наборе DataSet
da.Fill(ds, 3, 10, "Disks");
Адаптер имеет метод FillSchema(), который переносит схему таблиц запроса в DataSet. Метод FillSchema() получает из базы имена и типы всех задействованных в запросе столбцов. Кроме этого, данный метод получает сведения о допустимости для столбца значений Null и задает значение свойства AllowDBNull создаваемых им объектов DataColumn. Метод FillSchema() также пытается определить на объекте DataTable первичный ключ.
Метод FillSchema() принимает как параметр объект DataSet, или DataSet и имя таблицы, или объект DataTable. Однако у FillSchema() имеется дополнительный параметр. Он позволяет указать, нужно ли применить к информации схемы параметры набора TableMappings. Можно указать любое значение из перечисления SchemaType – Source или Mapped. При значении Mapped адаптер обратится к содержимому набора TableMappings точно так же, как сопоставляет столбцы при вызове метода Fill(). Вот пример вызова FillSchema():
SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM Disks", con);
DataSet ds = new DataSet();
da.FillSchema(ds, SchemaType.Source, "Disks");
Адаптер данных имеет три события:
FillError – событие наступает, если при заполнении DataSet или DataTable адаптер столкнулся с какой-либо ошибкой;
RowUpdating – событие наступает перед передачей измененной строки в базу данных;
RowUpdated – событие наступает после передачи измененной записи в базу данных.