Re[2]: Дерево C# новые узлы?
От: Sergei MO Россия  
Дата: 16.06.10 08:21
Оценка: 2 (1)
Здравствуйте, 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. Данный пример не учитывает удаление записей.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.