Книги, научные публикации Pages:     | 1 |   ...   | 3 | 4 | 5 | 6 | 7 |   ...   | 11 |

David Sceppa Microsoft' ADO.NET Microsoft Press Дэвид Сеппа Microsoft ADO.NET ...

-- [ Страница 5 ] --

Visual Basic.NET tblCustomers.DefaultView.RowFilter = "Country = 'Spain'" grldCustomers.DataSource = tblCustomers Visual C#.NET tblCustomers,DefaultView.RowFilter = "Country = 'Spain'";

gridCustomers.DataSource = tblCustomers;

Подробнее об объекте DataView Ч в главе 8.

Свойство DesignMode Свойство DesignMode возвращает логическое значение, указывающее, находится ли объект DataTable в режиме проектирования. Это свойство полезно при напи сании кода в нестандартном элементе управления. Если объект DataTable исполь зуется в компоненте в период разработки, свойство DesignMode возвращает True.

В противном случае оно возвращает False.

Объект DataSet также предоставляет свойство DesignMode. Данное свойство доступно только для чтения.

Свойство ExtendedProperties Свойство ExtendedProperties объекта DataTable возвращает объект PropertyCollection, предназначенный для хранения разнообразных объектов.

Объекты DataSet, DataGolumn, DataRelation и Constraint также предоставляют свойство ExtendedProperties.

Подробнее о данном свойстве, включая пример кода, Ч в разделе этой главы, посвященном свойству ExtendedProperties объекта DataSet, Свойство HasErrors Свойство HasErrors возвращает логическое значение, указывающее, содержат ли объекты DataRow из состава DataTable ошибки. Если вы передаете в БД пакеты изменений и задали свойству ContinueUpdateOnError объектов DataAdapter значение True, проверяйте по завершении передачи значение свойства HasErrors объектов ГЛАВА 6 Работа с объектами DataSet DataTabie. Таким образом вы узнаете, все ли операции передачи изменений завер шились успешно.

Объекты DataSet и DataRoiv также предоставляют свойство HasErrors.

Подробнее о действиях в случае ошибок при передаче изменений в БД Ч в главе 11.

Свойство Locale Свойство Locale определяет, как ADO.NET сравнивает строки в объекте DataTabie, Объект DataSet также предоставляет свойство Locale.

Подробнее о данном свойстве, включая пример кода. Ч в разделе этой главы, посвященном свойству Locale объекта DataSet, Свойство MinimumCapacity Если вам известно, сколько примерно записей будет в объекте DataTabie, можно повысить производительность кода, задав, прежде чем заполнить DataTabie резуль татами запроса, значение свойства MinimumCapacity.

По умолчанию оно равно 50, т. е. ADO.NET резервирует объем памяти, доста точный для того, чтобы объект DataTabie мог содержать 50 записей. Если вы пред ставляете себе примерное число записей в объекте DataTabie, то для повышения производительности кода следует назначить свойству MinimumCapacity более точное значение. Задавая при работе с объектами DataTabie данному свойству маленькое значение, вы также снижаете требования приложения к памяти.

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

Свойства Namespace и Prefix Свойства Namespace и Prefix позволяют указать для объекта DataTabie префикс и пространство имен XML. ADO.NET использует значения этих свойств при записи содержимого DataTabie в XML-файл, а также при загрузке XML-документа в объект DataTabie.

Объекты DataSet и DataRoiv также обладают свойствами Namespace и Prefix, Подробнее о пространствах имен XML Ч в документации MSDN.

Свойство PrimaryKey Свойство PrimaryKev содержит массив объектов DataColumn. составляющих пер вичный ключ объекта DataTabie.

У первичного ключа Ч два назначения. Во-первых, он играет роль ограниче ния на уникальность. Наличие двух объектов DataRoiv с одинаковыми значения ми полей первичного ключа невозможно. Предположим, у вас есть объект DataTabie с информацией о клиентах и вы определили первичный ключ на основе объекта DataColumn CustomerlD. Если при добавлении в объект DataTabie новой записи окажется, что у нее и одной из имеющихся записей одинаковые значения полей первичного ключа, ADO.NET сгенерирует исключение.

Кроме того, значения первичного ключа стоит использовать для поиска объек тов DataRow в объекте DataTabie при помощи метода Find набора Rows объекта DataTabie. Подробнее об этом Ч в главе 8.

232 Часть III Автономная работа с данными: объект DataSet модели ADO.NET Следующий фрагмент кода задает значение свойства РптагуКеу объекта Data Table.

Visual Basic.NET Dim tbl As New DataTableC'Customers") tbl.Columns.Add("CustomerID", GetTypefString)) tbl.Columns.Add("CompanyName", GetTypef String)) tbl.PrimaryKey = New DataColumnC) {tbl.Columns("CustomerID")} Visual C#.NET DataTable tbl = new DataTable("Customers");

tbl.Columns.AddC'CustomerlD", typeof(string));

tbl.Columns.Add("CompanyName", typeof(st ring));

tbl.PrimaryKey = new DataColumn[] {tbl.Columns["CustomerID"]};

Свойство Rows Свойство Rows возвращает объект DataRowCollection, содержащий объекты DataRow из состава DataTable. С помощью данного свойства удается добавлять объекты DataRou- в DataTable, а также обращаться к уже имеющимся объектам.

Для поиска объектов DataRow в объекте DataTable по порядковым номерам предназначен объект DataRowCollection. Подробнее о поиске объектов DataRow по значениям первичного ключа и другим критериям Ч в главе 8.

Свойство TableName Свойство TableName содержит имя объекта DataTable. Значение этого свойства задается в конструкторе DataTable.

Когда вы сохраняете содержимое DataTable в XML-документ, свойство TableName используется в качестве имени тега для всех записей из состава DataTable.

Методы объекта DataTable В табл. 6-9 перечислены наиболее часто используемые методы объекта DataTable.

Таблица 6-9. Методы объекта DataTable Описание Метод Подтверждает все отложенные изменения в объекте DataTable AcceptChanges Вызывается конструкторами Visual Studio.NET перед добавлением в Beginlnit объект DataTable сведений схемы BeginLoadData Отключает все ограничения при загрузке данных Удаляет из DataTable все объекты DataRow Clear Создает новый объект DataTable с идентичной схемой, но без объектов Clone DataRow Возвращает значение агрегатного выражения, основываясь на содержи Compute мом объекта DataTable Создает новый объект DataTable с такой же схемой и объектами Сору DataRow ГЛАВА 6 Работа с объектами DataSet Таблица 6-9. (продолжение) Метод Описание Вызывается конструкторами Visual Studio.NET после добавления в Endlnit объект DataTable сведений схемы Активирует ограничения после загрузки данных EndLoadData Возвращает новый объект DataTable с идентичной схемой, содержащий GetCbanges измененные записи оригинального объекта DataTable Возвращает массив объектов DataRow, содержащих ошибки GetErrors Импортирует объект DataRow в объект DataTable ImportRow Добавляет в объект DataTable новый объект DataRow, основываясь на LoadDataRow содержимом массива Возвращает для объекта DataTable новый объект DataRow NewRow Отменяет все отложенные изменения в объекте DataTable RejectChanges Восстанавливает оригинальное состояние объекта DataTable, в котором Reset он находился до инициализации Возвращает массив объектов DataRow на основании заданного крите Select рия поиска Методы AcceptChanges и RejectChanges Методы AcceptChanges и RejectChanges позволяют соответственно подтверждать и отбрасывать все отложенные изменения в объекте -DataTable.

Объекты DataSet и DataRow также обладают методами AcceptChanges и Reject Changes. Подробнее об этих методах Ч в разделе "Методы объекта DataSet этой главы.

Методы Beginlnit и Endlnit Методы Beginlnit и Endlnit вызываются конструкторами, поэтому их не надо ис пользовать непосредственно в коде.

Объект DataSet также обладает методами Beginlnit и Endlnit. Подробнее об этих методах Ч в разделе Методы объекта DataSet" этой главы, Методы BeginLoadData и EndLoadData Если вы добавляете в объект DataTable группу объектов DataRow, для повышения производительности кода следует применить методы BeginLoadData и EndLoadData.

При вызове метода BeginLoadData отключаются определенные на объекте Data Table ограничения. Активировать их можно средствами метода EndLoadData. Если в объекте DataTable есть записи, нарушающие какие-либо ограничения, при вы зове метода EndLoadData ADO.NET сгенерирует исключение ConstraintException.

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

Метод Clear Метод Clear позволяет удалить из объекта DataTable все объекты DataRow. Вызвать его быстрее, чем освободить оригинальный и создать новый объект DataTable с идентичной структурой.

Объект DataSet также предоставляет метод Clear.

9- 234 Часть III Автономная работа с данными: объект DataSet модели ADO.NET Методы Clone и Сору С помощью метода Сору создают новый объект DataTable, по структуре и содер жимому аналогичный оригинальному. Чтобы создать объект DataTabte с идентич ной структурой, но без записей, воспользуйтесь методом Clone, Объект DataSet также обладает методами Clone и Сору.

Метод Compute Метод Compute позволяет выполнять агрегатные запросы к отдельным столбцам объекта DataTable на основе заданных критериев поиска.

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

Visual Basic.NET Dim strSQL, strConn As String strConn = "Provider=SQLOLEDB;

Data Source=(local)\NetSDK;

" & _ "Initial Catalog=Northwind;

Trusted_Connection=Yes;

" strSQL = "SELECT OrderlD, ProductID, Quantity FROM [Order Details]" Dim da As New 01eDb.01eDbDataAdapter

strConn = "Provider=SQLQLEDB;

Data Source=(local)\\NetSDK;

" + "Initial Catalog=Northwind;

Trusted_Connection=Yes;

";

StrSQL = "SELECT OrderlD, ProductID, Quantity FROM [Order Details]";

OleDbDataAdapter da = new 01eDbDataAdapter(strSQL, strConn);

DataTable tbl = new DataTableC'Order Details");

da.Fill(tbl);

int intNumChaiOrders;

Int64 intNumChaiUnlts;

intNumChaiOrders = (int) tbl.Compute("COUNT(OrderID)", "ProductID = 1");

intNunChaiUnits = (Int64) tbl.Compute("SUM(Quantity>", "ProductID = 1");

Console.WriteLine{"# of orders that include chai: " + IntNumChaiOrders};

ГЛАВА 6 Работа с объектами DataSet Console.WrlteLine{"Total number of units ordered: " + intNumChaiUnits);

Метод Compute не позволяет вычислять агрегатные значения на основе несколь ких столбцов, например SUM(Quantity * UnitPrice). Тем не менее с помощью ос.-ю ванного на выражении столбца удается вычислить произведение указанных см ол бцов и использовать этот основанный на выражении столбец в мемоде Count:

SUM(ItemTotal).

Метод Compute возвращает результаты с использованием универсального типа данных Object. При вычислении значения средствами метода Compute вас, веро ятно, удивит тип данных, применяемый этим методом для хранения данных. Так.

тип данных столбца Quantity Ч 16-разрядное целое число, однако при вызове метод Compute возвращает 64-разрядное целое число.

Если вы не знаете, какой тип данных выбрать для хранения результатов мето да Compute, воспользуйтесь таким кодом:

Visual Basic.NET Dim objRetVal As Object = tbl,Compute("SUM(Quantity)", "ProductlD = 1") Console.WriteLine(objRetVal.GetType.ToString) Visual C#.NET object objRetVal = tbl.Compute("SUM(Quantity)", "ProductlD = 1"};

Console.WriteLine(objRetVal.GetType().ToString()};

Метод GetChanges Метод DataTable.GetChanges возвращает новый объект DataTable со смруктурой оригинального объекта DataTable, содержащий все записи оригинального объек та DataTable с отложенными изменениями. Подробнее об этом методе Ч в главе 11.

Объект DataSet также предоставляет метод GetChanges.

Метод GetErrors Метод GetErrors позволяет в случае нарушения ограничений или неудачной пере дачи обновлений в БД обращаться к объектам DataRoiv, содержащим ошибки, Данный метод возвращает массив объектов DataRoiv, Методы ImportRow, LoadDataRow и NewRow Метод ImportRow принимает объект DataRoiv и добавляет его в объект DataTable, Метод LoadDataRow принимает в качестве первого аргумента массив, элемен ты которого соответствуют элементам набора Columns объекта DataTable. Второй аргумент Ч это логическое значение, управляющее свойством RowState нового объекта DataRow, Чтобы задать свойству RowState значение Added, передайте в качестве второго аргумента False;

если необходимо задать значение Unmodified, передайте True, Метод LoadDataRow также возвращает только что созданный объект DataRow.

Часть III Автономная работа с данными: объект DataSet модели ADO.NET Метод NewRow возвращает для объекта DataTable новый объект DataRow, но не добавляет его в набор Rows указанного объекта. Добавление в набор следует осу ществить вручную, предварительно заполнив необходимые поля объекта DataRow.

Так каким же из этих трех методов воспользоваться?

Х Для импорта записи из стороннего объекта DataTable используйте метод Import Row.

Х Для одновременного импорта нескольких записей, например на основе содер жимого файла, применяйте метод LoadDataRow. Это позволит вам писать меньше кода, чем при работе с методом NewRow.

Х Во всех остальных случаях вызывайте метод NewRow.

Метод Reset Метод Reset восстанавливает оригинальное состояние объекта DataTable. в кото ром он находился до инициализации. Чтобы отбросить имеющийся и начать ра боту с новым объектом DataTable. используйте метод Reset, а не создавайте новый экземпляр DataTable.

Метод Select Метод Select позволяет искать записи в объекте DataTable на основе разнообраз ных критериев поиска. Он возвращает массив объектов DataRow, удоачетворяю щих заданным критериям.

Подробнее о методе Select Ч в главе 8.

События объекта DataTable В табл. 6-10 перечислены наиболее часто используемые события объекта DataTable.

Таблица 6-10. События объекта DataTable Событие Описание Наступает после изменения содержимого поля ColumnCbanged Наступает перед изменением содержимого поля ColumnCbanging Наступает после изменения содержимого записи RowCbanged Наступает перед изменением содержимого записи RowChanging Наступает после удаления записи RowDeleted Наступает перед удалением записи RowDeleting События ColumnChanged и ColumnChanging События ColumnCbanged и ColumnCbanging наступают каждый раз, когда изменя ется содержимое одного из полей записи. Они позволяют осуществлять провер ку данных, активировать и деактивировать элементы управления, и т.д.

У данных событий есть аргумент типа DataColumnCbangeAveniArgs, обладаю щий свойствами Row и Column, которые позволяют определить, какие именно поле и запись изменены.

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

ГЛАВА 6 Работа с объектами DataSet События RowChanged и Row/Changing События RowChanged и RowChanging наступают каждый раз, когда изменяется содержимое объекта DataRow или значение свойства RowState этого объекта.

Чтобы определить, почему наступило событие, достаточно просмотреть зна чение свойства Action аргумента DataColumnChangeAventArgs этого события. Свой ство Row указанного аргумента позволяет обращаться к изменяемой записи.

События RowDeteted и RowDeleting События RowDeleted и RowDeleting предоставляют такие же аргументы и свойства, как события RowChanged и RowChanging. Единственное отличие в том, что данные события наступают при удалении записи из объекта DataTable.

Свойства объекта DataColumn В табл. 6-11 перечислены наиболее часто используемые свойства объекта Data Column.

Свойства объекта DataColumn Таблица 6-11.

Описание Свойство Тип данных Определяет, допустимы ли в столбце значения AllowDBNull Boolean Null Определяет, генерирует.ли ADO.NET для столб!

Autolncrement Boolean новые значения автоинкремента Определяет начальное значение автоинкремента AutoIncrementSeed Integer Определяет шаг автоинкремента AutoIncrementStep Integer Содержит заголовок столбца, отображаемый в Caption String связанных элементах управления DataGrid Определяет, как ADO.NET запишет содержимое ColumnMapping MappingType столбца в XML-документ Содержит имя объекта DataColumn ColumnName String Определяет тип данных столбца DataType Type Определяет значение по умолчанию, используе Object DefauttValue мое ADO.NET при заполнении этого поля в но вых записях Определяет, как ADO.NET генерирует значения String Expression полей, основанных на выражении Содержит набор динамических свойств и значений PropertyCollection ExtendedProperties Задает максимально допустимую для столбца Integer MaxLengtb длину строки Содержит пространство имен, которое ADO.NEiT Namespace String использует при записи содержимого DataSet в XML-файл или при загрузке XML-данных в объект DataSet Возвращает порядковый номер объекта Integer Ordinal DataColumn в наборе Columns объекта DataTable Содержит префикс пространства имен, использу Prefix String емого ADO.NET при записи содержимого DataSet в XML-файл или при загрузке XML-данных в объект DataSet см. след. стр.

238 Часть III Автономная работа с данными: объект DataSet модели ADO.NET Таблица 6-11. (продолжение) Свойство Тип данных Описание Указывает, доступно ли содержимое столбца Readonly Boolean только для чтения Возвращает объект DataTable, в состав которого Table DataTable входит DataColumn Определяет, должны ли быть значения данного Unique Boolean столбца уникальными в пределах объекта DataTable Свойство AllowDBNull Свойство AllowDBNull определяет, допустимы ли в объекте DataColumn значения Null. Значение по умолчанию этого свойства новых объектов DataColumn Ч True.

При создании новых объектов DataColumn с помощью метода DataAdapterFill свойству AllowDBNull не назначается значение True, даже если соответствующий столбец БД не допускает значений Null. Когда вы вызываете метод Fill, объект DataAdapter не выбирает из БД информацию схемы. Чтобы выбрать эту инфор мацию и применить ее к новым столбцам объекта DataTable, воспользуйтесь ме тодом FillSchema, Свойства Autolncrement, AutolncrementSeed и AutolncrementStep Эти свойства определяют, генерирует ли ADO.NET для столбца значения автоин кремента.

Если задать свойству Autolncrement значение True, ADO.NET станет генериро вать для столбца значения автоинкремента. Значение данного свойства по умол чанию Ч False. Как и в случае со свойством AllowDBNull, чтобы задать свойству Autolncrement объектов DataColumn, соответствующих столбцам БД с автоинкре ментом, значение True, используйте метод DataAdapterfillScbema.

Если значение свойства Autolncrement Ч True, ADO.NET генерирует новые зна чения автоинкремента на основе значений свойств AutolncrementSeed и Autolncre mentStep. Значение свойства AutolncrementSeed по умолчанию Ч 0. а свойства AutolncrementStep Ч 1. Как я уже объяснял, если вы генерируете новые значения автоинкремента средствами ADO.NET, указанным свойствам следует задать значение -1. Вызов метода DataAdapterfillScbema не влияет на значения свойств Autolncre mentSeed и AutolncrementStep.

В одном из предыдущих разделов рассказывалось, почему не стоит применять средства автоинкремента ADO.NET для генерации новых значений для БД. Пусть реальные значения автоинкремента генерирует БД, a ADO.NET генерирует значе ния-метки для объектов DataSet.

Свойство Caption Свойство Caption определяет заголовок столбца, отображаемый при выводе со держимого объекта DataTable в связанном элементе управления DataGnd. По умол чанию Caption возвращает то же значение, что и свойство ColumnName. Тем не менее, если задать свойству Caption какое-либо значение, будет выводиться уже оно, а не значение свойства ColumnName.

Работа с объектами DataSet ГЛАВА Свойство ColumnMapping Свойство ColumnMapping определяет, как ADO.NET сохраняет содержимое столб ца, возвращая данные объекта DataSet в XML-формате.

Данное свойство принимает значения из перечисления MappingType, относя щегося к пространству имен SystemData. Значение свойства ColumnMapping по умолчанию Ч Element, т. е. значения отдельных полей объекта DataRoir представ лены тегами элементов. Свойству ColumnMapping можно также задать значение Attribute, Hidden или SimpleContent.

Далее показано, чем отличается представление содержимого DataSet при по мощи элементов и атрибутов.

Используется Column.ColumnMapping = MappingType.Element ABCuE New Customs r New Contact 425 555-1212 Используется Column.ColumnMapping = MappingType.Attribute Подробнее о функциях ADO.NET для работы с XML-данными Ч в главе 12.

Свойство ColumnName Свойство ColumnName содержит имя объекта DataColumn. Его значение задается в конструкторе DataColumn.

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

Так, в SQL Server предусмотрены разные типы данных для строк переменной и фиксированной длины, а также для строк в однобайтовом и двубайтовом пред ставлении. ADO обрабатывает все их по-разному. Если же говорить об объекте DataColumn модели ADO.NET, строка Ч это просто строка.

Свойство DataType принимает значение типа Туре. Ниже показано, как напря мую задать значение свойства DataType объекта DataColumn и использовать ме тод DataColumnCollectionAdd:

Автономная работа с данными: объект DataSet модели ADO.NET 240 Часть III Visual Basic.NET Dim col As New DataColumnC'NewColumn") col.DataType = GetTypeCDecimal) Dim tbl As New DataTable("Products") tbl.Columns.AddC'ProductlD", GetType(Integer)) tbl.Columns.AddC'ProductName", GetType(String)) tbl.Columns.Add("UnitPrice", GetType(Decimal)} Visual C#.NET DataColumn col = new DataColumnC'NewColumn");

col.DataType = typeof(Decimal);

DataTable tbl = new DataTableC'Products");

tbl.Columns.AddC'ProductlD", typeof(int));

tbl.Columns.Add("ProductName", typeof(string));

tbl.Columns.AddC'UnitPrice", typeof(Decimal));

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

SQL Server определяет для столбцов таблиц значения по умолчанию. Тем не менее свойство DefaultValue объекта DataColumn ведет себя несколько иначе, чем ана логичная функция SQL Server.

Определяя значение по умолчанию столбца SQL Server, вы указываете строку с выражением. SQL Server оценивает это выражение каждый раз, когда задает полю значение по умолчанию.

Свойство DefaultValue принимает статичное значение с универсальным типом данных Object. Предположим, вы имеете дело с датами размещения заказов. Мож но посредством свойства DefultValue определить значение столбца OrderDate по умолчанию. Однако оно статично. Завтра значение поля OrderDate будет таким же, как и сегодня.

Свойство DefaultValue удобно, но не предоставляет такой гибкости, как соот ветствующие средства SQL Server.

Свойство Expression Свойству Expression можно задать выражение, которое ADO.NET будет просмат ривать каждый раз при обращении к содержимому столбца. Если изменить зна чение данного свойства по умолчанию (пустая строка), свойству ReadOnly объек та DataColumn автоматически задается значение True.

Ниже показано, как задать значение свойства Expression объекта DataColumn, чтобы получить произведение значений столбцов Quantity и UnitPrice объекта DataTable. Кроме того, этот код добавляет в DataTable новый объект DataRow и выводит содержимое основанного на выражении столбца в окне консоли.

ГЛАВА 6 Работа с объектами DaiaSet Visual Basic.NET Dim tbl As New DataTable("Order Details") tbl. Columns. Add( "OrderlD", GetType( Integer) ) tbl. Columns, Add("ProductID", GetType(Integer)) tbl. Columns. Add( "Quantity", GetType(Integer)} tbl. Columns. Add{"UnitPrlce", GetType( Decimal)) Dim col As New DataColumn("ItemTotal", GetType(Decimal)) col. Expression = "Quantity * UnitPrice" tbl. Columns. Add(col) Dim row As OataRow = tbl.NewRow() row("OrderID") = row("ProductID") = row("Quantity") = row( "UnitPrice") = tbl. Rows. Add(row) Console. WriteLine(row("ItemTotal")) Visual C#.NET DataTable tbl = new DataTable( "Order Details");

tbl. Columns. Add( "OrderlD", typeof(int));

tbl. Columns. AddC'ProductID", typeof(int));

tbl. Columns. Add( "Quantity", typeof(int));

tbl. Columns. Add( "UnitPrice", typeof (Decimal));

DataColumn col = new DataColumn("ItemTotal", typeof(Decimal));

col. Expression = "Quantity * UnitPrice" tbl. Columns. Add(col);

DataRow row = tbl.NewRow();

row["OrderID"] = 1;

row["ProductID"] = 1;

row["Quantity"] = 6;

row["UnitPrice"] = 18;

tbl. Rows. Add( row);

Console. WriteLine( row["ItemTotal " ] ) ;

Подробнее о том, как ссылаться в основанных на выражении столбцах на содержимое других объектов DataTable Ч в главе 7. Подробнее о допустимых в свойстве Expression функциях Ч в документации MSDN.

Свойство ExtendedProperties Свойство ExtendedProperties объекта DataColumn возвращает объект РгореПуСоИес tion, предназначенный для хранения разнообразных объектов.

Объекты DataSet. DataTable, DataRelation и Constraint также предоставляют свой ство ExtendedProperties.

Подробнее о данном свойстве, включая пример кода, Ч в разделе этой Г/ШЕЛЛ, посвященном свойству ExtendedProperties объекта DataSet.

Свойство Свойство MaxLength позволяет гарантировать, что пользователь не поместит в объект DataColumn строку большего размера, чем допускает БД.

Часть III Автономная работа с данными: объект DataSet модели ADO. NET Значение этого свойства по умолчанию Ч 1, т. е. ограничений на длину строк нет. Как и в случае со свойствами AllowDbNull и Autolncrement, вызов метода Data AdapterFill не влияет на значение свойства Maxlength. Тем не менее его можно изменить посредством метода DataAdapterfittSchema.

Свойства Namespace и Prefix Свойства Namespace и Prefix позволяют задать для объекта DataSet префикс и пространство имен XML. ADO. NET использует значения этих свойств при записи содержимого DataSet в XML-файл, а также при загрузке XML-документа в объект DataSet.

Объекты DataSet и DataTable также обладают свойствами Namespace и Prefix.

Подробнее о пространствах имен XML Ч в документации MSDN, Свойство Ordinal Свойство Ordinal возвращает порядковый номер объекта DataColumn в наборе Columns объекта DataTable. Это свойство доступно только для чтения и, если DataColumn не относится к какому-либо объекте DataTable, возвращает -/.

Свойство Свойство Readonly определяет, доступно ли содержимое столбца только для чте ния. Значение данного свойства по умолчанию Ч False.

Если изменить значение по умолчанию свойства Expression объекта DataColumn (пустая строка), свойству Readonly автоматически задается значение True. При этом свойство Readonly становится доступным только для чтения.

При попытке изменить содержимое столбца, значение свойства Readonly ко торого Ч True, ADO. NET генерирует исключение ReadOnlyException. Однако, даже если значение свойства Readonly Ч True, до добавления объекта DataRow в набор Rows объекта DataTable содержимое столбца все равно можно изменять.

Как и в случае со свойствами AllowDbNull и Autolncrement, на значение свой ства Readonly влияет только метод FillSchema, но не метод Fill объекта DataAdapter.

Свойство Table Свойство Table возвращает объект DataTable, в состав которого входит DataColumn.

Если последний не относится к набору Columns какого-либо объекта DataTable, данное свойство возвращает неинициализированный объект.

Свойство Unique Свойство Unique позволяет обеспечить уникальность значений столбца в преде лах объекта DataTable. Значение этого свойства по умолчанию Ч False, Если задать свойству Unique значение True, то для объекта DataTable, в состав которого входит DataColumn, неянно создается объект UniqueConstraint. Аналогич ным образом, когда вы добавляете объект UniqueConstraint, основанный на отдель ном столбце, свойству Unique соответствующего объекта DataColumn задается значение True.

Если определить ограничение на уникальность или первичный ключ на осно ве нескольких столбцов, ADO.NET не задаст свойствам Unique всех соответствую щих объектов DataColumn значение True, поскольку значения в пределах столбца ГЛАВА 6 Работа с объектами DataSet не обязательно уникальные. Например, в таблице Order Details БД Norchwind оп ределен первичный ключ на осноье столбцов OrderlD и ProductlD. Ни один из этих столбцов не содержит уникальных значений, поскольку запись о заказе может вклю чать несколько товаров, а один товар может быть включен в несколько заказов.

Как и в случае со свойствами AlloicDbNull и Autolncrement, на значение свой ства Unique влияет только метод FittSchema, но не метод Fill объекта DataAdapter.

Свойства объекта Dataflow В табл. 6-12 перечислены наиболее часто используемые свойства объекта DataRow.

Таблица 6-12, Свойства объекта DataRow Свойство Тип данных Описание Указывает, содержит ли текущая запись ошибки Boolean HosErrors Возвращает/задает содержимое поля Object Item Массив типа Object Возвращает/задает содержимое записи ItemArrav Возвращает/задает сведения о наличии в записи String Ron-Error ошибок Возвращает состояние записи DataRoivState RowState Возвращает объект DataTable, в состав которого вхо Table DataTabla дит запись Свойство HasErrors Свойство HasErrors позволяет определить, содержит ли объект DataRow ошибки.

Это свойство возвращает логическое значение и доступно только для чтения.

Свойство Item Свойство Item позволяет просматривать и изменять содержимое отдельного поля записи. Обращаться к содержимому поля допустимо по порядковому номеру или имени поля, а также с использованием объекта DataColumn.

Кроме того, свойство Item может принимать значение из перечисления Data Rou'Version, определяющее, какую версию поля вы хотите видеть. Например, если вам потребуется оригинальное содержимое измененного поля, Свойство ItemArray Свойство ItemArray позволяет просматривать и изменять значения всех полей записи. Это свойство принимает и возвращает массив типа Object, элементы ко торого соответствуют столбцам объекта DataTable.

Если вы хотите с помощью свойства ItemArray изменить содержимое лишь отдельных полей записи, воспользуйтесь ключевым словом Nothing (Visual Basic) или null (Visual C*.NET). Следующий фрагмент кода пропускает первое поле за писи и изменяет содержимое второго, третьего и четвертого полей. Такой код потребуется вам при работе с объектами DataTable, включающими доступные только для чтения столбцы.

Visual Basic.NET row.ItemArray = New Object() {Nothing, 2, 3, 4, Nothing} Часть III Автономная работа с данными: объект DataSet модели ADO.NET Visual C#.NET row.ItemArray = new object[] {null, 2, 3, 4, null};

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

Свойство HasErrors объекта DataRow иногда возвращает True, даже если свой ству RowError задано пустое значение. Подробнее об этом Ч в документации SetColumnError.

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

Подробнее о возможных возвращаемых значениях свойства RowState Чв пре дыдущих разделах этой главы.

Свойство Table Свойство Table возвращает объект DataTable, в состав которого входит DataRow;

оно доступно только для чтения.

Иногда DataRow не относится к набору Rows какого-либо объекта DataTable например, когда он создан методом DataTable NetvRow и еще не добавлен в набор Rows этого объекта DataTable. Тем не менее свойство Table всегда возвращает объект DataTable, в состав которого входит DataRow.

Методы объекта DataRow В табл. 6-13 перечислены наиболее часто используемые методы объекта DataRow.

Таблица 6-13. Методы объекта DataRow Метод Описание Подтверждает все отлаженные изменения в объекте DataRow AcceptChanges Запускает процесс редактирования содержимого DataRow BeginEdit Отменяет все изменения, сделанные после вызова метода BeginEdit CancelEdit Удаляет ошибки из объекта DataRow ClearErrors Помечает объект DataRow как удаленный Delete Подтверждает все изменения, сделанные после вызова метода EndEdit BeginEdit Возвращает массив дочерних объектен DataRow текущего объекта GetCbildRows DataRow, основываясь на объекте Data-Relation Получает сведения об ошибках в конкретном поле GetColumnError Возвращает для текущей записи массив объектов DataColumn, со GetColumnsInError держащих ошибки * Возвращает родительский объект DataRow текущего объекта GetParentRow DataRow, основываясь на объекте DataRelation Возвращает массив родительских объектов DataRow текущего GetParentRows объекта DataRow, основываясь на объекте DataRi'latiou ГЛАВА 6 Работа с объектами DataSet Таблица 6-13. -(продолжение) Метод Описание Возвращает логическое значение, указывающее, может ли объект HasVersion DataRow вернуть требуемую версию данных Указывает, содержит ли определенное поле объекта DataRow зна IsNult чение Null Отменяет все отложенные изменения в объекте DataRow RejectCbanges Задает сведения об ошибках в конкретном поле SetColumnError Изменяет родительский объект DataRow текущего объекта DataRow, SetParentRow основываясь на объекте DataRelation Методы AcceptChanges и RejectChanges Методы AcceptChanges и RejectChanges позволяют подтверждать и отбрасывать все отложенные изменения в объекте DataRow.

По умолчанию, успешно передав отложенные изменения, хранящиеся в объекте DataRow, объект DataAdapter неявно вызывает метод DataRowAcceptChanges. При этом свойству RowState объекта DataRow задается значение Unmodified.

При вызове метода RejectChanges все отложенные изменения в объекте DataSet отбрасываются;

в этом случае свойству RowState объекта DataRow также задастся значение Unmodified.

Предположим, у вас есть запись с информацией о клиенте, содержащая отло женные изменения. Поле CompanyName изначально содержало значение Initial CompanyName, а теперь содержит значение New CompanyName.

После вызова метода AcceptChanges объект DataRow перестанет хранить ста рое оригинальное значение Initial CompanyName. Если вы запросите текущее или оригинальное значение записи с помощью метода DataRowItem, DataRow в лю бом случае вернет New CompanyName.

При вызове метода RejectChanges объект DataRow перестанет хранить новое значение New CompanyName. Если запросить текущее или оригинальное значе ние записи с помощью метода DataRowItem, DataRow в любом случае вернет Initial CompanyName.

Подробнее о том, как ADO.NET использует оригинальные значения DataRow для передачи изменений в БД, Ч в главах 10 и 11.

Методы BeginEdit, CancelEdit и EndEdit Методы BeginEdit, CancelEdit и EndEdit позволяют сохранять и отменять группы изменений содержимого объекта DataRow. Так, можно позволить пользователям редактировать содержимое записи и запросить подтверждение на сохранение или отмену этих изменений посредством диалогового окна.

Методы CancelEdit и EndEdit функционируют несколько иначе, чем методы AcceptCbanges и RejectCbanges. Проще всего объяснить разницу между этими ме тодами на примере. Следующий фрагмент создает новый объект DataRow, изме няет его содержимое, вызывает метод BeginEdit, снова изменяет содержимое объекта DataRow и выводит различные версии содержимого записи.

246 Часть III Автономная работа с данными: объект DataSet модели ADO.NET Visual Basic.NET Dim tbl As New DataTable("Customers") Ш, Columns. Add( "Customer-ID", GetType( String tbl.Columns.Add("CompanyName", GetType(String)) Dim row As Dataflow 'Создаем новую запись с помощью метода LoaddataRow row = tbl.LoadOataRow(New Object() {"ABODE", "Initial CompanyName"}, True) 'Изменяем содержимое объекта DataRow 'свойство row.RowState вернет Modified '"Оригинальное" значение поля - "Initial CompanyName" rowC'CompanyName") = "New CompanyName" 'Вызываем BeginEnit и снова изменяем содержимое поля CompanyName row.BeginEdit() rowC'CompanyName"} = "Even Newer CompanyName!" 'Выводим различные версии содержимого поля Console.WriteLine("Proposed: " & rowC'CompanyName", DataRowVersion.Proposed)) Console.WriteLine("Current: " & _ rowC'CompanyName", DataRowVersion.Cur rent)) Console.WriteLineC'Original: " & rowC'CompanyName", DataRowVersion.Original)) Visual C#.NET DataTable tbl = new DataTable("Customers");

tbl.Columns.AddC'CustomerlD", typeof(string));

tbl.Columns.Add("CompanyName", typeof(string));

DataRow row;

//Создаем новую запись с помощью метода LoadDataRow row = tbl.LoadDataRow(new object[] {"ABCDE", "Initial CompanyName"}, true);

//Изменяем содержимое объекта DataRow //свойство row.RowState вернет Modified //"Оригинальное" значение поля - "Initial CompanyName" row["CompanyName"] = "New CompanyName":

//Вызываем BeginEnit и снова изменяем содержимое поля CompanyName row. BeginEditO;

row["CompanyName"] = "Even Newer CompanyName!";

//Выводим различные версии содержимого поля Console.WriteLineC'Proposed: " + row["CompanyName", DataRowVersion.Proposed]);

ГЛАВА 6 Работа с объектами DataSel Console.WriteLine("Current: " + row["CompanyName", DataRowVersion.Current]};

Console.WriteLine("Original: " + row[ "CorfipanyNarae", DataRowVersion.Original ]);

Запустив код, вы увидите, что предполагаемое значение поля Ч Even Newer CompanyName!, текущее значение Ч New CompanyName и оригинальное Ч Initial CompanyName.

Для подтверждения редактирования можно использовать метод EndEdit. Теку щим значением поля станет предполагаемое значение. Оригинальное значение останется тем же.

Чтобы отметить изменения, вызовите метод CancelEdit. Текущее и оригиналь ное значения поля останутся неизменными.

Помните: если вы начали редактировать содержимое записи после вызова метода BeginEdit, метод DataRowJtem по умолчанию будет возвращать предпола гаемые значения полей. Подробнее о данной модели поведения Ч в разделе Про смотр отложенных изменений объекта DataRow этой главы.

Метод ClearErrors Метод ClearErrors позволяет удалить из объекта DataRou- все ошибки. Он удаляет как сведения об ошибках в опъекте DataRoic в целом, так и сведения об ошибках в отдельных полях записи.

Метод Delete При вызове метода Delete запись в действительности не удаляется из набора Rows соответствующего объекта Datalable. Вместо этого ADO.NET помечает запись как ожидающую удаления, чтобы ее можно было позже удалить из БД средствами метода DataAdapter.Update, Чтобы по-настоящему удалить объект DataRow, вызовите его метод Delete и за тем Ч AcceptChanges. Для уменьшения объема кода стоит воспользоваться мето дом DataRowCollectionRemove.

Метод GetChildRows Метод GetChildRou's позволяет обращаться к дочерним записям текущего объекта DataRow и принимает объект DataRelation или его имя. Можно также указать нужную вам версию содержимого дочерних записей, передав методу GetChildRoivs значе ние из перечисления DataRowVersion.

Метод GetChildRows возвращает дочерние данные в виде массива объектов DataRoii.

Методы GetColumnError и SetColumnError Методы GetColumnError и SetColumnError позволяют просматривать и задавать сведения об ошибках в конкретном поле. Они принимают имя поля, порядковый номер столбца в объекте DataTable или непосредственно объект DataColumn, Метод SetColumnError также позволяет удалить сведения об ошибках в конк ретном поле, передав в качестве второго параметра пустую строку.

248 Часть III Автономная работа с данными: объект DataSet модели ADO.NET Метод GetColumnslnError Если свойство HasErrors объекта DataRow возвращает True, метод GetColumnslnError позволяет определить, какие именно поля объекта DataRow содержат ошибки.

Следующий фрагмент кода с помощью методов GetColumnslnError и GetColumn Error возвращает информацию об ошибках для конкретного объекта DataRow.

Visual Basic.NET Dim row As DataRow If row.HasErrors Then Console.WriteLine("The row contains the following errors:") Console.WriteLineC'RowError: " i row.RowError) Dim colError As DataColumn For Each colError In row.GetColumnsInError Console.WriteLineC'Error in " & colError.ColumnName & ": " & _ row.GetColumnError(colError)) Next colError Else Console.WriteLine("The row does not contain errors") End If Visual C#.NET DataRow row;

if {row.HasErrors) Console.Writel_ine("The row contains the following errors:");

Console.WriteLineC'RowError: " + row.RowError);

foreach (DataColumn colError in row.GetColumnsInErrorO) Console.WriteLineC'Error in " + colError.ColutnnName + ": " + row.GetColumnError(colError));

, else Console.WriteLineC'The row does not contain errors");

Методы GetParentRow, GetParentRows и SetParentRow Методы GetParentRow, GetParentRows и SetParentRow позволяют просто и удобно просматривать и изменять родительскую запись текущего объекта DataRow в объек те DataRelation.

Как и метод GetChildRows, метод GetParentRows принимает объект DataRelation или его имя, а также значение из перечисления DataRouVersion, указывающее нужную вам версию содержимого родительской записи. Метод GetParentRows воз вращает объект DataRow.

Если у текущего объекта DataRow в отношении определено несколько роди тельских записей, для обращения к ним применяют метод GetParentRows, который принимает те же параметры, что и GetParentRow, но возвращает массив DataRow.

ГЛАВА 6 Работа с объектами DataSet Метод SetParentRow позволяет изменить определенную в отношении родитель скую запись объекта DataRow. Ему достаточно передать новый родительский объект DataRow или его имя. Если текущий DataRow входит в состав объекта DataTahle, являющегося дочерней таблицей нескольких отношений, которые определены в объекте DataSet, используйте перегруженный метод SetParentRow, Он принимает в качестве второго параметра объект DataRelation, позволяя указать, на какое от ношение требуется сослаться.

Метод Has Version Как уже рассказывалось, объект DataRoiv хранит несколько версий данных Ч те кущую, оригинальную и предполагаемую. Тем не менее все версии данных одно временно не хранятся.

Например, если значение свойства RowState Ч Added, объект DataRow поддер живает только текущую, но не оригинальную версию данных. Объект DataRow, значение свойства RowState которого Ч Deleted, хранит только оригинальн'/ю версию своего содержимого.

Метод HasVersion позволяет определить, поддерживает ли объект DataRoiv нуж ную вам версию данных. Он принимает значение из перечисления DataRowVersion и возвращает логическое значение, указывающее, содержит ли DataRow требуе мую версию данных.

Метод IsNult Предположим, у вас есть объект DataRow с информацией о клиенте и вам нужно получить содержимое поля ContactName в виде строковой переменной. Если для этого вы используете следующий код и поле ContactName содержит значение null, возможны проблемы:

Visual Basic.NET Dim row As DataRow Dim strContactName As String strContactName = CStr{row("ContactName")) Visual C#.NET DataRow row;

string strContactName;

strContactName = (string) row["ContactName"];

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

Метод IsNull значительно упрощает реализацию второго способа. Он прини мает имя поля, его порядковый номер или непосредственно объект DataColurnn и возвращает логическое значение, указывающее, содержит ли поле значений null.

Предыдущий фрагмент кода можно изменить следующим образом. Часть III Автономная работа с данными: объект DataSet модели ADO.NET Visual Basic.NET Dim row As Dataflow Dim strContactName As String If row.IsNull("ContactName") Then StrContactName = "" Else StrContactName = CStr(row("ContactName")) End If Visual C#.NET Dataflow row;

string strContactName;

if (row.IsNullC'ContactName")) StrContactName = "";

else strContactName = (string) row["ContactName"];

Четвертый перегруженный вариант метода IsNutt принимает объект DataColumn и значение из перечисления DataRoivVersion. С помощью этого метода IsNull уда ется определить, содержит ли конкретная версия поля значение null.

Свойства объекта UniqueConstraint В табл. 6-14 перечислены наиболее часто используемые свойства объекта Unique Constraint.

Свойства объекта UniqueConstraint Таблица 6-14.

Свойство Тип данных Описание Массив объектов Возвращает поля, на основе которых Columns определено ограничение DataColumn Возвращает имя ограничения ConstraintName String Содержит набор динамических свойств ExtendedProperties PropertyCollection и значений Указывает, является ли ограничение Boolean IsPrimaryKey первичным ключом объекта DataTable Возвращает объект DataTable, на котором Table DataTable определено данное ограничение Свойство Columns Свойство Columns возвращает массив объектов DataColumn, содержащий столб цы, котрые составляют ограничение. Это свойство доступно только для чтения.

Свойство ConstraintName Свойство ConstraintName позволяет просматривать и задавать имя объекта Unique Constraint.

ГЛАВА 6 Работа с объектами DataSet Свойство ExtendedProperties Свойство ExtendedProperties объекта UniqueConstraint возвращает объект Property Collection, предназначенный для хранения разнообразных объектов.

Объекты DataSet, DataColumn, DataRelation и ForeignKeyConstraint также предо ставляют свойство ExtendedProperties.

Подробнее о данном свойстве, включая пример кода, Ч в разделе этой главы, посвященном свойству ExtendedProperties объекта DataSet.

Свойство IsPrimaryKey Свойство IsPrimaryKey возвращает логическое значение, указывающее, является ли объект UniqueConstraint первичным ключом объекта DataTable.

Это свойство доступно только для чтения. Сделать объект UniqueConstraint пер вичным ключом объекта DataTable удается только с помощью его конструкторов, Кроме того, чтобы определить первичный ключ объекта DataTable, следует воспользоваться свойством PrimaryKey этого объекта.

Свойство Table Свойство Table возвращает объект DataTable, в состав которого входит UniqueConst raint;

оно доступно только для чтения.

Свойства объекта ForeignKeyConstraint В табл. 6-15 перечислены наиболее часто используемые свойства объекта Foreign KeyConstraint.

Таблица 6-15. Свойства объекта ForeignKeyConstraint Свойство Тип данных Описание Определяет, каскадируются ли результаты вызо AcceptRejectRule AcceptRejectRule ва методов AcceptCbanges и RejectCbanges роди тельского объекта DataRou' в дочерние записи Массив объектов Возвращает столбцы дочерней таблицы, Columns составляющие ограничение DataColumn Возвращает имя ограничения ConstraintName String Определяет, каскадируются ли удаление роди DeleteRule Rule тельского объекта DataRoic в дочерние записи Содержит набор динамических свойств ExtendedProperties PropertyCollection и значений Массив объектов Возвращает столбцы родительской таблицы, RelatedCoIttmns составляющие ограничение DataColumn Возвращает родительскую таблицу ограничения RelatedTable DataTable Возвращает дочернюю таблицу ограничения Table DataTable Управляет каскадированием изменений роди UpdateRule Rule тельской записи в дочерние записи Свойства AcceptRejectRule, DeleteRule и UpdateRule Свойства AcceptRejectRule, DeleteRule u UpdateRule управляют порядком каскадиро вания изменений родительской записи в дочерние записи.

Автономная работа с данными: объект DataSet модели ADO. NET 252 Часть III принимает значение из одноименного перечисления, Значение этого свойства по умолчанию Ч None, т. е. вызов метода AcceptCbanges или RejectRule объекта DataRow не сказывается на дочерних записях последнего.

Если задать свойству AcceptRejectRule значение Cascade, изменения каскадируют ся в дочерние записи, определенные объектом ForeignKey Constraint, Свойства DeleteRule и UpdateRule функционируют аналогичным образом, но при нимают значения из перечисления Rule. Значение этих свойств по умолчанию Cascade, т. е. изменения родительской записи каскадируются в дочерние записи.

Например, при вызове метода Delete родительского объекта DataRow вы неяв но вызываете и метод Delete его дочерних записей. Точно так же, редактируя зна чение поля ключа родительского объекта DataRow, вы неявно изменяете содер жимое соответствующих полей дочерних записей.

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

В первом случае при изменении или удалении содержимого родительской запи си соответствующим полям дочерних записей задаются значения null, а во вто ром Ч их значения по умолчанию.

Свойства Columns и RetatedColumns Свойство Columns возвращает массив объектов DataColumn, содержащий столб цы дочерней таблицы, которые составляют ограничение. Свойство RelatedColumns возвращает аналогичные столбцы родительской таблицы.

Оба свойства доступны только для чтения.

Свойство ConstraintName Свойство ConstraintName позволяет просматривать и задавать имя объекта Foreign KeyConstraint.

Свойство ExtendedProperties Свойство ExtendedProperties объекта ForeignKeyConstraint возвращает объект Ргорег tyCollection, предназначенный для хранения разнообразных объектов.

Объекты DataSet, DataColumn, DataRelation и UniqueConstraint также предостав ляют свойство ExtendedProperties.

Подробнее о данном свойстве, включая пример кода, Ч в разделе этой главы, посвященном свойству ExtendedProperties объекта DataSet.

Свойства RelatedTable и Table Свойство RelatedTable возвращает родительский, а свойство Table Ч дочерний объекта DataTable ограничения. Оба свойства доступны только для чтения.

Вопросы, которые стоит задавать почаще Вопрос. Стоит ли использовать объект DataSet при работе лишь с несколькими записями данных?

Ответ. В этой ситуации можно просто воспользоваться объектом DataTable, не прибегая к помощи объекта DataSet. DataSet предоставляет поддержку отношений ГЛАВА 6 Работа с объектами DataSet между таблицами, возможность чтения и записи данных в файлы и потоки, а так же поддержку XML-функций. Если все это вам не нужно, воспользуйтесь объек том DataTable.

Вопрос. Я добавил в объект DataTable новую запись, и при передаче этой запи си в БД возникла ошибка, связанная с тем, что запись нарушает определенное на таблице ограничение PrimatyKeyConstraint. Почему такой ошибки не возникло при добавлении записи в Ответ. ADO. NET обеспечивает соблюдение создаваемых вами ограничений, ос новываясь на данных объекта DataSet. Созданная вами новая запись нарушит оп ределенное на таблице ограничение Primary KeyConstraint, только если в объекте DataTable уже есть запись с такими же значениями полей первичного ключа. В ADO. NET нет каких-либо наследуемых знаний о содержимом вашей БД, Вопрос. Как просмотреть содержимое удаленной записи?

Ответ. Передайте в качестве необязательного параметра свойства Item констан ту1 DataRowVersion.Original.

Вопрос. Можно ли отменить изменения, внесенные мной в записи?

Ответ. Да. Метод RejectChanges объектов DataRou', DataTable и DataSet позволяет отменить все отложенные изменения затронутых записей, Вопрос. Как узнать, определен ли в объекте DataTable первичный ключ?

Ответ. Воспользуйтесь следующим фрагментом кода:

Visual Basic.NET If tbl.PrimaryKey. Length > 0 Then 'У DataTable есть первичный ключ Else 'У DataTable нет первичного ключа End If Visual C#.NET if (tbl.PrimaryKey. Length > 0) //У DataTable есть первичный ключ else //У DataTable нет первичного ключа Вопрос. Что насчет объектов DataSet и метода Dispose?

Ответ. Объекты DataSet, DataTable и DataColumn являются производными от класса MarsbalByValueComponent, предоставляющего метод Dispose и событие Disposed.

Метод Dispose позволяет освободить занимаемые объектом ресурсы. Если вам требуется выполнять код одновременно выполнением метода Dispose объекта, перехватывайте событие Disposed этого объекта, Часть III Автономная работа с данными: объект DataSet модели ADO.NET Для тех, кто собирается вызывать метод Dispose какого-либо из вышеперечис ленных объектов, отмечу, что действие этого метода не рекурсивно. При вызове метода Dispose объекта DataSet не осуществляется неявного вызова метода Dispose объектов DataTabte из состава DataSet.

ГЛАВА Работа с реляционными данными 1 аблицы БД редко бывают независимыми структурами данных. Из рис. 7-1 вид но, что все таблицы БД Northwind, поставляемой с SQL Server 2000, взаимосвяза ны, и таблиц, стоящих особняком, нет.

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

При работе с данными из нескольких объектов DataTable вам, по всей види мости, потребуются четыре функции Ч перемещение, проверка, агрегация и кас кадирование. Не спорю, слова каскадирование* на самом деле нет. Но хотя это и техническая книга, мне все-таки предоставлено ограниченное право на художе ственный вымысел.

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

В этой главе рассказывается о работе с данными из связанных объектов Data Table при помощи объекта DataRelation ADO.NET. Я также подробно опишу осо бенности объекта ForeignKey'Constraint, о которых упоминал в предыдущей главе.

Часть 1)1 Автономная работа с данными: объект DataSet модели ADO.NET Employees Customers ХE Employee'lD ""i ) ^ -.*i;

i.

Г J* ~ La^iMane CornDanvNamf risiomerlD FntrvVKI EmployealD TM= "^ COTeetTt*. ^QrderDite ти_ъ Ч Eithtatr 3hlpptлete ~CU HirsDWS 3l*Vn Address | | ~ PostalCode Prwrjhl й ХХ L. ProducU Oriter DetaUs. :

.:_ Supplier IP.-I,H i'.' J Crsmunt UlltMtB Ч 1^ СМедоМнч :....

' ~1 Catagaryriam?

*~J Df^cnption QPl*" Рис. 7-1. Отношения между таблицами БД Northwind Особенности доступа к реляционным данным Вполне очевидно, что модель ADO.NET не является пионером в области доступа к реляционным данным. Ей предшествовали другие способы обработки данных из связанных таблиц. Сейчас я познакомлю вас с основными способами работы с данными из связанных таблиц и вкратце сравню их с методом, когда использует ся объект DataRelation.

Соединяющие запросы Соединяющие запросы предшествовали всем технологиям доступа к данным Micro soft. Этот простой, стандартный способ позволяет получить данные из несколь ких таблиц посредством одного запроса. Вот как запрос выбирает данные из таб лиц Customers, Orders и Order Details БД Northwind:

SELECT C.CustomerlD, C.CompanyName, C.ContactName, C.Phone, 0.OrderlD, O.EmployeelD, O.OrderDate, D.ProductID, D.Quantity, D.UnitPrice FROM Customers C, Orders 0, [Order Details] D WHERE C.CustomerlD = 0. Customer-ID AND 0. OrderlD = D.OrderlD Преимущества соединяющих запросов таковы:

Х этот стандарт получил широкое признание. Все программисты БД умеют использовать соединяющие запросы;

Х они возвращают данные в виде одной структуры;

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

ГЛАВА 7 Работа с реляционными данными Однако не обошлось и без недостатков.

Соединяющие запросы иногда возвращают избыточные данные. Если клиент разместил 100 заказов, для каждого из них будет возвращена инфор мация об этом клиенте (рис. 7-2).

D30- D3D- ALFKI D3D- ALFKl D3D-. Til ll,l'i ХХ I.

-ALFKJ D3D- ALf 4lflлli Fiiiskisle D3D- IALFKI Alfre* Fultarkifli 030-ОП 03D Х III Alfre* FuHnkitle ALFKI Alfred! FuKekitle D3D4M JALJKI Alfred: Fglaftele.' Ч.

iANAIP.51566.,.

.. :.,,.

Ana ~iifa Е IANATR.5) 555- ANATF.5) 555- ^..

" = ч ANATP EmpaiEda] 5) 555- ANATP a Empaadad (5) 555- Ana Tnio Eriparedad (5) БЕ5-172Э ANATFi ANATFI rparedad (Б] ЕЕЕ- ДМАТЧ Ana Tnjlo inperffdad (Б] 5Е5-472Э (5) 555-472S ДМАТЧ (5) 555-472S (5) 555- 15) 555- (5) 555- (5) 555-ЗЭ.5) 555- (5) 555- [5) 555. (5) 555-3S [5] 555- [5] 655- Рис. 7-2. Данные, возвращаемые соединяющим запросом Х Обновление результатов соединяющих запросов затруднено. Модели доступа к данным, например ADO.NET, трудно определить, как же интерпре тировать изменения в результатах соединяющего запроса. Например, когда вы удаляете запись, означает ли это, что нужно удалить только соответствующую запись в дочерней таблице, или требуется также удалить эту запись в родитель ских таблицах? Когда вы добавляете запись, означает ли это, что ее нужно добавить только в дочернюю таблицу, или необходимо также добавить эту запись в родительские таблицы?

Х Синхронизация результатов соединяющих запросов затруднена. Ког да вы изменяете родительскую запись (например, вы изменили контактное имя клиента), обновленные данные требуется передать в БД и затем повторно вы полнить запрос, чтобы изменение проявилось также во всех связанных запи сях набора результатов.

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

Преимущества отдельных запросов таковы:

258 Часть III Автономная работа с данными: объект DataSet модели ADO.NET Х в целом они возвращают меньше данных, чем соединяющие запросы;

Х они больше подходят для обновления результатов. Поскольку вы изме няете структуру (например, объект Recordset), соответствующую отдельной таблице, технология типа ADO легко интерпретирует изменения и соответству ющим образом модифицирует данные БД;

Х отдельные запросы позволяют обращаться к разным источникам дан ных. Если связанные таблицы относятся к различным СУБД, данная возмож ность вам пригодится.

Недостатки отдельных запросов;

Х отдельные запросы требуют синхронизирующего кода. Чтобы найти заказы, размещенные конкретным клиентом, следует определить фильтр для дочернего объекта Recordset и написать код, синхронизирующий объекты Recordset друг с другом;

Х фильтрация результатов отдельных запросов затруднена. Создание за просов к дочерним таблицам, которые возвращают только записи, связанные с записями, полученными из родительской таблицы, Ч в общем-то, проблема тично. Подробнее об этом Ч в следующих разделах главы.

Иерархичные объекты Recordset модели ADO В ADO 2.0 реализована концепция иерархичных объектов Recordset. Специальный поставщик данных и специальный синтаксис запроса позволили объединять ре зультаты нескольких запросов в одну структуру. Следующий фрагмент кода помеща ет содержимое таблиц Customers, Orders и Order Details в иерархичный объект Recordset:

Dim rsCustomers As ADODB,Recordset, rsOrders As ADODB.Recorders Dim rsOrderDetails As ADODB.Recordset Dim strConn As String, strSQL As String strConn = "Provider=MSDataShape;

Data Provider=SQLOLEDB;

" & "Data Source=(local)\NetSDK;

Initial Catalog=Nc>rthwind;

" & "Trusted_Connection=Yes;

" strSQL = "SHAPE {SELECT CustomerlD, CompanyName, ContactName, " & "ContactTitle FROM Customers} AS Customers APPEND " & _ "((SHAPE {SELECT OrderlD, CustomerlD, EmployeelD, OrderDate " "FROM Orders} AS Orders APPEND ({SELECT OrderlD, ProductID, " & "UnitPrice, Quantity FROM [Order Details]} AS OrderDetaiLs " & "RELATE "OrderlD" TO 'OrderlD') AS OrcferDetails) AS Orders " & _ "RELATE 'CustomerlD' TO 'CustomerlD'} AS Orders" Set rsCustomers = New ADOOB.Recordset rsCustomers.Open strSQL, strConn, adOpenStatic, adLockBatchOptimistic Set rsOrders = rsCustomers.Fields("0rders").Value Set rsOrderDetails = rsOrders.Fields("OrderDetails").Value У объекта Recordset есть три объектных переменных, однако все они ссыла ются на данные, хранящиеся в одной структуре. Когда вы перемещаетесь по объекту Recordset верхнего уровня, в дочерних объектах Recordset доступны только свя занные данные.

ГЛАВА 7 Работа с реляционными данными Преимущества иерархичных объектов Recordset таковы:

Х они возвращают меньше данных, чем соединяющие запросы;

Х они возвращают данные в виде одной структуры;

Х они годятся для простых обновлений. Несмотря на то, что иерархичные объекты Recordset хорошо справляются с простыми обновлениями, у них есть определенные ограничения. Б лучшем случае проблемы возникают при пере даче отложенных изменений нескольких таблиц.

У иерархичных объектов Recordset есть и недостатки:

Х синтаксис запросов ужасен. Посмотрите-ка на этот запрос! Хоть я и счи таю себя экспертом по ADO, я никогда и не думал изучать синтаксис SHAPE;

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

Х можно обращаться только к одному источнику данных;

Х затруднена фильтрация.

Объекты DataRelation модели ADO.NET Структура объектов DataRelation ADO.NET сильно отличается от структуры иерар хичных объектов Recordset. Объектам DataRelation не требуется дополнительный поставщик или отвратительный синтаксис запросов SHAPE. Объекты DataRelation считаются частью схемы объекта DataSet.

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

Преимущества объектов DataRelation таковы:

Х они возвращают меньше данных, чем соединяющие запросы;

Х упрощают поиск связанных данных;

Х не требуют сложного синхронизирующего кода;

Х рассчитаны на сложные случаи обновления. Например, вы можете добав лять новых клиентов до добавления их заказов, а также удалять имеющиеся заказы перед удалением имеющихся клиентов. Также, если у вас есть группа отложенных изменений таблиц Orders и Order Details, перед передачей новых записей в таблицу Order Details можно выбрать для новых заказов автоинкре ментные значения, генерируемые сервером. Подробнее о таких ситуациях в главе 11;

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

Х поддерживают каскадные обновления. Указать, должны ли изменения родительской записи передаваться дочерним записям, можно при помощи свойств ограничения ForeignKeyConstraint, связанного с объектом DataRelation;

Х позволяют создавать иерархии на основе разных источников данных.

Вам нужно связать результаты запроса о клиентах к БД SQL Server и результа ты запроса о заказах к БД Oracle? Без проблем.

260 Часть 111 Автономная работа с данными: объект DataSet модели ADO.NET Недостаток у объектов DataRelation только один:

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

Работа с объектами DataRelation в коде Вы можете использовать собственный код для перемещения по нескольким таб лицам данных, для проверки и агрегации данных, а также для каскадирования изменений. Тем не менее эти же операции легко и просто осуществляются сред ствами объекта DataRelation модели ADO.NET. Сейчас я покажу, как программно создать объект DataRelation.

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

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

Чтобы упростить создание одноименных объектов, класс DataRelation предостав ляет отдельные конструкторы, принимающие отдельные объекты DataColumn и массивы таких объектов.

В стандартном примере, создающем отношение, используются объекты Data Table, содержащие информацию о заказах и клиентах (рис. 7-3). Следующий фраг мент кода создает объект DataRelation для данного примера:

Visual Basic.NET 'Создаем новый объект DataSet и добавляем объекты DataTable и DataColumn Dim ds As New DataSet() 'Создаем объект DataRelation, связывающий две таблицы Dim rel As DataRelation rel = New DataRelation("CustomersOrders", ds.TablesC"Customers").Columns("CustomerlD"), ds.Tables("Qrders").Columns( "Customer-ID" ds.Relationships.Add(rel) Visual C#.NET //Создаем новый объект DataSet и добавляем объекты DataTable и DataColumn DataSet ds = new DataSetO;

//Создаем объект DataRelation, связывающий две таблицы DataRelation rel;

rel = new DataRelation{"CustomersOrders", ds.Tables["Custome rs"].ColumnsE"CustomerlD"], ГЛАВА 7 Работа с реляционными данными ds.Tables["Orders"].Columns["CustomerID"]);

ds.Relationships.Add(rel);

- B/3/19 И/1 3/1 /I 1, /II Я /II./19 К f4/l'l nj llii In Х!Х, /JB/19 i, - /B/ - 1/H8/1 9V /-1/19Ч 5Ь - /28/19..,,,1 с е Hum 1B35E. I/IS/I IB183 2/16/1 S3 - /21/19 S8 - /4/ Рис. 7-3- Вывод связанной информации Если нужно определить отношение, основанное на нескольких столбцах, вос пользуйтесь конструктором DataRelation, принимающим массивы объектов Data Column:

Visual Basic.NET 'Создаем новый объект DataSet и добавляем объекты DataTable и DataColumn Dim ds As New DataSetC) 'Создаем массивы объектов DataColumn, на которых 'будет основан новый объект DataRelation Dim colsParent, colsChiLd As DataColumnO With ds.Tables("ParentTable") colsParent = New DataColumnO {.Columns("ParentColumn1"), _.Columns("ParentColumn2")} End With With ds,Tables("ChildTable") colsChild = New DataColumnO {.Columns("ChildColumn1"), _.Columns("ChildColumn2")} End With 'Создаем новый объект DataRelation Dim rel As DataRelatlon rel = New DataRelation("MultipleColunms", colsParent, colsChild) ds.Relationships.Add(rel) Visual C#.NET //Создаем новый объект DataSet и добавляем объекты DataTable и DataColumn DataSet ds = new DataSetO;

//Создаем массивы объектов DataColumn, на которых //будет основан новый объект DataRelation Автономная работа с данными: объект DataSel модели ADO.NET 262 Часть III DataTable tblParent, tblChlld;

DataColumn[] colsParent, colsChild;

tblParent = ds.Tables["ParentTable"];

colsParent = new DataColumn[] {tblParent.Columns["Paren1:Column1"], tblParent.Columns["ParentColumn2"]};

tblChild = ds.Tables["ChildTable"];

colsChild = new DataColumn[] {tblChild.ColumnsE"ChildColumn1"], tblChild.Coluлns["ChildColun2"]};

//Создаем новый объект DataRelation DataRelation rel;

rel = new DataRelation("MultipleColumns", colsParent, colsChild);

ds.Relationships,Add(rel);

Кроме того, у DataRelation есть пара конструкторов, сигнатуры которых соот ветствуют сигнатурам, рассмотренным выше, и включают четвертый параметр, определяющий, нужно ли создавать ограничения для обеспечения ссылочной целостности на основе нового отношения. По умолчанию при создании объекта DataRelation в объекты DataTable добавляются ограничения (если только эти ог раничения еще не существуют). Подробнее об этом Ч чуть дальше.

Созданный объект DataRelation следует добавить в набор Relations объекта DataSet. Как и в случае с новыми объектами DataTable к DataColumn, создают и добавляют объект DataRelation в набор Relations объекта DataSet посредством од ного вызова:

Visual Basic.NET 'Создаем новый объект DataSet и добавляем объекты DataTable и DataColumn Dim ds As New DataSet() 'Создаем объект DaiaRelation, связывающий две таблицы ds.Relationships.Add("CustomsrsOrders", ds.Tables{"Customers").ColumnsC'CustomerlD"), ds.Tables("Orders").Columns( "Customer-ID")) Visual C#.NET //Создаем новый объект DataSet и добавляем объекты DataTable и DataColumn DataSet ds = new DataSetO;

//Создаем объект DataRelation, связывающий две таблицы ds.Relationships.Add("CustomersOrders", ds.Tables["Customers"].Columns["CustomerlD"], ds.Tables["Orders"].Columns["CustomerID"]);

Поиск связанных данных Одно из основных применений объекта DataRelation Ч поиск связанных данных в различных объектах DataTable. Тем не менее непосредственно объект DataRelation ГЛАВА 7 Работа с реляционными данными не предоставляет такой функциональности Ч она реализуется методами GetCbiid Rotvs, GetParentRoiv и GetParentRows объекта DataRow. Так каким же образом Data Relation участвует в таком поиске? Упомянутые методы принимают в качестве параметра объект DataRelation. Давайте подробно разберем, что представляют собой эти методы и как их использовать, Метод GetChildRows объекта DataRow Поиск связанных с родительской записью дочерних записей в другом объекте DataTable Ч очень простая задача. Для этого достаточно вызвать метод GetCbudRows нужного объекта DataRow и передать ему имя объекта DataRelation, определяю щего отношение между объектами DataTable. Вместо имени можно также пере дать сам объект DataRelation. Метод GetChildRows возвращает связанные данные в виде массива объектов DataRoiv.

Следующий фрагмент кода вызывает метод GetChildRows и просматривает воз вращаемые им данные;

Visual Basic.NET 'Просматриваем записи о клиентах Din rowCustomer, rowOrder As DataRow For Each rowCustomer In ds.Tables("Customers").Rows Console, WriteLine(rowCustomer("CustomerID") & " - " & _ rowCustomer("CompanyName")) 'Просматриваем заказы, размешенные этими клиентами For Each rowQrder In rowCustomer. GetChildRowsC'fielationName") Console. WriteLine(vbTab & rowOrder("Order!D") & " - " * _ rowOrder("OrderDate" Next rowOrder Next rowCustomer Visual C#.NET //Просматриваем записи о клиентах foreach (DataRow rowCustomer in ds,Tables["Customers"].Rows) { Console. WriteLine(rowCustomer["CustomerID"] + " - " + rowCustomer [ "CompanyName" ]);

//Просматриваем заказы, размещенные этими клиентами foreach (DataRow rowOrder in rowCustomer. GetChildRowsC'RelationName")) Console. WriteLine("\t" + rowOrder["OrderID"] + " - " + rowO rde r [ " 0 rde rDate " ] ) ;

Метод GetParentRow объекта DataRow Объект DataRelation позволяет перемещаться по иерархии не только вниз, но и вверх. У объекта DataRow есть метод GetParentRoiv, при помощи которого удается найти родительскую запись дочерней записи на основании определенного в объек те DataSet объекта DataRelation. Как и метод GetChildRows, GetParentRow принимает нужный объект DataRelation или строку с его именем:

264 Часть III Автономная работа с данными: объект DataSet модели ADO.NET Visual Basic.NET Dim rowCustomer, rowOrder As DataRow 'Просматриваем записи о заказах For Each rowOrder In ds.TablesC'Orders").Rows Console.Write(rowOrder("OrderIO") & vbTab & rowOrder("OrderDate")} 'Ищем связанную родительскую запись rowCustomer = rowOrder.GetParentRow("CustomersOrders") Console.WriteLine(vbTab & rowCustomer("CompanyName")) Next rowOrder Visual C#.NET DataRow rowCustomer;

//Просматриваем записи о заказах foreach (DataRow rowOrder in ds.Tables["Orders"].Rows) I Console.Write(rowOrder["OrderID"] + "\t" + rowOrder["OrderDate"]);

//Ищем связанную родительскую запись rowCustomer = rowOrder.GetParentRow{"CustomersOrders");

Console.WriteLine("\t" + rowCustomer["CompanyName"]);

' Метод GetParentRows.объекта DataRow Если вы имеете дело с отношением лодин ко многим и вам нужно просмотреть все родительские записи конкретного объекта DataRow, воспользуйтесь методом GetParentRows объекта DataRow. Его сигнатуры аналогичны сигнатурам метода GetChildRows-.

Visual Basic.NET 'Просматриваем записи о клиентах Dim rowChild, rowParent As DataRow For Each rowChild In ds.Tables("ChildTable").Rows 'Просматриваем родительские,загшси Console.WriteLine(rowChild("ChildName")) For Each rowParent In rowChild.GetParentRows{"RelationName") Console.WriteLine(vbTab i rowParent("ParentName")) Next rowParent Next rowChild Visual C#.NET //Просматриваем записи о клиентах foreach (DataRow rowChild in ds.Tables["ChildTable"]. Rows) { Console. WriteLine(rowChild["ChildName"]);

//Просматриваем связанные с этими записями за'казы foreach (DataRow rowParent in rowChild.GetChildRows("RelationName")) Console.WriteLine("\t" + rowParent["ParentName";

|);

ГЛАВА 7 Работа с реляционными данными Примечание В БД редко встречаются отношения многие ко многим*, и далее я объясню почему.

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

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

Из главы 6 вы помните, что метод Item объекта DataRow позволяет просмат ривать оригинальное и текущее значение конкретного поля соответствующей записи. Кроме того, методы GetCbildRows, GetParentRow и GetParentRows объекта DataRow принимают значение из перечисления DataRowVersion, указывающее нужную вам версию данных.

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

Visual Basic.NET 'Просматриваем записи о клиентах Dim rowCustomer, rowOrder As DataRow For Each rowCustomer In ds.Tables("Customers").Rows Console.WriteLine(rowCustomer("CustomerID") i " " & _ rowCustomer("CompanyName")) 'Выводим оригинальные значения записей о заказах, 'размещенных конкретным клиентом For Each rowOrder In rowCustomer.GetChildRowsC'RelationName", _ DataRowVersion.Original) Console.WriteLine(vbTab & rowOrder("OrderID") & " - " & _ rowOrder{"OrderDate")) Next rowOrder Next rowCustomer Visual C#.NET //Просматриваем записи о клиентах foreacn (DataRow rowCustomer in ds.Tables["Customers"].Rows) Console.WriteLine(rowCustomer["CustomerID"] + " - " + rowCustome r [" CoinpanyName" ]);

//Выводим оригинальные значения записей о заказах, //размещенных конкретным клиентом foreach {DataRow rowOrder in rowCustomer.GetChildRowsC'RelationName", 10- Автономная работа с данными: объект DataSet модели ADO.NET 266 Часть III DataRowVersion.Original)) Console,WriteLine("\t" + rowOrder["OrderlD"] + " rowOrder["OrderDate"]);

!

Примечание Методы, не принимающие значения из перечисления DataRow Version, выводят текущую версию данных, Проверка данных средствами объектов DataRelation Теперь, когда вы умеете с помощью объектов DataRelation перемещаться по дан ным связанных объектов DataTable, мы рассмотрим еще одну важную функцию объекта DataRelation Ч проверку данных.

При создании отношения между двумя объектами Data-Table обычно требует ся гарантировать отсутствие в дочернем объекте DataTable лосиротевших дан ных, т. е. запретить пользователям добавлять в таблицу Orders записи, не соответ ствующие записям таблицы Customers. Объект DataRelation позволяет обеспечить соблюдение ограничений в связанных объектах DataTable.

Создание ограничений По умолчанию при создании объекта DataRelation в родительском объекте DataTable определяется ограничение UntqueKey, а в дочернем объекте DataTable Ч ограни чение ForeignKey Constraint. Следующий фрагмент кода создает на столбце Custo merlD объекта DataTable Customers ограничение UniqueKey, а на столбце CustomerlD объекта DataTable Orders Ч ограничение ForeignKeyConstraint.

Visual Basic.NET Dim strConn, strSQL As String strConn = "Provider=SQLOLEDB;

Data Source=(local)\NetSDK;

" & "Initial Catalog=Northwind;

Trusted_Connection=Yes;

" Dim en As New OleDbConnection(strConn) cn.OpenO StrSQL = "SELECT CustomerlD, CompanyName, ContactName FROM Customers" Dim daCustomers As New 01eDbDataAdapter(strSQL, en) strSQL = "SELECT OrderlD, CustomerlD, EmployeelD, OrderDate FROM Orders" Dim daOrders As New 01eDbDataAdapter(strSQL, en) Dim ds As New DataSetQ daCustomers.Fill(ds, "Customers") daOrders.Fill(ds, "Orders") en.Close() ds,Relationships.Add("CustomersOrders", ds.Tables("Customers").Columns("GustomerlD"), ds.Tables("Orders").Columns("CustomerlD")) Console.WriteLine("The parent DataTable now contains " & ds.Tables("Customers").Constraints.Count & Работа с реляционными данными ГЛАВА " constraints") Console.WriteLine("The child DataTable now contains " & ds.Tables("Orders").Constraints.Count " constraints") Visual C#.NET string strConn, strSOL;

strConn = "Provider=SQLOLEDB;

Data Source=(local)\\NetSDK;

" + "Initial Catalog=Northwind;

Trusted_Connection=Yes;

";

OleDbConnection en = new OleDbConnection(strConn);

en.Open();

strSQL = "SELECT CustomerlD, CompanyName, ContactName FROM Customers";

OleDbDataAdapter daCustomers = new 01eDbDataAdapter{strSQL, en);

strSQL = "SELECT OrderlD, CustomerlD, EmployeelD, OrderDate FROM Orders";

OleDbDataAdapter daOrders = new 01eDbDataAdapter(strSQL, en);

DataSet ds = new DataSetQ;

daCustomers.Fill(ds, "Customers");

daOrders.Fill(ds, "Orders");

cn.CloseO;

ds.Relationships.Add("CustomsrsOrders", ds.Tables["Customers"].Columns["CustomerlD"], ds.Tables["Orders"].Columns["CuEitomerID"]);

Console.WriteLine("The parent DataTable now contains " + <3s. Tables[ "Customers"]. Constraints. Count + " constraints");

Console.WriteLineC'The child DataTable now contains " + ds.Tables["Orders"].Constraints.Count + " constraints");

Использование имеющихся ограничений Ограничения можно также определять заблаговременно;

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

Visual Basic.NET Dim ds As New DataSetO daCustomers.Fill(ds, "Customers") uaOrders.Fill(ds, "Orders") cn.CloseO With ds.Tables("Customers").PrimaryKey = New DataColumnO {.Columns("CustomerID")} End With With ds.TablesC'Orders").Constraints.Add("FK_CustomersOrders", _ 268 Часть III Автономная работа с данными: объект DataSet модели ADO.NET ds.TablesC"Customsrs").Columns("CustomsrID"), _.ColumnsC"Custome rID")) End With us. Relationships.Add("CustomersOrders", _ ds.TablesC"Customers").ColumnsC"GustomerlD"), _ ds.Tables("Orders").Columns("CustomerID")) Console.WriteLineC"The parent DataTable now contains " & ds.Tables("Customers").Constraints.Count & " constraints") Console.WriteLineC"The child DataTable now contains " & _ ds.TablesC"Orders").Constraints.Count & " constraints") Visual C#.NET DataSet ds = new DataSetO;

daCustomers.Fill(ds, "Customers");

daOrders.FillCds, "Orders");

cn.CloseC);

DataTable tbl = ds.Tables["Customers"];

tbl.PrimaryKev = new DataColumn[] {tbl.Colufnns["CustomerlD"]};

tbl = ds.Tables["Orders"];

tbl.Constraints,Add("FK_CustomersOrders", ds.Tables["Customers"].Columns["CustomerID"], tbl.Columns["CustoraerID"]);

ds. Relationships.Add("CustomersOrders", ds.Tables["Customers"].Columns["CustomerID"], ds,Tables["Orders"].Columns["CustomeirID"]);

Console.WriteLineC"The parent DataTable now contains " + ds.TablesC"Customers"].Constraints.Count + " constraints");

Console.WriteLineC"The child DataTable now contains " +Х ds.Tables["Orders"].Constraints,Count + " constraints");

Смотри-ка! Нет ограничений!

Как вы помните, при создании объекта DataRelation ADO.NET по умолчанию до бавляет в объект DataSet ограничения UniqueKey и ForeignKeyConstraint, сигнату ры которых соответствуют аналогичным сигнатурам нового объекта DataRelation.

Если в объекте DalaSet эти ограничения уже есть, DataRelation станет ссылаться на них. В противном случае ADO.NET явно создаст новые ограничения.

ГЛАВА 7 Работа с реляционными данными Однако есть еще один вариант. В одном из разделов, посвященном конструк торам класса DataRelation, говорилось, что есть конструкторы, позволяющие ADO.NET запретить создавать ограничения для объекта DataRelation. Такие конструкторы полезны, если вам нужен объект DataRelation, но не требуются соответствующие ограничения в объекте DataSet.

Ограничения ForeignKeyConstraint и значения Null Возможно, мне удастся аас удивить. Например, я не мог и предположить, что даже при наличии ограничения ForeignKeyCon&traint в БД и объекте DataSet вполне могут появиться лосиротевшие данные.

Вы мне не верите? Выполните следующий запрос к своей любимой БД. Norhtwind (выполнять похожие запросы к производственным БД при раз - работке приложений или изучении ADO.NET не рекомендуется).

UPDATE Orders SET Customer!!) = NULL WHERE CustOfflerlO = 'ALFKI Запрос успешно завершится, и в таблице Orders появится записи, не.

связанные с какой-либо записью таблицы Customers. Чтобы вернуть все на свои места:

UPDATE Orders SET CustomerlD = 'ALFKI' WHERE CustofflerlD IS NULL Убедиться, что на таблице Orders определено ограничение ForeignKey Constraint Y, можно, выполнив приведенный ниже запрос. Если в таблице Customers нет записи, значение поля CustomerlD которой Ч ZZZZZ, запрос завершится с ошибкой:

UPDATE Orders SET CustomerlD = 'ZZZZZ' WHERE CustomerlD = 'ANTON' Записи, хотя бы одно поле которых, указанное в ограничении Foreign KeyConstraint, содержит значение MULL, не проверяются на соответствие этому ограничению. Помните об этом, определяя схему БД и объекта DataSet.

Объекты DataRelation, ссылающиеся на себя Иногда родительская и дочерняя таблицы, участвующие в отношении, являются одним и тем же объектом. Возьмем таблицу Employees БД Northwind. В ней есть столбец EmpIoyeelD с идентификатором сотрудника, а также столбец ReportsTo с идентификатором руководителя сотрудника. Кроме того, на столбце ReportsTo определено ограничение FOREIGN KEY, гарантирующее, что данный столбец при нимает только значения из столбца EmpIoyeelD.

Следующий фрагмент кода выбирает из таблицы Employees данные в объект DataSet и создает ссылающийся на себя объект DataRelation:

Visual Basic.NET Dim strConn, strSQL As String strConn = "Provider=SQLOLEDB;

Data Source=(local)\NetSDK;

" & _ "Initial Catalog=Northwind;

Trusted_Connection=Yes;

" StrSQL = "SELECT EmpIoyeelD, ReportsTo, " & "LastName + ', ' + FirstName AS EmployeeName FROM Employees" 270 Часть III Автономная работа с данными: объект DataSet модели ADO.NET Dim da As New 01eDbDataAdapter{strSQL, strConn) Dim ds As New DataSetQ da.Fill(ds, "Employees") Dim tbl As DataTable = ds.TablesC'Employees") ds.ftelations.Add("SelfReferencing", tbl.Columns("EmployeeID"), _ tblColumnsC"ReportsTo"), False) Visual C#.NET string strConn, strSQL;

strConn = "Provider=SQLOLEDB;

Data Source=(local)\\NetSDK;

" + "Initial Catalog=Northwind;

Trusted_Connection=Yes;

";

StrSQL = "SELECT EmployeelD, ReportsTo, " + "LastName + ', ' + FirstName AS EmployeeName FROM Employees";

QleDbDataAdapter da = new 01eDbDataAdapter(strSQL, strCcnn);

DataSet ds = new DataSetO;

da.FilKds, "Employees");

DataTable tbl = ds.Tables["Employees"];

ds.Relations.Add("SelfReferencing", tbl.Columns["EmployeelD"], tbl.Columnst"ReportsTo"], false);

Создать DataRelation Ч только половина дела. Наша настоящая цель Ч вывес ти древовидный список всех сотрудников (рис. 7-4). Обход иерархии для вывода сотрудников по их руководителям связан с определенными проблемами, особен но если вы никогда не писали рекурсивный код. В таком случае рекомендую вам изучить документацию языка по вашему выбору и найти там необходимую инфор мацию, поскольку вопросы рекурсии не вошли в эту книгу.

Рис. 7-4. Вывод содержимого таблицы Employees с использованием ссылающегося на себя объекта DataRelaHon Приведенный далее фрагмент кода просматривает содержимое объекта Data Table и выводит данные о руководителе наивысшего ранга. В таблице Employees ему соответствует запись, значение поля ReportsTo которой Ч NULL. Подробнее о значениях NULL и ограничениях ForeignKey Constraint Ч во врезке -Ограничения ForeignKey Constraint и значения Null.

Обнаружив руководителя наивысшего ранга, код выводит его непосредствен ных подчиненных, добавляя перед их именами символ табуляции, затем Ч под Работа с реляционными данными ГЛАВА чиненных этих подчиненных и так до тех пор, пока не останется сотрудников, имеющих кого-либо в подчинении.

Но достаточно объяснений Ч подать сюда код!

Visual Basic.NET Dim row As Dataflow For Each row In tbl.Rows If row.IsNull("ReportsTo") Then DisplayRow(row, "") End If Next row Public Sub DisplayRow(DataRow row, string strlndent) Console.WriteLine{strIndent i rowC'EmployeeName")) Dim rowChild As DataRow For Each rowChild in row.GetChildRows("SelfReferencing") DisplayRow(rowChild, strlndent & vbTab) Next rowChild End Sub Visual C#.NET foreach (DataRow row in tbl.Rows) if (row.IsNull("ReportsTo")) DisplayRowfrow, "");

static votd DisplayRow(DataRow row, string strlndent) { Console.WriteLine(strIndent + row["EmployeeName"]};

foreach (DataRow rowChild in row.GetChildRows("SelfReferencing")) DisplayRow(rowChild, strlndent + "\t"};

Примечание Руководителю наивысшего ранга соответствует запись таблицк Employees, значение поля ReportsTo которой Ч Л7ДХ. Кроме того, мож но сделать так, чтобы сотрудник подчинялся сам себе. Если вы имеете дело с таблицей, в которой реализован аналогичный подход к ссылаю щимся на себя отношениям, вам потребуется слегка изменить данный фрагмент кода.

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

рассмотрим таблицы authors и titles БД pubs SQL Server.

Часть III Автономная работа с данными: объект DataSet модели ADO.NET Предположим, что между данными этих таблиц существует отношение мно гие ко многим, поскольку один автор мог написать несколько книг, а у одной книги может быть несколько авторов. Тем не менее эти две таблицы не связаны напря мую через ограничение ForeignKeyConstraint, поскольку это ограничение требует наличия уникального ключа. В связи с этим у дочерней записи не может быть несколько родительских записей в связанной таблице, т, е. здесь нет непосредствен ного отношения многие ко многим.

В БД pubs также есть таблица titleauthor (рис. 7-5), позволяющая создать кос венное отношение многие ко многим. В данной таблице определен составной первичный ключ, включающий поля au_id и title_id Ч поля первичного ключа таблиц authors и titles соответственно.

Рис. 7-5. Таблицы authors, titles и titleauthor БД pubs SQL Server Предположим, что над одной из книг работали два автора. В таблице titleauthor для такой книги будет две записи Ч по одной для каждого автора. Таким образом, таблица titleauthor позволит отыскать значения первичного ключа для всех авто ров конкретной книги. Точно так же выполняется поиск значения первичного ключа всех книг, которые автор написал самостоятельно или в работе над кото рыми принимал участие.

Следующий код получает данные из всех трех таблиц. Он добавляет объекты DataRelation, связывающие таблицу authors и titleauthor, а также таблицу titles и titleauthor. Далее код просматривает записи объекта DataTable authors, выводит имя автора и затем при помощи объектов DataRelation выводит написанные этим ав тором книги.

Visual Basic.NET Dim strConn, strSQL. as string strConn = "Provider=SQLOLEDB;

Data Source=(local)\NetSDK;

" & ГЛАВА 7 Работа с реляционными данными "Initial Catalog=Northwind;

Trusted_Connection=Yes;

" Dim en As New OleDbConnection(strConn) cn.OpenO Dim daAuthors, daTitles, daTitleAuthor As OleDbDataAOapter strSQL = "SELECT au_id, au_lname, au^fname FROM authors" daAuthors = New 01eDbDataAdapter(strSQL, en) StrSQL = "SELECT title.id, title FROM titles" daTitles = New 01eDbDataAdapter{strSQL, en) strSQL = "SELECT au_id, title.id FROM titleauthor" daTitleAuthor = New 01eDbDataAdapter(strSQL, en) Dim ds As New DataSetQ daAuthors.Fill(ds, "authors") daTitles.Fill(ds, "titles") daTitleAuthor.Fill(ds, "titleauthor") cn.CloseO ds.Relations.Add{"authors_titleauthor", _ ds.TablesC"authors").Columns("au_id"), _ ds.Tablesf"titleauthor").Columnsf"au_id"), False) ds.Relations.Add("titles_titleauthor", _ ds.Tablest"titles").Columns("title_id"), _ ds.Tables("titleautnor"),Columns("title_id"), False) Dim rowAuthor, rowTitle, rowTitleAuthor As DataRow For Each rowAuthor In ds.Tables("authors").Rows Console.WriteLine(rowAuthor("au_lname") & ", " & rowAuthor("au_fname")) For Each rowTitleAuthor In _ rowAuthor.GetChildRowsC'authors_titleauthor") rowTitle = rowTitleAuthor.GetParentRow("titles_titleauthor") Console.WriteLine(vbTab & rowTitle("title")) Next rowTitleAuthor Next rowAuthor Visual C#.NET string strConn, strSQL;

strConn = "Provider=SQLOLEDB;

Data Source=(local)\\NetSDK;

" + "Initial Catalog=Northwind;

Trusted_Connection=Yes;

";

OleDbConnection en = new OleDbConnection(strConn);

cn.OpenO;

OleDbDataAdapter dsAuthors, dsTitles, dsTitleAuthor;

strSQL = "SELECT au^id, au_lname, au_fname FROM authors";

daAuthors = new 01eDbDataAdapter(strSQL, en) strSQL = "SELECT title.id, title FROM titles";

daTitles = new 01eDbDataAdapter(strSQL, en);

strSQL = "SELECT au_id, title^id FROM titleauthor";

daTitleAuthor = new 01eDbDataAdapter(strSQL, en);

274 Часть III Автономная работа с данными: объект DataSet модели ADO.NET DataSet ds = new DataSetO;

daAuthors. Fill{ds, "authors"};

daTitles.FillCds, "titles");

daTitleAuthor.Fill(ds, "titleauthor");

cn.CloseO;

ds.Relations.Add("authors_titleauthor", ds.Tables["authors"].Columns["au_id"], ds.Tables["titleauthor"].Columns["au_id"], false);

ds.Relations.Add("titles_titleauthor", ds.Tables["titles"].Columns["title_id"], ds.Tables["titleauthor"].Columns["title_ld"], false);

foreach {DataRow rowAuthor in ds.Tables["authors"].Rows) { Console.Writel_ine(rowAuthor["au_lname"] + ", " + rowAuthor["au_fname"]);

foreach (DataRow rowTitleAuthor in rowAuthor.GetChildRows("authors_titleauthor")) { DataRow rowTitle;

rowTitle = rowTitleAuthor.GetParentRow("titles_titleauthor");

Console.WriteLine("\t" + rowTitle["title"]);

Использование объектов DataReiation в объектах DataColumn, основанных на выражениях В главе 6 я рассказывал, как с помощью свойства Expression объекта DataColumn создавать объекты DataColumn, отображающие результаты уравнений типа Quantity ' UnitPrice. Кроме того, основанные на выражениях объекты DataColumn можно использовать совместно с объектами DataRelation для вычисления агрегатных данных, например числа дочерних записей, а также суммы и средних значений дочерних данных.

Далее показаны два примера использования объектов DataRelation в свойстве Expression объекта DataColumn. Сначала код создает отношение между объектами DataTabie Orders и Order Details, а затем добавляет в объект Data-Table Order Details вычисляемый объект DataColumn ItemTotal. После этого код добавляет два осно ванных на выражениях объекта Data-Column, использующих новое отношение.

Первый объект DataColumn возвращает число дочерних записей. Чтобы полу чить его, код задает свойству Expression объекта DataColumn следующее значение:

Count(Child.ProductlD) Данный синтаксис можно использовать для ссылки на дочерние данные, только если у родительского объекта DataTabie есть единственный связанный дочерний ГЛАВА 7 Работа с реляционными данными объект DataTable. Если дочерних объектов несколько, воспользуйтесь таким син таксисом:

Count(Child(RelationName).ProductID) Второй объект DataColumn возвращает сумму значений объекта DataColumn ItemTotal в дочернем объекте DataTable. Свойству Expression данного объекта DataColumn задается значение, похожее на значение одноименного свойства пер вого объекта DataColumn:

Sum(Child,ItemTotal) Visual Basic.NET Dim ds As New DataSetQ ds.Relations.Add("OrdersOrderDetails", ds,Tables("Orders").Columns("QrderID"), ds.Tables("Order Details").Columns("OrderID")) ds.Tables("Order Details").Columns.Add("ItemTotal", GetType(Decimal), "Quantity - UnitPrice") ds.Tables("Orders").Columns.Add("NumItems", GetType(Integer), "Count(ChiLd.ProductlD)") ds.Tables("Orders"),Columns.Add("OrderTotal", GetType(Decimal), "Sum(Child.ItemTotal)") Visual C#.NET DataSet ds = new DataSetO;

ds.Relations.Add("OrdersOrderDetails", ds.Tables["Qrders"].Columns["OrderID"], ds.Tables["Order Details"].Columns["OrderID"]);

ds.Tables["Order Details"].Columns.Add("ItemTotal", typeof(Decimal), "Quantity * UnitPrice");

ds.Tables["Orders"].Columns.Add("NumItems", typeof(int), "Count(Child.ProductlD)");

ds.Tables["Orders"],Columns.Add("OrderTotal", typeof(Decimal), "Sum(Child.ItemTotal)");

Основанные на выражениях объекты DataColumn также позволяют получать информацию из участвующего к отношении родительского объекта DataTable. В одном из предыдущих примеров мы рассматривали отношение многие ко мно гим* между таблицами author и title БД pubs: фрагмент кода выводил список книг, которые автор написал сам или в работе над которыми принимал участие.

Можно упростить этот код, добавив в объект DataTable titleauthor основанный на выражении объект DataColumn, который использует отношение между объек тами DataTable titleauthor и title и возвращает значение объекта DataColumn title.

После этого нам больше не придется искать требуемую запись в объекте DataTable с помощью GetParentRow.

276 Часть III Автономная работа с данными: объект DataSet модели ADO.NET Visual Basic.NET Dim ds As New DataSetO ds.Relations.Add("authors_titleauthor", _ ds.TablesC"authors").ColumnsC"au_id"), _ ds.Tables("titleauthor").Columns{"au_id"), False) ds.Relations,Add("titles_titleauthor", _ ds.Tables("titles").Columns("title_id"), ds.Tables("titleauthor").Columns("title_id"), _ False) ds.TablesC"titleauthor").Columns.Add("title", GetType(String), _ "Parent(titles_titleauthor).title") Dim rowAuthor, rowTitleAuthor As Dataflow For Each rowAuthor In ds.TablesC"authors").Rows Console.WriteLine(rowAuthorC"au_lname") & ", " A rowAuthor("au_fname")) For Each rowTitleAuthor In rowAuthor.OetChildRows("authors_titleauthor") Console.Writel_ine(vbTab & rowTitleAutnor("title")) Next rowTitleAuthor Next rowAuthor Visual C#.NET DataSet ds = new DataSetO;

ds.Relations.Add("authors_titleauthor", ds,Tables["authors"].Columns["au_id"], ds.Tables["titleauthor"].Columns["au_id"], false);

ds.Relations.AddC"titles_titleauthor", ds.Tables["titles"].Columns["title_id"], d$.Tables["titleauthor"].Columns["title_id"], false);

ds.Tables["titleauthor"].Columns.Add{"title", typeof(string), "Parent(titles_titleauthof).title");

foreach (DataRow rowAuthor in ds.Tables["authors"].Rows) { Console.WriteLine(rowAuthor["au_lname"] + ", " + rowAuthor["au_fname"]>;

foreach (DataRow rowTitleAuthor in rowAuthor.GetChildRows("authors_titleauthor")) ГЛАВА 7 Работа с реляционными данными Console.WriteLine("\t" + rowTitleAuthor["title"]);

Полный список агрегатных функций для свойства Expression Ч в документа ции MSDN.

Каскадирование изменений Иногда изменения содержимого записи должны отражаться на связанных дан ных. Например, удалив заказ, вы, возможно, захотите удалить и входившие в него товары.

Различные СУБД по-разному разруливают данную ситуацию. Ограничение FOREIGN KEY в таблице Order Details БД Northwind SQL Server гарантирует, что пользователь не сможет удалить из таблицы Orders запись, связанную с записями таблицы Order Details. В SQL Server 2000 реализована поддержка каскадных изме нений на основе ограничения FOREIGN KEY. Определяя ограничения FOREIGN KEY вы можете быть уверены, что при обновлении или удалении записи изменения автоматически каскадируются в записи связанной таблицы. На рис. 7-6 показа но диалоговое окно SQL Server 2000 для настройки параметров ограничения FOREIGN KEY.

Efforts relationship I;

: INSERTS anc UPMiSs - свивА jpHtoto Jidatci Fields Рис. 7-6. Настройка параметров каскадирования для ограничения FOREIGN KEY в SQL Server Объект ForeignKeyConstraint модели ADO.NET обладает аналогичными возмож ностями. Он предоставляет свойства DeleteRule и UpdateRule, определяющие, что произойдет при изменении данных записи родительской таблицы, на которой определено ограничение FOREIGN KEY.

Свойства DeleteRule и UpdateRule объекта ForeignKeyConstraint Свойства DeleteRule и UpdateRule принимают значения из перечисления Rule, от носящегося к пространству имен SystemData. Значение обоих этих свойств по умолчанию Ч Cascade, т. е. при удалении записи родительского объекта DataTable 278 Часть III Автономная работа с данными: объект DataSet модели ADO.NET также удаляются дочерние записи связанного объекта DataTable. Если вы измените значение поля в родительском объекте DataTable, на котором определено огра ничение FOREIGN KEY (например, измените значение поля CustomerlD объекта DataTable Customers), это значение также обновится и в связанных записях до чернего объекта DataTable, Другие возможные значения свойств DeleteRule и UpdateRule Ч None, SetDefault и SetNutt. Если значение свойства DeleteRule Ч None, связанные данные из дочер него объекта DataTable не удаляются. Если нужно изменить родительскую запись и задать одному из задействованных в ограничении полей значение NULL, задай те свойству DeleteRule и/или свойству UpdateRule значение NULL. Точно так же, если задать этим свойствам значение SelDefault, полям дочерней записи, задействован ным в ограничении FOREIGN KEY, будет задано значение свойства Default.

Постепенный отказ от соединяющих запросов Многие разработчики получают данные из нескольких таблиц при помощи со единяющих запросов. Конечно, вы имеете право поместить результаты запроса, возвращающего данные нескольких таблиц, в объект DataTable, но обычно я не рекомендую этот способ. Как рассказывается в главе 10, объект DataAdapter пред назначен для просмотра изменений, хранящихся в отдельном объекте DataTable и передачи их в конкретную таблицу вашей БД. Таким образом, для изменения содержимого объектов DataTable данные следует поместить в отдельные объекты DataTable, соответствующие таблицам БД.

И что же нам делать с соединяющими запросами?

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

SELECT CustomerlD, CompanyName, ContactName, Phone FROM Customers WHERE Country = 'Canada' Если вам нужны только данные из связанной таблицы Orders, следует получить только сведения о заказах, которые разместили возращенные этим запросом кли енты. Поскольку в таблице Orders нет столбца Country, придется воспользоваться запросом, ссылающимся на таблицу Customers:

SELECT 0. Order-ID, 0. CustomerlO, 0. Employ ее ID, O.OrderDate FROM Customers C, Orders WHERE C.CustomerlD = O.OrderlD AND C.Country = 'Canada ГЛАВА 7 Работа с реляционными данными Создание объектов DataRelation в Visual Studio.NET Обсудив основные возможности объектов DataRelation, давайте разберем, как создавать их в Visual Studio.NET.

Добавление объекта DataRelation в объект DataSet со строгим контролем типов В главе 6 я рассказал, как создавать объекты DataSet со строгим контролем типов средствами Visual Studio.NET. Чтобы просмотреть структуру объекта DataSet в конструкторе XML Schema Designer, дважды щелкните файл проекта DataSet (*.xsd) в окне Solution Explorer.

Щелкните объект DataTable, который станет в отношении родительским, пра вой кнопкой и выберите Add\New Relation (рис. 7-7). Можно также воспользоваться меню Schema среды Visual Studio.NET. Откроется диалоговое окно (рис. 7-8). по зволяющее добавить новый объект DataRelation.

*, lViiiiloлHAp|ilir.)ln>n1 Microuifl I'isii..! (3.14,1 KM Г(|Щ(.п! OalaSeM.xtd Рис, 7-7. Открытие диалогового окна Edit Relation В диалоговом окне Edit Relation для нового объекта DataRelation. задают дочерние и родительские объекты DataTable и DataColumn. Кроме того, здесь можно задать значения свойств UpdateRule, DeleteRule и AcceptRejectRule ограничения ForeignKey Constraint. связанного с новым объектом DataRelation. Если необходимо создать только ограничение ForeignKeyConstraint и новый объект DataRelation не требу ется, пометьте флажок Foreign Key Constraint Only.

Щелкните ОК. В конструкторе XML Schema Designer появится графическое представление нового объекта DataRelation Ч линия : соединяющая два объекта DataTable (рис. 7-9).

Часть III Автономная работа с данными: объект DataSet модели ADO.NET Ъз define в -Atiorship (toyref), select ФерачЧ etemert and Vey, selлt th9 cMd element, nd rher. *elKt*ta child *И <иги[щлч*чСй each potent field.

Data* Properties Х Х Г" счл* Рсгявл toy conrtralnt a Рис. 7-8. Добавление нового объекта DataRelation в диалоговом окне Edit Relation Eto E* Bлл 5 н ф| цюЬ Sindm* ttлtp.л** &*Х e*ug l! Х- eljijo Ш- S' В? в в.S- fe ^ Х PtdpHfei * X fbjitmrieriorderi -'s:keyrff>^3j JjJ ЕЗВ" Х>- Х;

-i..":.jslo.n L' Х iriq ftOdsrint j| ComtrerttJa t CustometlD ttrmg ^ EmployeelD in[ F. OrderM-- ditaTi intKtrtle f.

Idress fl-jng *[ RgqumdCut datsTir nfar mstns:keyl fsiiactor "./,'mлn.*Jrder JBdatfRule (Dsfault) ddubngan-i и* РГРл >te [O Рис. 7-9- Новый объект DataRelation Добавление объекта DataRelation в объект DataSet без контроля типов Объекты DataRelation разрешается также добавлять в объекты DataSet без конт роля типов. В предыдущей главе рассказывалось, как добавить объект DataSet без контроля типов в область проектирования, например на Web-форму, и затем до Работа с реляционными данными ГЛАВА бавить в него объекты DataTable и DataColumn. Добавить новый объект DataRelation настолько же просто.

Выберите объект DataSet в панели компонентов области проектирования и затем выберите свойство Relations в окне Properties. В правой части строки свойства появится кнопка для запуска мастера Relations Collection Editor (рис. 7-10).

'- Relation* СоИееЙип Editor Relation 1 Properties:

" " '.' В,...

ggmowe j Close M'rT Рис. 7-10. Добавление нового объекта DataRelation в объект DataSet без контроля типов при помощи мастера Relations Collection Editor Мастер Relations Collection Editor позволяет добавлять, редактировать и удалять объекты DataRelation. При добавлении нового или редактировании имеющегося объекта DataRelation открывается диалоговое окно Edit Relation (рис. 7-8).

Особенности объекта DataRelation Если честно, справочная информация по объекту DataRelation не особенно инте ресна. Объект не предоставляет каких-либо методов и событий, он обладает только свойствами. Тем не менее не рассказать о них означало бы обмануть чьи-то ожи дания.

Свойства объекта DataRelation Большинство свойств объекта DataRelation доступно только для чтения. Задать их значение можно средствами конструкторов объекта DataRelation. В табл. 7-1 пе речислены наиболее часто используемые свойства объекта DataRelation.

Свойство ChildColumns Свойство CbildColumns возвращает массив, содержащий объекты DataColumn из участвующего в отношении дочернего объекта DataTable. Данное свойство доступно только для чтения.

Часть III Автономная работа с данными: объект DataSet модели ADO.NET Наиболее часто используемые свойства объекта DataRelation Таблица 7-1.

Хж*к1иж1тшиаяяяя& Свойство Тип данных Описание Массив объектов Указывает дочерние столбцы, определяющие CbildColmnns отношение- Доступно только для чтения DataColumn Указывает ограничение FOREIGN KEY в уча CbiidKeyConstraint ForeignKeyConstraint ствующей в отношении дочерней таблице, Доступно только для чтения Указывает дочернюю таблицу в отношении.

СЫШТаЫе DataTable Доступно только для чтения DataSet Указывает объект DataSet, в котором нахо DataSet дится объект Dai'aRetation. Доступно только для чтения Указывает набор динамических свойств ExtendedProperties Property/Collection Nested Boolean Указывает, нужно при преобразовывать до черние записи в дочерние объекты при за писи содержимого объекта DataSet в XML файл Массив объектов Указывает родительские столбцы, определяю ParcntColumns DataColumn щие отношение. Доступно только для чтения ParentKeyConstraint UniqueConstraint Указывает ограничение UNIQUE в участвую* щей в отношении: родительской таблице. До ступно только для чтения ParentTable DataTable Указывает родительскую таблицу в отноше нии. Доступно только для чтения RelationName String Указывает имя отношения Свойство ChildKeyConstraint Свойство ChildKeyConstraint возвращает ограничение ForeingKeyConstraint, на ко торое ссылается объект DataRelation. Если ваш объект DataRelation не использует ограничения, свойство ChildKeyConstraint возвращает Nothing или null, в зависимости от языка программирования. Данное свойство доступно только для чтения.

Свойство ChildTable Свойство ChildTable возвращает участвующий в отношении дочерний объект Data Table. Данное свойство доступно только для чтения.

Свойство DataSet Свойство DataSet возвращает объект DataSet, в котором находится объект Data Relation. Данное свойство доступно только для чтения.

Свойство ExtendedProperties Объект DataRelation, как и объекты DataSet, DataTable и DataColumn, предостав ляет свойство ExtendedProperties, позволяющее хранить дополнительную инфор мацию.

Свойство Nested Свойство Nested управляет размеще! шем содержимого дочернего объекта DataTable при записи содержимого объекта DataSet в XML-файл с помощью метода Data ГЛАВА 7 Работа с реляционными данными SetWriteXml. Это одно из немногих свойств объект:) Data-Relation, значение кото рого разрешается изменять.

Если значение свойства Nested Ч False (используется по умолчанию), данные дочернего и родительского объектов DataTable хранятся раздельно. Как показы вает следующий фрагмент кода, если предварительно не просмотреть информа цию схемы объекта DataSet, не скажешь, что есть объект DataRelatton, связываю щий оба объекта DataTable:

Данные дочернего и родительского объектов DataTable хранятся раздельно ALFKK/CustomerID> Alfreds Futterkiste ANATR Ana Trujillo Emparedados у helados <0rders> 10308 ANATR <0rders> 10355 AROUT При значении True ADO.NET вкладывает содержимое дочернего объекта Data Table в данные родительского объекта DataTable:

Данные дочернего объекта DataTable вложены в родительский объект ALFKK/Custome r!D> Alfreds Futterkiste <0rders> 10643 ALFKK/CustomerID> <0rders> 10692 ALFKI ANATn Ana Trujillo Emparedados у helados Автономная работа с данными: объект DataSet модели ADO.NET 284 Часть III <0rders> 10308 ANATR <0rders> 10625 ANATR Свойство ParentColumns Свойство ParentColumns возвращает массив, содержащий объекты DataColumn из участвующего в отношении родительского объекта DataTable. Данное свойство доступно только для чтения, Свойство ParentKeyConstraint Свойство ParentKeyConstraint возвращает ограничение UniqueConstraint, на кото рое ссылается объект DataRelation. Если ваш объект DataRelation не использует ограничения, свойство ParentKeyConstraint возвращает Nothing или null Ч в зависи мости от языка программирования. Данное свойство доступно только для чтения, Свойство ParentTable Свойство ParentTable возвращает участвующий в отношении родительский объект DataTable. Данное свойство доступно только для чтения.

Свойство RelationName Свойство RelationName доступно как для чтения, так и для записи и позволяет получать и задавать имя объекта DataRelation.

Вопросы, которые стоит задавать почаще Вопрос. Когда нужно создавать объекты DataRelation без ограничений?

Ответ. Предположим, ваше приложение получает из БД данные о клиентах и заказах и выводит их в простой Web-форме. Приложение позволяет только про сматривать, но не редактировать информацию.

В таком приложении полезен объект DataRelation, с помощью которого легко вывести список заказов конкретного клиента. Однако ограничения в объекте DataSet скорее всего не понадобятся. Почему? Ограничения применяются для проверки данных, требующей времени. Если данные в приложении доступы только для чтения и БД уже проверила их средствами собственного набора ограничений, ADO.NET нет смысла повторно проверять эти данные.

Вопрос. Я собираюсь работать с данными нескольких таблиц, но не буду выби рать все записи родительской таблицы. Приведенные в главе запросы, которые выбирают только связанные дочерние записи, кажутся мне сложными и неэффек ГЛАВА 7 Работа с реляционными данными тивными. Разве отдельный основанный на соединении запрос не будет в этой ситуации быстрее?

Ответ. В период тестирования бета-версии Microsoft,NET Framework в одной из групп новостей развернулось широкое обсуждение, да иного вопроса. Двое разра ботчиков пришли к выводу, что выборка данных от отдельных запросов неэффек тивна. Их предпосылки были логичными и разожгли мое любопытство, и я решил проверить, как применение отдельных запросов влияет на производительность.

Я создал приложение на Visual Basic.NET, использующее ADO.NET, и приложение на Visual Basic, работающее с ADO 2.6. Оба приложения выбирали из таблиц Custo mers, Orders и Order Details БД Northwind информацию о клиентах, находящихся в США. Для каждого приложения я написал различные процедуры, получавшие данную информацию с использованием запросов разного типа.

Процедура, тестировавшая соединяющий запрос, просто выбирала данные в отдельный объект DataTable, для которого не были заданы ограничения. Я исполь зовал следующие запросы:

Запрос, основанный на соединении SELECT C.CustomerlD, С.CompanyName, С,ContactName, С.Phone, O.OrderlD, O.EmployeelD, Q.OrderDate, D.ProductID, D.Quantity, D.UnitPrlce FROM [Order Details] D, Orders 0, Customers С WHERE D.OrderlD = O.OrderlD AND O.CustomerlD = C.CustomerlD AND C.Country = N'USA' Отдельные запросы SELECT CustomerlD, CompanyName, ContactName, Phone FROM Customers WHERE C.Country = N'USA' SELECT O.OrderlD, 0.CustomerlD, 0.EmployeelD, O.OrderOate FROM Orders 0, Customers С WHERE O.CustomerlD = C.CustomerlD AND C.Country = N'USA' SELECT D.OrderlD, D.ProductID, D.Quantity, D.UnitPrice FROM [Order Details] D, Orders 0, Customers С WHERE D.OrderlD = O.OrderlD AND O.CustomerlD = C.CustomerlD AND C.Country = N'USA' Параметризованные запросы SELECT CustomerlD, CompanyName, ContactName, Phone FROM Customers WHERE C.Country = N'USA SELECT OrderlD, CustomerlD, EmployeelD, OrderDate FROM Orders WHERE CustomerlD = ?

SELECT OrderlD, ProductID, Quantity, UnitPrice FROM [Order Details] WHERE OrderlD = ?

Автономная работа с данными: объект DataSel модели ADO.NET 286 Часть III Как и многие другие участники группы новостей, я ожидал, что производитель ность соединяющего и параметризированных запросов окажется выше произво дительности отдельных запросов. К счастью, прежде чем поделиться своим мне нием с другими, я провел ряд тестов. Оказалось, что отдельные запросы работали быстрее всех. В среднем, их производительность были на 20% выше производи тельности соединяющего запроса и в 6-8 раз выше производительности парамет ризованных запросов.

Почему? Ну, оптимизация запросов Ч не самая сильная моя сторона, но, пола гаю, я все же смогу объяснить данный факт. Отдельные запросы вернули данные быстрее, чем один соединяющий запрос, поскольку последний возвращал боль ше данных. Соединяющий запрос возвращает избыточные данные, В таблице Order Details может оказаться 100 записей, соответствующих одной записи таблицы Customers, и каждая из них содержит одинаковую информацию.

Я воспользовался методом WriteXml объекта DataSet (подробнее о нем Ч в гла ве 12) и записал содержимое объекта DataSet в XML-файл. Размер XML-файла, со ответствовавшего объекту DataSet с результатами соединяющего запроса, почти в три раза превышал размер XML-файла, соответствовавшего объекту DataSet с ре зультатами отдельных запросов. Соединяющий запрос может показаться более эф фективным, потому что с ним проще работать, однако в действительности это не так, поскольку он возвращает больше данных.

Для тестирования параметризованных запросов я выполнил простой запрос к таблице Customers и поместил его результаты в объект DataTable, Затем я стал просматривать эти результаты и единожды для каждого клиента выполнял пара метризованный запрос к таблице Orders. Далее я просматривал совмещенные результаты запросов к таблице Orders и единожды для каждого запроса выполнял параметризованный запрос к таблице Order Details. При этом мне пришлось вы полнять отдельные запросы для каждого клиента и каждого заказа. Структура па раметризованных запросов проста, однако их многократное выполнение для каж дого клиента и заказа оказалось неэффективным, и производительность снизилась.

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

Примечание Я также запускал две дополнительные процедуры, одна из кото рых применяла вложенные запросы IN, а другая объединяла отдельные запросы в пакет. Производительность вложенных запросов IN оказалась сравнима с производительностью процедуры, использовавшей отдель ные запросы. Объединив отдельные запросы в пакет, я смог повысить производительность примерно на 8%.

Вопрос. Я выбираю данные из хранимых процедур, но, к сожалению, не могу использовать в них соединяющие запросы. Есть ли простой способ разделить* результаты на отдельные объекты DataTable?

ГЛАВА 7 Работа с реляционными данными Ответ. Осуществляющего это метода в модели ADO.NET нет Ч по крайней мере:

пока. Однако можно написать код, разделяющий результаты посредством объек та DalaReader и метода Find набора Rows объекта DataTable (подробнее об этом методе Ч в следующей главе).

Следующий фрагмент кода выполняет соединяющий запрос к таблицам Custo mers и Order Details. Затем он просматривает возвращаемые в объект DataReader данные и добавляет новые записи в отдельные объекты DataTable Customers и Orders.

Visual Basic.NET ds.Tables("Customers")-BegtnLoadDataС) ds,Tables("Orders").BeginLoadData{) Dim strConn, strSQL As String strConn = "Provider=SQLOLEDB;

Data Source=(local)\NetS[>K;

" & "Initial Catalog=Northwind;

Trusted_Connection=Yes;

" Dim on As New OleDbConnection(strConn) en.Openf) strSQL = "SELECT C.CustomerlD, C.CompanyName, C.ContactName, " i "C.Phone, O.OrderlD, O.EmployeelD, O.OrderDate " & _ "FROM Customers C, Orders 0 " & _ "WHERE C.CustomerlD = O.CustomerlD AND C.Country = 'Canada'" Dim cmd As New 01eDbCommand{strSQL, en) Dim rdr As OleDbDataReader = cmd.ExecuteReader Dim objNewCustomer, objNewOrder As Object() Do While rdr.Read If ds.Tables("Customers").Rows.Find(rdr.GetString{0)> Is Nothing Then objNewCustomer = New ObjectO {rdr.GetString(O), rdr.GetString(l), rdr,GetString(2), rdr.GetStringO)} ds.Tablesf"Customers").LoadDataRow(objNewCustomer, True) End If objNewOrder = New Object{) {rdr.Get!nt32(4), rdr.GetString(O), rdr.Geлnt32(5), rdr.6etDateTime(6)} ds.TablesC"Orders").LoadDataRow(objNewOrder, True) Loop rdr.Close() cn.CloseO ds.Tables("Customers").EndLoadDataf) ds.TablesC"Orders"). EndLoadDataQ Visual C#.NET ds.Tables["Customsrs"].BeginLoadData();

ds.Tables["Orders"].BeginLoadData();

string strConn, strSQL;

StrConn = "Provider=SQLOLEDB;

Data Source=(local)\\NetSDK;

" + "Initial Catalog=Northwind;

Trusted_Connection=Yes;

";

288 Часть 111 Автономная работа с данными: объект DataSet модели ADO.NET OleDbConnection en = new 01eDbConnection{strConn);

cn.OpenO;

strSQL = "SELECT C,CustomerlD, C.CompanyName, C.ContactName, " + "C.Phone, O.OrderlD, 0.EmployeelD, O.OrderDate " + "FROM Customers C, Orders 0 " + "WHERE C.CustomerlD = 0.CustomerlD AND C.Country = 'Canada'";

QleDbCommand cmd = new 01eDbCommand(strSQL, en);

OleDbDataReader rdr = cmd. ExecuteReaderO;

obj ect[] objNewCustomer, obj NewOrde r;

while (rdr.ReadO) ( if (ds.Tables["Customers"].Rows.Find(rdr.GetString(0)) == null) < objNewCustomer = new object[] (rdr.GetString(O), rdr.GetString{1), rdr.GetString{2), rdr.GetString(3)};

ds.Tables["Customers"].LoadDataRow(objNewCustomer, true);

} objNewOrder = new object[] {rdr.Get!nt32(4), rdr.GetString(O), rdr.Get!nt32(5), rdr.GetDateTime(6)};

ds.Tables["Orders"].LoadDataRow(ob]NewOrder, true);

!

rdr.CloseO;

cn.Close{);

ds. Tablest"Customers"].EndLoadData();

ds.Tablest"Orders"].EndLoadData{);

ГЛАВА Сортировка, поиск, фильтрация Г> главе 5 рассказывалось, как с помощью объекта DataAdapter выбрать резуль таты запроса в объект DataSet. В главе б, посвященной объекту DataSet и его вло женным объектам, вы научились просматривать результаты таких запросов, пе ремещаясь по объектам DataRow объекта DataTable.

Но как найти нужную запись в объекте DataTable по значению или группе зна чений? Как применить фильтр, чтобы отображались только записи, удовлетворя ющие заданному критерию? Как управлять порядком сортировки записей, к ко торым вы собираетесь обращаться или выводить пользователю?

Здесь даются ответы на все эти вопросы, а также обсуждается метод Fmrf клас са DataRowCollection, метод Select класса DataTable и рассматриваются объекты DataVieiv и DataRowVieiv.

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

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

290 Часть III Автономная работа с данными: объект DataSel модели ADO.NET SELECT CustomerlD, CompanyName, ContactName, Phone FROM Customers WHERE CustomerlD = 'ALFKI' Объект DataRow тоже можно искать в объекте DataTable по значениям первич ного ключа записи. Вероятно, вы заметили, что последний фрагмент кода в гла ве 7 просматривал результаты запроса и должен был определить, есть ли опреде ленный ряд данных в объекте DataTable. В том коде использовался метод Find для поиска в содержимом DataTable по значению первичного ключа.

Несмотря на то, что метод Find предназначен для объектов DataTable, его пре доставляет класс DataRowCollection. Метод Find принимает объект, содержащий значение первичного ключа искомой записи. Поскольку значения первичного уникальны, метод Find вернет не более одного объекта DataRow. Следующий фраг мент кода ищет клиента по его значению первичного ключа и затем определяет, найдена ли запись.

Visual Basic.NET Dim strConn, strSQL As String strConn = "Provider=SQLOLEDB;

uata Source=(local)\NetSDK;

" & _ "Initial Catalog=Northwind;

Trusted_Connection=Yes;

" strSQL = "SELECT CustomerlD, CompanyName, ContactName, Phone " & _ "FROM Customers" Dim da As New 01eDbDataAdapter(strSQL, strConn) Dim tbl As New DataTableO da.Fill(tbl) tbl.PrimaryKey = New DataColumn() {tbl.Columns("CustomerID")} Dim row As DataRow = tbl.Rows.Find("ALFKI") If row Is Nothing Then Console.WriteLine("Row not found!") Else Console.WriteLine(row("CompanyName")) End If Visual C#.NET string strConn, strSQL;

strConn = "Provider=SQLOLEDB;

Data Source=(local)\\NetSDK;

" + "Initial Catalog=Northwind;

Trusted_Connection=Yes;

";

strSQL = "SELECT CustomerlD, CompanyName, ContactName, Phone " + "FROM Customers";

OleDbDataAdapter da = new 01eDbDataAdapter(strSQL, strConn);

DataTable tbl = new DataTableO;

da.Fill(tbl);

tbl.PrimaryKey = new DataColumn[] {tbl.Columns["CustomerID"]};

DataRow row = tbl.Rows.FindC'ALFKI");

if (row == null) Console.WriteLine("Row not found!");

else Console.WriteLine(row["CompanyName"]);

Сортировка, поиск, фильтрация ГЛАВА Примечание В принципе, объект DataTable может содержать несколько запи сей с одинаковыми значениями первичного ключа. Если задать свойству EnforceConstraints объекта DataSet значение False, при нарушении огра ничения PRIMARY KEY объект DataTable не будет генерировать исклю чение. В результате метод Find вернет все записи с искомым значением первичного ключа.

Метод Find перегружен в расчете на случаи, когда первичный ключ вашего объекта DataTable состоит из нескольких объектов DataColumn. Например, пер вичный ключ таблицы Order Details основан на столбцах OrderlD и ProductlD. Тогда для поиска записи в объекте DataTable с похожей схемой следует использовать такой код:

Visual Basic.NET Dim strConn, strSQL As String strConn = "Provider=SQLOLEDB;

Data Source=(local)\NetSDK;

" & "Initial Catalog=Northwind;

Trusted_Connection=Yes;

" strSQL = "SELECT OrderlD, ProductlD, Quantity, UnitPrice " & _ "FROM [Order Details]" Dim da As New 01eDbDataAdapter(strSQL, strConn) Dim tbl As New DataTable(> da.Fill(tbl) tbl.PrimaryKey = New DataColumnQ {tbl.Columns("OrderID"), tbl.Colunns("ProductlD")} Dim objCriteria As New Objectf) {10643, 28} Dim row As DataRow = tbl.Rows.Find(objCriteria) If row Is Nothing Then Console.WriteLine("Row not found!") Else Console.WriteLine(row("Quantity") & " - " & row("UnitPrice")) End If Visual C#.NET string strConn, strSQL;

strConn = "Provider=SQLOLEDB;

Data Source=(local)\\NetSDK;

" + "Initial Catalog=Northwind;

Trusted_Connection=Yes;

";

strSQL = "SELECT OrderlD, ProOuctID, Quantity, UnitPrice " + "FROM [Order Details]";

OleObOataAdapter da = new QleDbDataAdapter(strSQL, strConn);

DataTable tbl = new DataTableO;

da.Fill(tbl);

tbl.PrimaryKey = new DataColumn[] {tbl.Columns["OrderID"], tbl.Columnst"ProductlD"]};

object[] objCriteria = new object[] {10643, 28};

DataRow row = tbl.Rows.Find(objCriteria);

if {row == null) 292 Часть III Автономная работа с данными: объект DataSet модели ADO.NET Console.WrlteLlne("Row not found!");

else Console.WriteLine(row["Quantity"] + " - " + row["UnitPrice"]);

Динамичный поиск Поиск записи по значениям первичного ключа эффективен, но далеко не во всех ситуациях поиск окажется таким прямолинейным. Что, если требуется найти кли ентов из США (USA), живущих не в Сиэтле (Seattle)? Добавить такой критерий в запрос к БД можно при помощи раздела WHERE:

SELECT CustomerlD, CompanyName, ContactName, Phone, City, Country FROM Customers WHERE Country = 'USA' AND City <> 'Seattle' Метод Select объекта DataTable позволяет искать ряды по похожим критериям.

Предположим, вы выбрали все содержимое таблицы Customers в объект DataTable, Тогда для поиска клиентов из США. но не из Сиэтла, можно воспользоваться кри терием из приведенного выше запроса:

Visual Basic.NET Dim strConn, strSQL. As String strConn = "Provider=SQLOLEDB;

Data Source=(local)\NetSDK;

" & "Initial Catalog=Northwind;

Trusted_Connection=Yes;

" strSQL = "SELECT CustomerlD, CompanyName, ContactName, " & _ "Phone, City, Country FROM Customers" Dim da As New 01eDbDataAdapter(strSQL, strConn) Dim tbl As New DataTableO da.Fill(tbl) Dim aRows As DataRowO Dim row As DataRow aRows = tbl.Select("Country = 'USA' AND City <> 'Seattle'") For Each row In aRows Console.WriteLine(row("CompanyName") & " " & rowf'City") i Д " - " & rowC'Country")) Next row Visual C#.NET string strConn, strSQL;

strConn = "Provider=SQLOLEDB;

Data Source=(local)\\NetSDK;

" + "Initial Catalog=Northwind;

Trusted_Connection-Yes;

";

strSQL = "SELECT CustomerlD, CompanyName, ContactName, " + "Phone, City, Country FROM Customers";

OleDbDataAdapter da = new 01edbDataAdapter(strSQL, strConn);

DataTable tbl = new DataTableO;

da.FIll(tbl);

DataRow[] aRows = tbl.SelectC'Country = 'USA' AND City <> 'Seattle'");

foreach (DataRow row in aRows) ГЛАВА 8 Сортировка, поиск, фильтрация Console.WriteLine(row["CompanyName"] + " - " + row["Clty"] + " - " + row["Country"]);

Поиск по шаблону ADO.NET позволяет искать ряды по шаблону. Следующий SQL-запрос возвращает список клиентов, значение поля CustomerlD которых начинается с буквы Л.

SELECT CustomerlD, CompanyNante, ContactName, Phone FROM Customers WHERE CustomerlD LIKE 'АГ В начало или конец строки поиска можно добавлять символы шаблона % и *.

Так, этот запрос возвращает список клиентов из Нью-Хэмпшира (New Hampshire), Нью-Джерси (New Jersey), Нью-Мехико (New Mexico) и Нью-Йорка (New York):

strFilter = "State LIKE 'New %'" Следующий запрос вернет список клиентов из Северной и Южной Дакоты (Dakota):

strFilter = "State LIKE 'K Dakota" Односимвольные шаблоны типа ? и _ в ADO.NET не допускаются.

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

Нельзя просто взять и заключить в критерии поиска строку в одиночные ка вычки. Ну, вообще-то это допустимо, но не рекомендуется. Предположим, ваше при ложение позволяет искать сотрудников по фамилиям и пользователь ввел фами лию O'Malley. Если заключить в критерии поиска литеральное значение в одиночные кавычки, критерий будет выглядеть так:

LastName = 'O'Malley' Тем не менее, если в искомом значении уже присутствует символ-разделитель, его необходимо продублировать. В нашем случае критерий поиска будет таким:

Pages:     | 1 |   ...   | 3 | 4 | 5 | 6 | 7 |   ...   | 11 |    Книги, научные публикации