ADO.NET. Управление базами данных. Связь по схеме OLE DB провайдера. Коррекция стилей DataGridView, страница 31

Так как количество таблиц БД заранее неизвестно, то память для массива списков отводится в конструкторе класса (см. код new List<DataRow>[count]). Размер массива (count) зависит от числа таблиц БД и выбирается из свойства Count коллекции DataTableCollection. Ссылка на саму коллекцию передается в параметре конструктора.

Мы декларируем три массива вида List<DataRow>[count], так как необходимо разделить изменения строк на три категории (добавленные, удаленные и измененные). Декларация массивов выглядит таким образом.

List<DataRow>[] added, deleted, changed;

Для того, чтобы с массивами списков было удобно работать в классе главной формы, созданы свойства, которые дают доступ к ним только на чтение (Readonly).

public List<DataRow>[] Added { get { return added; } }

public List<DataRow>[] Deleted { get { return deleted; } }

public List<DataRow>[] Changed { get { return changed; } }

Метод RowChanged с описателем internal (что означает — доступен для всех классов данной сборки) является обработчиком двух событий: RowDeleting и RowChanged, происходящих со всеми таблицами DataSet. Характерно, что сам DataSet и его таблицы определены в другом классе и делегаты, реагирующие на указанные события, создаются в классе формы, а не в классе TablesChange. Это делается так.

ds.Tables[0].RowDeleting += new DataRowChangeEventHandler (changes.RowChanged);

ds.Tables[1].RowDeleting += new DataRowChangeEventHandler (changes.RowChanged);

ds.Tables[0].RowChanged += new DataRowChangeEventHandler (changes.RowChanged);

ds.Tables[1].RowChanged += new DataRowChangeEventHandler (changes.RowChanged);

Здесь changes — ссылка на объект класса TablesChange.

Итак, пользователь работает с компонентами DataGridView в рамках класса MainForm, а события обрабатывают методы, расположенные в классе TablesChange. Все изменения запоминаются в списках, сгруппированных в массивы, раздельно для каждой таблицы и для каждого типа изменения (added, deleted, changed). В нашем случае имеется две таблицы (студентов и их экзаменов), поэтому массив added содержит два списка добавлений. Массив deleted содержит два списка удалений, а массив changed — два списка обновлений (UPDATE).

Суть оптимизации состоит в том, что строки, вставленные пользователем в DataTable, а затем удаленные им же в процессе работы с DataSet (в отсоединенном режиме) не запоминаются в списке deleted. Просмотрите код ветви case DataRowAction.Delete метода RowChanged.

Строки, вставленные в DataTable, а затем измененные (до нажатия кнопки Save) остаются в категории added, и не попадают в категорию changed. Это, видимо, и отличает наш подход от того, что делает DbDataAdapter. Мы не знаем, что он делает, так как не имеем открытого кода этого метода.

Накопленные в списках изменения вносятся в БД в порядке, описанном ранее, а именно: удаления — снизу-вверх, добавления — сверху-вниз. Это делается в методе класса MainForm. Во избежание ошибок приведем полный код той части класса, которая не управляется дизайнером студии.

namespace StudentsDBEx

{

public partial class MainForm : Form

{

  string dbPath;

  DataSet ds;

  OleDbDataAdapter da;

  OleDbConnection cn;

  BindingSource bsStuds, bsExams;

  TablesChange changes;

  public MainForm () { InitializeComponent (); }

  void MainForm_Load (object sender, EventArgs e) { LoadFromDB(); }

void LoadFromDB()

  {

   try

   {

    cn = new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0; Data Source=" +

      FindAFile("\\Students.mdb"));

    ConnectAndRead();

   }

   catch (Exception ex)

   {

    MessageBox.Show(ex.Message, Text);

    Application.Exit();

    return;

   }

  }

  string FindAFile(string file)

  {

   dbPath = Environment.CurrentDirectory;

   string root = Path.GetPathRoot(dbPath);

   while (true)

   {

    if (File.Exists(dbPath + file))

      break;

    if ((dbPath = Path.GetDirectoryName(dbPath)) == root)

      throw new FileNotFoundException("Could not find Database file: " + file);

   }

   return dbPath + file;

  }

  void ConnectAndRead()