2. Работа с наборами типизированных данных

Наборы данных «ведущий-детализация»

вы можете создавать между ними

Как уже упоминалось, несколько таблиц в одном наборе данных — это нечто большее, чем просто коллекция таблиц. Вы можете создавать между ними родительско-дочерние отношения и добавлять ограничения первичных, уникальных и внешних ключей. Наборы данных, содержащие родительско-дочерние отношения такого рода, часто называют наборами данных «ведущий-детализация» , поскольку они обычно используются в таких сценариях привязки данных, где выбор строки в родительской таблице приводит к отображению всех ассоциированных дочерних строк. Метод в листинге Г.5 Снова извлекает обе таблицы Customers и Orders, используя на этот раз два отдельных адаптера данных, и создает между ними родительско-дочернее, или отношение ведущий-детализация. Установка для свойства MissedSchemaAction Адаптера данных значения AddWithKey Предписывает адаптеру при исполнении запроса дополнительно определить столбцы, образующие первичный ключ, и если таковые найдутся, он создаст соответствующие уникальные ограничения в результирующей таблице. Для создания отношения вам нужно специфицировать, какой столбец в родительской таблице является первичным ключом, а также соответствующий столбец внешнего ключа в дочерней таблице. Проще всего это сделать с использованием объектов DataColumn, Представляющих эти столбцы. При этом также неявно специфицируется, какая таблица является родительской, а какая дочерней, поскольку столбец может принадлежать лишь к одной таблице, альтернативно можно было бы специфицировать родительскую и дочернюю таблицы и столбцы по именам. Добавление отношения данных создает также соответствующее ограничение внешнего ключа, обеспечивающее выполнение правила, что если строка в дочерней таблице ссылается на строку в родительской таблице, то такая строка должна существовать. Обратите внимание, что код устанавливает также свойство Nested Отношения данных в True. Это влияет только на XML, если вы сохраняете набор данных вызовом WriteXml Или получаете его строковое представление при помощи GetXml. При этом дочерние строки будут выводиться как вложенные элементы под элементом родительской строки.

Образец приложения SimpleSpread

приложение поддерживает даже выбор прямоугольной

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

Чтобы написать код приложения, мне пришлось проделать кое-что сверх того, что обычно присутствует в стандартных применениях DataGridView. Как уже упоминалось, я хотел обеспечить поддержку модели выбора, характерной для электронных таблиц, где вы можете выбирать отдельные ячейки, но щелчок на заголовке строки или столбца выбирает соответственно всю строку или весь столбец. Для этого я установил SelectionMode Сетки равным RowHeaderSelect, Выключил сортировку для всех столбцов в процессе их создания и добавления к сетке и затем обработал событие ColumnHeaderMouseClick, Чтобы вручную выбирать все ячейки столбца при щелчке на его заголовке. Здесь я просто добавляю программно к сетке несколько столбцов и строк, устанавливаю в заголовках столбцов буквы алфавита и выключаю сортировку каждого столбца, устанавливая в свойстве sortMode значение NotSortable. Если бы вам нужно было поддерживать очень большие электронные таблицы, потребовалось бы, возможно, хранить в памяти разреженный массив и отображать ячейки только при необходимости, чтобы избежать расходов, связанных с хранением большого числа ячеек, их содержимого и выбора, если сетка будет заполнена скудно. Для получения номеров строк, показываемых в их заголовках, я обрабатываю событие RowAdded И в обработчике устанавливаю значение заголовочной ячейки: Еще один режим выбора, который вам, возможно, захочется поддерживать, состоит в реализации горячих ячеек, когда выбор ячеек изменяется просто при движении мыши по сетке, без щелчков кнопкой. Для этого необходимо обрабатывать события CellMouseEnter И CellMouseLeave, Соответственно выбирая и отменяя выбор ячейки под курсором мыши в обработчиках этих событий.

Конструктор построителя команд

b следующий код демонстрирует применение

Конструктор построителя команд берет адаптер данных и устанавливает его свойства InsertCommand, DeleteCommand И UpdateCommand Подходящими запросами, исходя из столбцов, которые заполняет SelectCommand. Следующий код демонстрирует применение построителя команд для генерации запросов обновления и использует их для сброса изменений из набора данных в базу данных. Набор данных и адаптер данных в этом коде являются элементами охватывающего класса, поскольку они используются в другом методе для заполнения набора данных посредством запроса SELECT. Для простых случаев применение SqlCommandBuilder Очень удобно. Однако он имеет ряд недостатков, и в коде программной продукции лучше отказаться от него в пользу написания своих собственных запросов SQL или использования сохраняемых процедур. Запросы, генерируемые построителем команд, всегда сравнивают значения всех столбцов, которые были извлечены запросом SELECT, чтобы обнаружить нарушения оптимистической конкуренции, что описывается в следующем разделе. Однако обычно это будет не лучшим подходом. Это также означает, что запросы несут на себе гораздо больше данных для каждого обновления, чем это может быть необходимо. Для систем большего масштаба лучшим подходом по соображениям безопасности, сопровождаемости и производительности будет работа с сохраняемыми процедурами. Более подробно см. далее в разделе «Обновление при помощи наборов данных и сохраняемых процедур». Для решения этой проблемы в ADO. NET обычным подходом является применение принципа, называемого оптимистической конкуренцией, которая по существу означает априорное предположение, что между моментом, когда вы извлекаете строку данных, и моментом, когда вы вновь обращаетесь к источнику данных, чтобы ее обновить, никакой другой запрос или клиент не модифицирует эту строку. Если это предположение нарушается, что в системе с высокой степенью параллелизма может происходить весьма часто, ваш код должен быть способен как-то с этим справиться.

Таблица Orders

перетащите таблицу на свободное пространство

Захватите таблицу Orders, показанную в качестве дочернего компонента таблицы Customers в окне Data Sources. Перетащите таблицу на свободное пространство в нижней части формы. В результате на форму будет добавлен еще один элемент управления DataGridView для таблицы Orders, а также дополнительные экземпляры BindingSource и OrdersTableAdapter. Вторая сетка будет привязана ко второму источнику привязки, а свойство Datasource второго источника будет установлено на первый источник привязки, который был привязан к таблице Customers. Свойство DataMember второго источника привязки будет установлено на Orders, имя дочерней коллекции в первом источнике привязки. В обработчик события формы Load будет добавлена строка кода, заполняющая, наряду с таблицей Customers, таблицу Orders. Конечным результатом простого перетаскивания на форму двух взаимосвязанных элементов из иерархии объектов явилось то, что конструктор написал за вас код для привязки «ведущий-детализация». То же самое произошло бы, если вместо таблиц Customers и Orders из типизированного набора данных вы использовали в качестве ведущего коллекцию специальных объектов, а в качестве детализации — свойство для дочерней коллекции, экспонируемое объектами родительской коллекции. Это опять же неплохой результат! До появления Visual Studio 2005 вам пришлось бы вручную написать немало запутанного кода, чтобы заставить подобную вещь работать. Теперь все за вас делает конструктор. В последних нескольких главах вы познакомились с основами механизмов привязки данных в Windows Forms и сопутствующими деталями. Вы увидели ряд примеров привязки наборов данных к сеткам, комбинированным спискам, текстовым полям и элементам управления PictureBox. Вы научились использовать источники привязки, чтобы избежать сцепления привязанных элементов управления с конкретными источниками данных, что дает ряд преимуществ в плане сопровождения и развития приложений. Вы увидели, как вручную устанавливаются привязки для отдельных элементов управления, и как посредством каскадирования источников организуются привязки для отношений «ведущий-детализация».

Метод Apply Sort Core

b метод b applysortlnternal b

Метод ApplySortCore Создает объект SortComparer Для специфицированных свойства и направления сортировки и передает его вспомогательному методу ApplySortlnternal. Метод ApplySortlnternal Проверяет несортированную коллекцию, была ли она заполнена, и если нет, добавляет в нее все текущие объекты в том порядке, как они расположены в коллекции на момент вызова ApplySort. Проверка заполнения списка необходима на тот случай, если список уже сортировался несколько раз подряд. Затем метод приводит коллекцию Items, Унаследованную вашим классом, к ссылке типа List<T> И вызывает ее метод Sort, Передавая объект SortComparer. В результате элементы коллекции, поддерживаемой базовым классом, сортируются в соответствии с критерием, переданным классу SortComparer, И логикой сравнения, реализованной в этом классе. Наконец, метод ApplySortlnternal Устанавливает флаг, показывающий, что коллекция сортирована, и запускает событие через вызов метода OnListChanged, Показывая, что список изменился. Наиболее подходит тип изменения Reset, Поскольку в принципе каждый элемент коллекции мог переместиться. Метод RemoveSortCore Очищает текущую коллекцию и заново заполняет ее значениями, сохраняемыми в списке исходной коллекции. Затем он очищает этот список и соответствующим образом устанавливает используемые алгоритмом переменные, показывая, что в данный момент никакой сортировки не применяется. Чтобы нельзя было изменять элементы списка, когда коллекция сортирована, вам нужно переопределить реализации соответствующих элементов IBindingList: Для реализации интерфейса IRaiseitemChangedEvents Вы должны для каждого свойства объектов, входящих в ваши коллекцию, предоставить дескриптору свойства возвратно-вызываемый делегат. Ваш возвратный вызов будет активироваться всякий раз, когда значение свойства устанавливается через дескриптор. Довольно странно, что это не экспонируется как событие, на которое можно было бы явным образом подписаться; вы должны вызывать методы AddValueChanged И RemoveValueChanged, Передавая делегат воз — вратно-вызываемого метода.