Так как количество таблиц БД заранее неизвестно, то память для массива списков отводится в конструкторе класса (см. код 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()
Уважаемый посетитель!
Чтобы распечатать файл, скачайте его (в формате Word).
Ссылка на скачивание - внизу страницы.