Реализовать генерацию данных для иерархической таблицы
- Обеспечить вложеность не менее чем в три уровня
Будем теперь возиться с генератором иерархической структур. У меня в качестве такой структуры выступает некоторая локация.
Ну и для гонок можно конечно использовать какую-нибудь гоночную трассу или стадион, но там иерархию придумать сложнее. А можно использовать генератор локаций в виде многоэтажек.
То есть дом, в каждом доме есть этаж, на каждом этаже есть квартиры, в каждой квартиры есть комнаты, ванные и прочие удобства. Все это складывается во вполне интересную иерархию которую можно отразить в базе данных с помощью parent поля, которое ссылается в таблице на саму себя
И так добавляем новый таб под генератор локаций
и расставляем элементы на форме. У вас должно получится примерно по одному элементу на каждый уровень который вы задумали. У меня получилось как-то так
Сначала настрою трекбары. Создаю в форме метод
private void UpdateLabelForTrackbar(TrackBar tb, Label lbl)
{
if (tb.Minimum != tb.Value)
{
lbl.Text = $"от {tb.Minimum} до {tb.Value}";
} else
{
lbl.Text = tb.Value.ToString();
}
}
который заполняет лейбл по значению на трекбаре.
Далее создаю метод, который должен привязать все трекбары к лейблам, вот так:
private void BindTrackBars()
{
// тут я добавляю реакцию на изменение значений
tbApartaments.ValueChanged += (e, o) => UpdateLabelForTrackbar(tbApartaments, lblAparataments);
tbFloors.ValueChanged += (e, o) => UpdateLabelForTrackbar(tbFloors, lblFloors);
tbRooms.ValueChanged += (e, o) => UpdateLabelForTrackbar(tbRooms, lblRooms);
// а тут один раз вызываю методы чтобы сразу обновить тексты на лейблах
// лейблы использую которые зелененьким выделил
UpdateLabelForTrackbar(tbApartaments, lblAparataments);
UpdateLabelForTrackbar(tbFloors, lblFloors);
UpdateLabelForTrackbar(tbRooms, lblRooms);
}
ну и вызываю этот метод в конструкторе формы
public Form1()
{
// ...
BindTrackBars();
}
проверяю:
в принципе пойдет =)
Создаем метод для генерации здания
// добавляю себе для полного счастья некоторый набор типов зданий
readonly string[] BUILDING_TYPES = new []{ "Дом", "Усадьба", "Пансионат", "Небоскреб", "Коттедж" };
// метод для генерации здания, принимает на вход конекшен
private void GenerateBuilding(SqlConnection connection)
{
// генерим имя
var maleAdjectives = this.adjectives.Where(x => x.gender == "муж").ToList();
var adjective = maleAdjectives.ElementAt(random.Next(0, maleAdjectives.Count()));
var buildingType = BUILDING_TYPES.ElementAt(random.Next(0, BUILDING_TYPES.Count()));
var buildingName = $"{adjective.word.Capitalize()} {buildingType}";
// ну и вызываем уже привычный нам insert
SqlCommand command = new($@"INSERT INTO Locations(title) VALUES('{buildingName}')", connection);
command.ExecuteNonQuery();
}
давайте теперь кликнем два раза на кнопочку Запустить и вызовем в ней этот метод. И убедимся, что он работает без проблем.
private void btnGenerateLocations_Click(object sender, EventArgs e)
{
using (var connection = GetConnection())
{
connection.Open();
GenerateBuilding(connection);
}
}
запускаем:
вроде работает.
Теперь мне надо чтобы в здании сгенерировалось некоторое количество этажей в соответствии с трекбаром
private void GenerateBuilding(SqlConnection connection)
{
// ...
command.ExecuteNonQuery();
// выбираем случайное количество этажей
var floorsCount = random.Next(tbFloors.Minimum, tbFloors.Value + 1);
// ну и создаем их последовательно
for(var floor = 1; floor <= floorsCount; ++floor)
{
var floorName = $"{floor} этаж";
command = new($@"INSERT INTO Locations(title) VALUES('{floorName}')", connection);
command.ExecuteNonQuery();
}
}
проверяем:
ну что-то добавилось. С самим зданием вроде ок. Этаж тоже вроде как-то добавился, но по нему непонятно к какому зданию он принадлежит. Может он к “Плечному коттеджу” относится.
Собственно, чтобы отразить эту принадлежность воспользуемся полем parent_location в которой будем пихать идентификатор здания, в котором находится этаж.
Но есть одна проблема. Надо где-то взять идентификатор здания. А помните мы делали запрос INSERT и добавляли к нему OUTPUT. Мы можем и тут сделать так же
INSERT Locations(title)
OUTPUT inserted.id
VALUES ('...')
правим метод
private void GenerateBuilding(SqlConnection connection)
{
var maleAdjectives = this.adjectives.Where(x => x.gender == "муж").ToList();
var adjective = maleAdjectives.ElementAt(random.Next(0, maleAdjectives.Count()));
var buildingType = BUILDING_TYPES.ElementAt(random.Next(0, BUILDING_TYPES.Count()));
var buildingName = $"{adjective.word.Capitalize()} {buildingType}";
// добавляем OUTPUT inserted.id
SqlCommand command = new($@"INSERT INTO Locations(title) OUTPUT inserted.id VALUES('{buildingName}')", connection);
// тут заменям command.ExecuteNonQuery();
int buildingId = (int)command.ExecuteScalar(); // ExecuteScalar выполняет запрос и возвращает то что идет в OUTPUT
var floorsCount = random.Next(tbFloors.Minimum, tbFloors.Value + 1);
for(var floor = 1; floor <= floorsCount; ++floor)
{
var floorName = $"{floor} этаж";
// тут добавляем buildingId
command = new($@"INSERT INTO Locations(title) VALUES('{floorName}', {buildingId})", connection);
command.ExecuteNonQuery();
}
}
вообще мы сейчас параметры в запросы передаем не очень канончино, мы просто формируем строки запросов. Но в реальной жизни за такое бьют по рукам потому что
то есть если кто-то решит запихать в название здания или еще чего запрос к БД это может привести к плачевным последствиям.
Чтобы избежать этого надо передавать параметры немного более хитрым способом, но этот способ гарантирует, что если кто-то запихает в название запрос он сто процентов не будет выполнен.
Делается это так:
private void GenerateBuilding(SqlConnection connection)
{
var maleAdjectives = this.adjectives.Where(x => x.gender == "муж").ToList();
var adjective = maleAdjectives.ElementAt(random.Next(0, maleAdjectives.Count()));
var buildingType = BUILDING_TYPES.ElementAt(random.Next(0, BUILDING_TYPES.Count()));
var buildingName = $"{adjective.word.Capitalize()} {buildingType}";
// в запросе вместо '{buildingName}' пишем например @name, это будет плейсхолдер для параметра
SqlCommand command = new($@"INSERT INTO Locations(title) OUTPUT inserted.id VALUES(@name)", connection);
command.Parameters.Add("@name", SqlDbType.Text); // теперь добавляем сам параметр
command.Parameters["@name"].Value = buildingName; // и передаем в него значение
int buildingId = (int)command.ExecuteScalar();
var floorsCount = random.Next(tbFloors.Minimum, tbFloors.Value + 1);
for(var floor = 1; floor <= floorsCount; ++floor)
{
var floorName = $"{floor} этаж";
// тут делаем по аналогии, имена параметров в принципе можно выбирать какие хотите
// желательно чтобы они все-таки отражали суть передаваемых значений
// я вот использовал прямо как имена переменных
// вообще один из плюсов такой передачи параметров, помимо экранирования кода
// является то что не надо больше думать надо оборачивать параметр в кавычки или нет
// SqlCommand сам определит это по типу
command = new($@"INSERT INTO Locations(title, parent_location_id) VALUES(@floorName, @buildingId)", connection);
command.Parameters.Add("@floorName", SqlDbType.Text);
command.Parameters["@floorName"].Value = floorName;
command.Parameters.Add("@buildingId", SqlDbType.Int);
command.Parameters["@buildingId"].Value = buildingId;
command.ExecuteNonQuery();
}
}
ну что ж запускам:
смотрим на данные и как сработала привязка:
сработала как надо =)
Теперь надо каждому этажу добавить сколько-нибудь квартир. Тут надо делать вложенный цикл делать, но в принципе устройство тоже самое:
private void GenerateBuilding(SqlConnection connection)
{
// ...
var floorsCount = random.Next(tbFloors.Minimum, tbFloors.Value + 1);
// у меня будет сквозная нумерация квартир, поэтому добавляю поле
var nextAppartamentNumber = 1;
for (var floor = 1; floor <= floorsCount; ++floor)
{
var floorName = $"{floor} этаж";
// добавляем OUTPUT inserted.id
command = new($@"INSERT INTO Locations(title, parent_location_id) OUTPUT inserted.id VALUES(@floorName, @buildingId)", connection);
command.Parameters.Add("@floorName", SqlDbType.Text);
command.Parameters["@floorName"].Value = floorName;
command.Parameters.Add("@buildingId", SqlDbType.Int);
command.Parameters["@buildingId"].Value = buildingId;
// заменяем command.ExecuteNonQuery();
int floorId = (int)command.ExecuteScalar();
// ну и тут все по аналогии
// сначала рандомно бросаем количество квартир
var appartamentsCount = random.Next(tbApartaments.Minimum, tbApartaments.Value + 1);
for (var a = 1; a <= appartamentsCount; ++a)
{
// формируем имя квартиры
var appartamentName = $"{nextAppartamentNumber} квартира";
// увеличиваем номер квартиры в сквозной нумерации
nextAppartamentNumber++;
// ну и формируем запрос, передавая в качестве parent уже номер этажа
command = new($@"INSERT INTO Locations(title, parent_location_id) OUTPUT inserted.id VALUES(@appartamentName, @floorId)", connection);
command.Parameters.Add("@appartamentName", SqlDbType.Text);
command.Parameters["@appartamentName"].Value = appartamentName;
command.Parameters.Add("@floorId", SqlDbType.Int);
command.Parameters["@floorId"].Value = floorId;
command.ExecuteNonQuery();
}
}
}
запускаем:
Интересно что значит тамбурмажорский…
В общем теперь у нас тут двойная вложенность уже:
ну я думаю дальше понятно, что делать. Добавлять еще один вложенный цикл. И т. д.
У меня получился такой код:
private void GenerateBuilding(SqlConnection connection)
{
var maleAdjectives = this.adjectives.Where(x => x.gender == "муж").ToList();
var adjective = maleAdjectives.ElementAt(random.Next(0, maleAdjectives.Count()));
var buildingType = BUILDING_TYPES.ElementAt(random.Next(0, BUILDING_TYPES.Count()));
var buildingName = $"{adjective.word.Capitalize()} {buildingType}";
SqlCommand command = new($@"INSERT INTO Locations(title) OUTPUT inserted.id VALUES(@name)", connection);
command.Parameters.Add("@name", SqlDbType.Text);
command.Parameters["@name"].Value = buildingName;
int buildingId = (int)command.ExecuteScalar();
var floorsCount = random.Next(tbFloors.Minimum, tbFloors.Value + 1);
var nextAppartamentNumber = 1;
for (var floor = 1; floor <= floorsCount; ++floor)
{
var floorName = $"{floor} этаж";
command = new($@"INSERT INTO Locations(title, parent_location_id) OUTPUT inserted.id VALUES(@floorName, @buildingId)", connection);
command.Parameters.Add("@floorName", SqlDbType.Text);
command.Parameters["@floorName"].Value = floorName;
command.Parameters.Add("@buildingId", SqlDbType.Int);
command.Parameters["@buildingId"].Value = buildingId;
int floorId = (int)command.ExecuteScalar();
var appartamentsCount = random.Next(tbApartaments.Minimum, tbApartaments.Value + 1);
for (var a = 1; a <= appartamentsCount; ++a)
{
var appartamentName = $"{nextAppartamentNumber} квартира";
nextAppartamentNumber++;
command = new($@"INSERT INTO Locations(title, parent_location_id) OUTPUT inserted.id VALUES(@appartamentName, @floorId)", connection);
command.Parameters.Add("@appartamentName", SqlDbType.Text);
command.Parameters["@appartamentName"].Value = appartamentName;
command.Parameters.Add("@floorId", SqlDbType.Int);
command.Parameters["@floorId"].Value = floorId;
int appartamentId = (int)command.ExecuteScalar();
var roomsCount = random.Next(tbRooms.Minimum, tbRooms.Value + 1);
for (var r = 1; r <= roomsCount; ++r)
{
var roomName = $"{r} комната";
command = new($@"INSERT INTO Locations(title, parent_location_id) OUTPUT inserted.id VALUES(@roomName, @appartamentId)", connection);
command.Parameters.Add("@roomName", SqlDbType.Text);
command.Parameters["@roomName"].Value = roomName;
command.Parameters.Add("@appartamentId", SqlDbType.Int);
command.Parameters["@appartamentId"].Value = appartamentId;
command.ExecuteNonQuery();
}
if (chkHasBaclocny.Checked)
{
var roomName = $"Балкон";
command = new($@"INSERT INTO Locations(title, parent_location_id) OUTPUT inserted.id VALUES(@roomName, @appartamentId)", connection);
command.Parameters.Add("@roomName", SqlDbType.Text);
command.Parameters["@roomName"].Value = roomName;
command.Parameters.Add("@appartamentId", SqlDbType.Int);
command.Parameters["@appartamentId"].Value = appartamentId;
command.ExecuteNonQuery();
}
if (chkHasBathroom.Checked)
{
var roomName = $"Ванная";
command = new($@"INSERT INTO Locations(title, parent_location_id) OUTPUT inserted.id VALUES(@roomName, @appartamentId)", connection);
command.Parameters.Add("@roomName", SqlDbType.Text);
command.Parameters["@roomName"].Value = roomName;
command.Parameters.Add("@appartamentId", SqlDbType.Int);
command.Parameters["@appartamentId"].Value = appartamentId;
command.ExecuteNonQuery();
}
}
}
}
еще я забыл добавить поле в котором можно указать количество зданий. Добавлю ка я его:
и в реакции на кнопку пропишу:
private void btnGenerateLocations_Click(object sender, EventArgs e)
{
using (var connection = GetConnection())
{
connection.Open();
pbLocationsGenerator.Value = 0;
pbLocationsGenerator.Maximum = (int)udLocationsCount.Value;
for (var i = 0;i<udLocationsCount.Value; ++i)
{
GenerateBuilding(connection);
pbLocationsGenerator.Value++;
}
}
}
попробуем запустить, например, на 1000 элементов,
Думаю, пойдет =)
Реализовать генерацию данных для иерархической таблицы