Здравствуйте, webinc, Вы писали:
W>Работает ... но как то не нравится
В коде есть следующие проблемы:
1. Некорректно используется GetHashCode() — контракт этого метода не гарантирует уникальность возвращаемых значений. Теоретически, могут существовать две изменённые записи с одинаковым хэш-кодом. При использовании Hashtable в качестве ключа нужно брать row, а не row.GetHashCode().
2. Есть квадратичная зависимость от количества изменённых записей (dtup.Rows).
3. Есть вызов Fill — после каждого изменения вся таблица заново грузится с сервера. Если таблица довольно большая, а изменения происходят часто, будут проблемы с производительностью.
Но в целом, общий подход вполне нормальный. Я бы ещё предложил задействовать события RowUpdating и RowUpdated для отслеживания сгенерированных сервером ключей. Как-то так:
// таблица на сервере создана такой командой:
// CREATE TABLE Tree (
// Program_id Int IDENTITY(1, 1) NOT NULL PRIMARY KEY,
// Parrent_id Int NULL REFERENCES Tree
// )
// задаёт соответствие ключей, сгенерированных клиентом,
// ключам, сгенерированным на сервере
Hashtable idMapping = new Hashtable();
// вызывается после выполнения команды INSERT
void TreeRowUpdated(Object sender, SqlRowUpdatedEventArgs e)
{
// старое значение ключа, сгенерированное клиентом
object originalId = e.Row["Program_id", DataRowVersion.Original];
// новое значение ключа, сгенерированное сервером
object newId = e.Row["Program_id", DataRowVersion.Current];
// запоминаем соответствие ключей
idMapping.Add(originalId, newId);
// сообщаем адаптеру, что нужно сохранить версии полей Original
// и не вызывать AcceptChanges для этой записи
e.Status = UpdateStatus.SkipCurrentRow;
}
// вызывается перед выполнением команды UPDATE
void TreeRowUpdating(Object sender, SqlRowUpdatingEventArgs e)
{
object originalParentId = e.Row["Parrent_id", DataRowVersion.Original];
// если отображение содержит такой ключ, значит он изменился
// при вставке записей, и его нужно заменить на новое значение
if (idMapping.ContainsKey(originalParentId))
{
// на момент вызвова этого обработчика значения параметров уже заполнены,
// поэтому значение меняем не только в самой записи, но и в параметре
e.Command.Parameters["@Parrent_id"].Value =
e.Row["Parrent_id"] = idMapping[originalParentId];
}
}
// переносит изменения из DataTable в базу
void Update(DataTable table, SqlDataAdapter adapter)
{
// не забываем очистить отображение ключей
idMapping.Clear();
// вставляем добавленные записи в таблицу, Parrent_id в БД будет NULL
adapter.RowUpdated += new SqlRowUpdatedEventHandler(TreeRowUpdated);
adapter.Update(table.Select(null, null, DataViewRowState.Added));
adapter.RowUpdated -= new SqlRowUpdatedEventHandler(TreeRowUpdated);
// изменяем Parrent_id в БД на правильные значения
adapter.RowUpdating += new SqlRowUpdatingEventHandler(TreeRowUpdating);
adapter.Update(table.Select(null, null, DataViewRowState.ModifiedCurrent));
adapter.RowUpdating -= new SqlRowUpdatingEventHandler(TreeRowUpdating);
}
// инициализация адаптера и демонстрация работы
void Test(SqlConnection connection)
{
DataTable table = new DataTable();
table.Columns.Add("Program_id", typeof(Int32));
table.Columns.Add("Parrent_id", typeof(Int32));
table.Columns["Program_id"].AutoIncrement = true;
table.Columns["Program_id"].AutoIncrementSeed = -1;
table.Columns["Program_id"].AutoIncrementStep = -1;
SqlDataAdapter adapter = new SqlDataAdapter();
adapter.InsertCommand = new SqlCommand("INSERT INTO Tree (Parrent_id) VALUES (NULL) " +
"SELECT Program_id, Parrent_id FROM Tree WHERE Program_id = SCOPE_IDENTITY()", connection);
adapter.UpdateCommand = new SqlCommand(
"UPDATE Tree SET Parrent_id = @Parrent_id WHERE Program_id = @Program_id", connection);
adapter.UpdateCommand.Parameters.Add("@Program_id", SqlDbType.Int, 0, "Program_id");
adapter.UpdateCommand.Parameters.Add("@Parrent_id", SqlDbType.Int, 0, "Parrent_id");
// добавляем записи в таблицу
DataRow row1 = table.NewRow();
table.Rows.Add(row1);
DataRow row2 = table.NewRow();
table.Rows.Add(row2);
DataRow row3 = table.NewRow();
table.Rows.Add(row3);
DataRow row4 = table.NewRow();
table.Rows.Add(row4);
// создаём из записей такое дерево:
// 2
// / \
// 1 3
// \
// 4
// причем порядок записей в таблице такой, что обычным способом их невозможно
// записать в базу за один проход по таблице (без топологической сортировки)
row1["Parrent_id"] = row2["Program_id"];
row3["Parrent_id"] = row2["Program_id"];
row4["Parrent_id"] = row3["Program_id"];
// внесение изменений в базу
Update(table, adapter);
// вызов Fill не нужен - данные в table и в базе уже одинаковые
// перевешиваем 4-ую вершину на 2-ую - это чтобы показать,
// что обычное обновление тоже работает
row4["Parrent_id"] = row2["Program_id"];
Update(table, adapter);
}
PS. Данный пример не учитывает удаление записей.