Дерево C# новые узлы?
От: webinc  
Дата: 26.05.10 04:34
Оценка:
Доброго времени суток.
Храню дерево в структуре ID (автоинкрементное), Parrent_id.
В программе работаю с ним через типизированный датасет с автоинкрементым ID.
Возник вопрос. Как сохранить иерархию при добавлении нескольких уровней вложенности за один сеанс. Т.к. реальные ID меняются, как одновременно поменять их значения в Parrent_id?
Re: Дерево C# новые узлы?
От: alexey.kostylev Новая Зеландия http://alexeykostylev.livejournal.com/
Дата: 26.05.10 04:59
Оценка:
Здравствуйте, webinc, Вы писали:

W>Доброго времени суток.

W>Храню дерево в структуре ID (автоинкрементное), Parrent_id.
W>В программе работаю с ним через типизированный датасет с автоинкрементым ID.
W>Возник вопрос. Как сохранить иерархию при добавлении нескольких уровней вложенности за один сеанс. Т.к. реальные ID меняются, как одновременно поменять их значения в Parrent_id?

может проще не использовать автоинкремент? ну или использовать хранимку в которой вытаскивать last scope identity
Re[2]: Дерево C# новые узлы?
От: webinc  
Дата: 26.05.10 05:46
Оценка:
Здравствуйте, alexey.kostylev, Вы писали:

AK>может проще не использовать автоинкремент? ну или использовать хранимку в которой вытаскивать last scope identity

Хранимка используется для InsertCommand и возвращает после добавления реальное значение ID.
Т.е. обновлять датасет при каждом добавлении нового узла? Не слишком "правильно" что ли. Хочется внести все изменениея разом, или это невозможно стандартным DataSet'ом?
Re: Дерево C# новые узлы?
От: webinc  
Дата: 27.05.10 03:13
Оценка:
Пока сделал так

var dtup = dataSet.pePrograms.GetChanges();
if (dtup != null)
{
    var chRows = new Hashtable();

    // Сохраняем старые ID
    foreach (DataRow row in dtup.Rows)
        chRows.Add(row.GetHashCode(), row["Program_id"]);

    peProgramsTableAdapter.Update((DataSet.peProgramsDataTable)dtup);
    dataSet.AcceptChanges();

    /* Обновляем Parrent_id изменившихся ID */
    dtup.AcceptChanges();
    foreach (DataRow row in dtup.Rows)
    {
        var oldId = (int)chRows[row.GetHashCode()];

        foreach (DataRow chRow in dtup.Rows)
            if (chRow["Parrent_id"].Equals(oldId))
                chRow["Parrent_id"] = row["Program_id"];
    }
    var reldtup = dtup.GetChanges();
    if (reldtup != null) peProgramsTableAdapter.Update((DataSet.peProgramsDataTable)reldtup);
    dtup.AcceptChanges();

    peProgramsTableAdapter.Fill(dataSet.pePrograms);
}


Работает ... но как то не нравится
Re: Дерево C# новые узлы?
От: _FRED_ Черногория
Дата: 04.06.10 11:35
Оценка: 1 (1)
Здравствуйте, webinc, Вы писали:

W>Храню дерево в структуре ID (автоинкрементное), Parrent_id.

W>В программе работаю с ним через типизированный датасет с автоинкрементым ID.
W>Возник вопрос. Как сохранить иерархию при добавлении нескольких уровней вложенности за один сеанс. Т.к. реальные ID меняются, как одновременно поменять их значения в Parrent_id?

Можно попробовать сделать так, что бы Parrent_id был nullable (достаточно в базе). Тогда InsertCommand у адаптера не должна выставлять ParentId, а Updateдолжна. Соответственно, сначала из датасета всё удаляется, потом получаются все новые строки и инсёртятся, а потом все добавленные и изменённые апдейтятся. Есно, надо позаботиться о транзакционности этого дела.
Help will always be given at Hogwarts to those who ask for it.
Re[2]: Дерево C# новые узлы?
От: webinc  
Дата: 16.06.10 00:39
Оценка:
Здравствуйте, _FRED_, Вы писали:

_FR>Можно попробовать сделать так, что бы Parrent_id был nullable (достаточно в базе). Тогда InsertCommand у адаптера не должна выставлять ParentId, а Updateдолжна. Соответственно, сначала из датасета всё удаляется, потом получаются все новые строки и инсёртятся, а потом все добавленные и изменённые апдейтятся. Есно, надо позаботиться о транзакционности этого дела.


Не сработает когда добавят несколько уровней иерархии за один сеанс.
Re[3]: Дерево C# новые узлы?
От: _FRED_ Черногория
Дата: 16.06.10 03:01
Оценка:
Здравствуйте, webinc, Вы писали:

_FR>>Можно попробовать сделать так, что бы Parrent_id был nullable (достаточно в базе). Тогда InsertCommand у адаптера не должна выставлять ParentId, а Updateдолжна. Соответственно, сначала из датасета всё удаляется, потом получаются все новые строки и инсёртятся, а потом все добавленные и изменённые апдейтятся. Есно, надо позаботиться о транзакционности этого дела.


W>Не сработает когда добавят несколько уровней иерархии за один сеанс.


Почему не срабатывает? Какая разница, сколько уровней
Help will always be given at Hogwarts to those who ask for it.
Re: Дерево C# новые узлы?
От: Sinix  
Дата: 16.06.10 03:30
Оценка:
Здравствуйте, webinc, Вы писали:

Как сохранить иерархию при добавлении нескольких уровней вложенности за один сеанс. Т.к. реальные ID меняются, как одновременно поменять их значения в Parrent_id?

Cascade Update на FK датасета + отключенный (по умолчанию отключен) batch update у адаптера. Чтоб перестраховаться, можно реализовать топологическую сортировку обновляемых строк.
Re[4]: Дерево C# новые узлы?
От: webinc  
Дата: 16.06.10 05:51
Оценка:
Здравствуйте, _FRED_, Вы писали:

_FR>Почему не срабатывает? Какая разница, сколько уровней

Ну смотри.
Добавили мы узел, к нему лист и к листу еще лист.
ID | Parrent_ID
----------------------
-1 | NULL
-2 | -1
-3 | -2

Все эти записи добавлены. При апдейте получим
ID | Parrent_ID
----------------------
1 | NULL
2 | -1
3 | -2

Измененных записей получается нет.
Re[2]: Дерево C# новые узлы?
От: webinc  
Дата: 16.06.10 05:55
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Cascade Update на FK датасета + отключенный (по умолчанию отключен) batch update у адаптера. Чтоб перестраховаться, можно реализовать топологическую сортировку обновляемых строк.

Пробовал, но тогда Parrent_id не может быть NULL ну или я не нашел.
Re[4]: Дерево C# новые узлы?
От: webinc  
Дата: 16.06.10 05:58
Оценка:
Здравствуйте, _FRED_, Вы писали:

_FR>Почему не срабатывает? Какая разница, сколько уровней

Перечитал
Я сделал как раз то что ты и предложил по сути. Пока это единственный путь который я увидел
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. Данный пример не учитывает удаление записей.
Re[3]: Дерево C# новые узлы?
От: Sinix  
Дата: 16.06.10 08:30
Оценка:
Здравствуйте, webinc, Вы писали:

W>Пробовал, но тогда Parrent_id не может быть NULL ну или я не нашел.

Почему не может? DataColumn.AllowDbNull = true.
Re[3]: Дерево C# новые узлы?
От: webinc  
Дата: 17.06.10 05:09
Оценка:
Здравствуйте, Sergei MO, Вы писали:

Спасибо большое за проделаную работу. Мне она очень помогла прояснить некоторые моменты.
Доработаю удаление и этот вариант будет как раз то что я хотел.
Re[3]: Дерево C# новые узлы?
От: Аноним  
Дата: 24.06.10 11:56
Оценка:
Здравствуйте, webinc, Вы писали:

W>Здравствуйте, alexey.kostylev, Вы писали:


AK>>может проще не использовать автоинкремент? ну или использовать хранимку в которой вытаскивать last scope identity

W>Хранимка используется для InsertCommand и возвращает после добавления реальное значение ID.
W>Т.е. обновлять датасет при каждом добавлении нового узла? Не слишком "правильно" что ли. Хочется внести все изменениея разом, или это невозможно стандартным DataSet'ом?

Вы решили разгрузить сеть, нагрузив SQL-сервер и сломав голову себе?
Re[2]: Дерево C# новые узлы?
От: Lloyd Россия  
Дата: 22.07.10 07:59
Оценка:
Здравствуйте, _FRED_, Вы писали:

W>>Возник вопрос. Как сохранить иерархию при добавлении нескольких уровней вложенности за один сеанс. Т.к. реальные ID меняются, как одновременно поменять их значения в Parrent_id?


_FR>Можно попробовать сделать так, что бы Parrent_id был nullable (достаточно в базе).


А что, Parrent_id может быть не-nullable?
Re[3]: Дерево C# новые узлы?
От: _FRED_ Черногория
Дата: 22.07.10 08:37
Оценка:
Здравствуйте, Lloyd, Вы писали:

W>>>Возник вопрос. Как сохранить иерархию при добавлении нескольких уровней вложенности за один сеанс. Т.к. реальные ID меняются, как одновременно поменять их значения в Parrent_id?


_FR>>Можно попробовать сделать так, что бы Parrent_id был nullable (достаточно в базе).


L>А что, Parrent_id может быть не-nullable?


Parrent_id, возможно и нет, но может быть и так, что ссылка на другую (или даже ту же) запись в таблице должна быть обязательной.
Help will always be given at Hogwarts to those who ask for it.
Re[4]: Дерево C# новые узлы?
От: Lloyd Россия  
Дата: 22.07.10 08:46
Оценка:
Здравствуйте, _FRED_, Вы писали:

_FR>>>Можно попробовать сделать так, что бы Parrent_id был nullable (достаточно в базе).


L>>А что, Parrent_id может быть не-nullable?


_FR>Parrent_id, возможно и нет, но может быть и так, что ссылка на другую (или даже ту же) запись в таблице должна быть обязательной.


А как вставить первую запись? Хотя, если ссылка на себя — допустима, то можно.
Re[5]: Дерево C# новые узлы?
От: Аноним  
Дата: 09.12.11 03:27
Оценка:
Спасибо за код!
Как раз эта мысль блуждала в голове, что нужно вытащить id из сохраненной записи
и вставить ее в parentId дочерней. Никак не мог придумать, как это сделать.

У меня не получилось использовать именно этот код.
Ошибки при добавлении записи — не найдена подходящая версия.
У только что вставленных записей НЕТ версии Original.
Именно в том порядке у меня не выполнялось
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.