Доброго времени суток.
Храню дерево в структуре ID (автоинкрементное), Parrent_id.
В программе работаю с ним через типизированный датасет с автоинкрементым ID.
Возник вопрос. Как сохранить иерархию при добавлении нескольких уровней вложенности за один сеанс. Т.к. реальные ID меняются, как одновременно поменять их значения в Parrent_id?
Здравствуйте, webinc, Вы писали:
W>Доброго времени суток. W>Храню дерево в структуре ID (автоинкрементное), Parrent_id. W>В программе работаю с ним через типизированный датасет с автоинкрементым ID. W>Возник вопрос. Как сохранить иерархию при добавлении нескольких уровней вложенности за один сеанс. Т.к. реальные ID меняются, как одновременно поменять их значения в Parrent_id?
может проще не использовать автоинкремент? ну или использовать хранимку в которой вытаскивать last scope identity
Здравствуйте, alexey.kostylev, Вы писали:
AK>может проще не использовать автоинкремент? ну или использовать хранимку в которой вытаскивать last scope identity
Хранимка используется для InsertCommand и возвращает после добавления реальное значение ID.
Т.е. обновлять датасет при каждом добавлении нового узла? Не слишком "правильно" что ли. Хочется внести все изменениея разом, или это невозможно стандартным DataSet'ом?
Здравствуйте, 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.
Здравствуйте, _FRED_, Вы писали:
_FR>Можно попробовать сделать так, что бы Parrent_id был nullable (достаточно в базе). Тогда InsertCommand у адаптера не должна выставлять ParentId, а Updateдолжна. Соответственно, сначала из датасета всё удаляется, потом получаются все новые строки и инсёртятся, а потом все добавленные и изменённые апдейтятся. Есно, надо позаботиться о транзакционности этого дела.
Не сработает когда добавят несколько уровней иерархии за один сеанс.
Здравствуйте, webinc, Вы писали:
_FR>>Можно попробовать сделать так, что бы Parrent_id был nullable (достаточно в базе). Тогда InsertCommand у адаптера не должна выставлять ParentId, а Updateдолжна. Соответственно, сначала из датасета всё удаляется, потом получаются все новые строки и инсёртятся, а потом все добавленные и изменённые апдейтятся. Есно, надо позаботиться о транзакционности этого дела.
W>Не сработает когда добавят несколько уровней иерархии за один сеанс.
Почему не срабатывает? Какая разница, сколько уровней
Help will always be given at Hogwarts to those who ask for it.
Как сохранить иерархию при добавлении нескольких уровней вложенности за один сеанс. Т.к. реальные ID меняются, как одновременно поменять их значения в Parrent_id?
Cascade Update на FK датасета + отключенный (по умолчанию отключен) batch update у адаптера. Чтоб перестраховаться, можно реализовать топологическую сортировку обновляемых строк.
Здравствуйте, _FRED_, Вы писали:
_FR>Почему не срабатывает? Какая разница, сколько уровней
Ну смотри.
Добавили мы узел, к нему лист и к листу еще лист.
ID | Parrent_ID
----------------------
-1 | NULL
-2 | -1
-3 | -2
Все эти записи добавлены. При апдейте получим
ID | Parrent_ID
----------------------
1 | NULL
2 | -1
3 | -2
Здравствуйте, Sinix, Вы писали:
S>Cascade Update на FK датасета + отключенный (по умолчанию отключен) batch update у адаптера. Чтоб перестраховаться, можно реализовать топологическую сортировку обновляемых строк.
Пробовал, но тогда Parrent_id не может быть NULL ну или я не нашел.
Здравствуйте, _FRED_, Вы писали:
_FR>Почему не срабатывает? Какая разница, сколько уровней
Перечитал
Я сделал как раз то что ты и предложил по сути. Пока это единственный путь который я увидел
Здравствуйте, 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();
// вызывается после выполнения команды INSERTvoid 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;
}
// вызывается перед выполнением команды UPDATEvoid 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);
}
Спасибо большое за проделаную работу. Мне она очень помогла прояснить некоторые моменты.
Доработаю удаление и этот вариант будет как раз то что я хотел.
Re[3]: Дерево C# новые узлы?
От:
Аноним
Дата:
24.06.10 11:56
Оценка:
Здравствуйте, webinc, Вы писали:
W>Здравствуйте, alexey.kostylev, Вы писали:
AK>>может проще не использовать автоинкремент? ну или использовать хранимку в которой вытаскивать last scope identity W>Хранимка используется для InsertCommand и возвращает после добавления реальное значение ID. W>Т.е. обновлять датасет при каждом добавлении нового узла? Не слишком "правильно" что ли. Хочется внести все изменениея разом, или это невозможно стандартным DataSet'ом?
Вы решили разгрузить сеть, нагрузив SQL-сервер и сломав голову себе?
Здравствуйте, _FRED_, Вы писали:
W>>Возник вопрос. Как сохранить иерархию при добавлении нескольких уровней вложенности за один сеанс. Т.к. реальные ID меняются, как одновременно поменять их значения в Parrent_id?
_FR>Можно попробовать сделать так, что бы Parrent_id был nullable (достаточно в базе).
Здравствуйте, 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.
Здравствуйте, _FRED_, Вы писали:
_FR>>>Можно попробовать сделать так, что бы Parrent_id был nullable (достаточно в базе).
L>>А что, Parrent_id может быть не-nullable?
_FR>Parrent_id, возможно и нет, но может быть и так, что ссылка на другую (или даже ту же) запись в таблице должна быть обязательной.
А как вставить первую запись? Хотя, если ссылка на себя — допустима, то можно.
Re[5]: Дерево C# новые узлы?
От:
Аноним
Дата:
09.12.11 03:27
Оценка:
Спасибо за код!
Как раз эта мысль блуждала в голове, что нужно вытащить id из сохраненной записи
и вставить ее в parentId дочерней. Никак не мог придумать, как это сделать.
У меня не получилось использовать именно этот код.
Ошибки при добавлении записи — не найдена подходящая версия.
У только что вставленных записей НЕТ версии Original.
Именно в том порядке у меня не выполнялось