using System;
 using System.Data;

 namespace mergeTest
 {
 class Class1
 {
 static void Main(string[] args)
 {
 // Создается объект DataSet. 
 DataSet ds = new DataSet("myDataSet");

 // Создается таблица. 
 DataTable t = new DataTable("Items");

 // Столбцы таблицы – это особые объекты.
 // Имя первого столбца – id, тип значения – System.Int32.
 DataColumn c1 = new DataColumn("id", Type.GetType("System.Int32"));
 c1.AutoIncrement=true;
 // Имя второго столбца – Item, тип значения – System.Int32.
 DataColumn c2 = new DataColumn("Item", Type.GetType("System.Int32"));


 // Сборка объекта DataSet:
 // Добавляются объекты-столбцы...   
 t.Columns.Add(c1);
 t.Columns.Add(c2);


 // А вот массив столбцов (здесь он из одного элемента)
 // для организации первичного ключа (множества первичных ключей).
 DataColumn[] keyCol= new DataColumn[1];

 // И вот, собственно, как в таблице задается множество первичных ключей.
 keyCol[0]= c1;
 // Свойству объекта t передается массив, содержащий столбцы, которые
 // формируемая таблица t будет воспринимать как первичные ключи.
 t.PrimaryKey=keyCol;
 // А что с этими ключами будет t делать? А это нас в данный момент
 // не касается. Очевидно, что методы, которые обеспечивают контроль
 // над информацией в соответствии со значениями ключей, уже где-то
 // "зашиты" в классе DataTable. Как и когда они будут выполняться – 
 // не наше дело. Наше дело – указать на столбцы, которые для данной
 // таблицы будут ключевыми. Что мы и сделали.

 // Таблица подсоединяется к объекту ds – представителю класса DataSet.
 ds.Tables.Add(t);

 DataRow r;

 // В таблицу, которая уже присоединена к
 // объекту ds DataSet, добавляется 10 rows.
 for(int i = 0; i <10;i++)
 {
 r=t.NewRow();
 r["Item"]= i;
 t.Rows.Add(r);
 }

 // Принять изменения.
 // Так производится обновление DataSet'а.
 // Сведения о новых изменениях и добавлениях будут фиксироваться
 // вплоть до нового обновления.
 ds.AcceptChanges();
 PrintValues(ds, "Original values");

 // Изменение значения в первых двух строках.
 t.Rows[0]["Item"]= 50;
 t.Rows[1]["Item"]= 111;
 t.Rows[2]["Item"]= 111;

 // Добавление еще одной строки.
 // Судя по всему, значение первого столбца устанавливается автоматически.
 // Это ключевое поле со значением порядкового номера строки.
 r=t.NewRow();
 r["Item"]=74;
 t.Rows.Add(r);


 // Объявляем ссылку для создания временного DataSet. 
 DataSet xSet;

 // ДЕКЛАРАЦИЯ О НАМЕРЕНИЯХ КОНТРОЛЯ ЗА КОРРЕКТНОСТЬЮ ЗНАЧЕНИЙ СТРОКИ. 
 // Вот так добавляется свойство, содержащее строку для описания 
 // ошибки в значении. Наш DataSet содержит одну строку с описанием.
 // Это всего лишь указание на то обстоятельство, что МЫ САМИ 
 // обязались осуществлять
 // некоторую деятельность по проверке чего-либо. Чтобы не забыть, 
 // в чем проблема,
 // описание возможной ошибки (в свободной форме!) добавляем 
 // в свойства строки,
 // значения которой требуют проверки.
 t.Rows[0].RowError= "over 100 (ЙЦУКЕН!)";
 t.Rows[1].RowError= "over 100 (Stupid ERROR!)";
 t.Rows[2].RowError= "over 100 (Ну и дела!)";
 // Но одно дело – декларировать намерения, а другое – осуществлять 
 // контроль.
 // Проблема проверки корректности значения – наша личная проблема.
 // Однако о наших намерениях контроля за значениями становится 
 // известно объекту – представителю DataSet!

 PrintValues(ds, "Modified and New Values");

 // Мы вроде бы согласились проводить контроль значений.
 // Даже декларировали некий принцип проверки. 
 // Однако ничего само собой не происходит.
 // Так вот,
 //
 // ЕСЛИ В ТАБЛИЦУ БЫЛИ ДОБАВЛЕНЫ СТРОКИ ИЛИ ИЗМЕНЕНЫ ЗНАЧЕНИЯ СТРОК
 // И
 // МЫ ОБЯЗАЛИСЬ КОНТРОЛИРОВАТЬ ЗНАЧЕНИЯ СТРОК В ТАБЛИЦЕ,
 //
 // то самое время организовать эту проверку...
 // Критерий правильности значений, естественно, наш!
 // Алгоритмы проверки – тоже НАШИ!
 // Единственное, чем нам может помочь ADO .NET, – это выделить
 // подмножество строк таблицы,
 // которые были добавлены или модифицированы со времени последнего
 // обновления нашего объекта - представителя DataSet'а,

 if(ds.HasChanges(DataRowState.Modified | DataRowState.Added)& ds.HasErrors)
 {
 // И для этого мы воспользуемся методом, который позволяет обеспечить
 // выделение подмножества добавленных и
 // модифицированных строк в новый объект DataSet'а.
 // Use GetChanges to extract subset.
 xSet = ds.GetChanges(DataRowState.Modified|DataRowState.Added);
 PrintValues(xSet, "Subset values");
 // Insert code to reconcile errors. In this case, we'll reject changes.
 // Вот, собственно, код проверки. Все делается своими руками.
 foreach(DataTable xTable in xSet.Tables)
 {
 if (xTable.HasErrors)
 {
 foreach(DataRow xRow in xTable.Rows)
 {
 // Выделенное подмножество проверяем на наличие
 // ошибочного значения (для нас все, что больше 100, – 
 // уже ошибка!) 
 Console.Write(xRow["Item"] + "  ");
 if((int)xRow["Item",DataRowVersion.Current ]> 100)
 {
 // Находим ошибку в строке, сообщаем о ней,               
 Console.WriteLine("Error! – " + xRow.RowError);
 // Возвращаем старое значение...
 xRow.RejectChanges();
 // Отменяем значение свойства - уведомителя о возможных
 // ошибках для данной строки...
 xRow.ClearErrors();
 }
 else
 Console.WriteLine("OK.");
 }
 }
 }

 PrintValues(xSet, "Reconciled subset values");

 // Сливаем измененные и откорректированные строки в основной 
 // объект – DataSet
 // Merge changes back to first DataSet.
 ds.Merge(xSet);

 PrintValues(ds, "Merged Values");
 }
 }

 // А это всего лишь вывод содержимого DataSet'а.
 private static void PrintValues(DataSet ds, string label)
 {
 Console.WriteLine("\n" + label);
 foreach(DataTable t in ds.Tables)
 {
 Console.WriteLine("TableName: " + t.TableName);
 foreach(DataRow r in t.Rows)
 {
 foreach(DataColumn c in t.Columns)
 {
 Console.Write("\t " + r[c] );
 }
 Console.WriteLine();
 }
 }
 }
 }
 }

Листинг 18.8.
Закрыть окно