using System.Data; using Npgsql; namespace praktika { public partial class Form1 : Form { public const string SchemaName = "dyomin_be"; public const string AdminLogin = "admin"; public const string AdminPassword = "admin"; public const string ConnectionString = "Host=edu-pg.itiscaf.ru;Port=5432;Database=dbwork;Username=dyomin_be;Password=$vAx&y5zxQ$;Search Path=dyomin_be"; public readonly TextBox loginTextBox = new(); public readonly TextBox passwordTextBox = new(); public readonly Button loginButton = new(); public readonly Label loginStatusLabel = new(); public readonly Panel loginPanel = new(); public readonly Panel mainPanel = new(); public readonly ComboBox tableComboBox = new(); public readonly DataGridView dataGridView = new(); public readonly Button refreshButton = new(); public readonly Button addButton = new(); public readonly Button saveButton = new(); public readonly Button deleteButton = new(); public readonly Button logoutButton = new(); public readonly Label currentUserLabel = new(); public readonly Dictionary tables = new() { ["authors"] = new("id"), ["books"] = new("id"), ["tags"] = new("id"), ["book_tags"] = new("book_id", "tag_id") { AutoIncrementKeys = [] }, ["comments"] = new("id"), ["users"] = new("id") { AdminOnly = true }, ["v_book_catalog"] = new() { ReadOnly = true } }; public DataTable? currentTable; public bool isAdmin; public string currentLogin = ""; public Form1() { InitializeComponent(); BuildUi(); ShowLogin(); } public void BuildUi() { Text = "PostgreSQL CRUD"; Width = 1100; Height = 700; StartPosition = FormStartPosition.CenterScreen; MinimumSize = new Size(900, 550); loginPanel.Dock = DockStyle.Fill; var loginBox = new TableLayoutPanel { Width = 360, Height = 210, Anchor = AnchorStyles.None, ColumnCount = 1, RowCount = 7, Padding = new Padding(20) }; loginBox.Location = new Point((loginPanel.Width - loginBox.Width) / 2, (loginPanel.Height - loginBox.Height) / 2); loginBox.RowStyles.Add(new RowStyle(SizeType.Absolute, 24)); loginBox.RowStyles.Add(new RowStyle(SizeType.Absolute, 36)); loginBox.RowStyles.Add(new RowStyle(SizeType.Absolute, 24)); loginBox.RowStyles.Add(new RowStyle(SizeType.Absolute, 36)); loginBox.RowStyles.Add(new RowStyle(SizeType.Absolute, 36)); loginBox.RowStyles.Add(new RowStyle(SizeType.Absolute, 24)); loginBox.RowStyles.Add(new RowStyle(SizeType.Absolute, 36)); loginBox.Controls.Add(new Label { Text = "Логин", Dock = DockStyle.Fill }, 0, 0); loginTextBox.Dock = DockStyle.Fill; loginBox.Controls.Add(loginTextBox, 0, 1); loginBox.Controls.Add(new Label { Text = "Пароль", Dock = DockStyle.Fill }, 0, 2); passwordTextBox.Dock = DockStyle.Fill; passwordTextBox.UseSystemPasswordChar = true; loginBox.Controls.Add(passwordTextBox, 0, 3); loginButton.Text = "Войти"; loginButton.Dock = DockStyle.Fill; loginButton.Click += LoginButton_Click; loginBox.Controls.Add(loginButton, 0, 4); loginBox.Controls.Add(new Label { Text = "Админ: admin / admin", Dock = DockStyle.Fill }, 0, 5); loginStatusLabel.ForeColor = Color.DarkRed; loginStatusLabel.Dock = DockStyle.Fill; loginBox.Controls.Add(loginStatusLabel, 0, 6); loginPanel.Controls.Add(loginBox); Controls.Add(loginPanel); mainPanel.Dock = DockStyle.Fill; mainPanel.Visible = false; var root = new TableLayoutPanel { Dock = DockStyle.Fill, ColumnCount = 1, RowCount = 2, Padding = new Padding(10) }; root.RowStyles.Add(new RowStyle(SizeType.Absolute, 42)); root.RowStyles.Add(new RowStyle(SizeType.Percent, 100)); var toolbar = new FlowLayoutPanel { Dock = DockStyle.Fill, FlowDirection = FlowDirection.LeftToRight, WrapContents = false }; currentUserLabel.AutoSize = true; currentUserLabel.Padding = new Padding(0, 8, 16, 0); toolbar.Controls.Add(currentUserLabel); tableComboBox.Width = 180; tableComboBox.DropDownStyle = ComboBoxStyle.DropDownList; tableComboBox.SelectedIndexChanged += TableComboBox_SelectedIndexChanged; toolbar.Controls.Add(tableComboBox); refreshButton.Text = "Обновить"; refreshButton.Click += RefreshButton_Click; toolbar.Controls.Add(refreshButton); addButton.Text = "Добавить"; addButton.Click += AddButton_Click; toolbar.Controls.Add(addButton); saveButton.Text = "Сохранить"; saveButton.Click += SaveButton_Click; toolbar.Controls.Add(saveButton); deleteButton.Text = "Удалить"; deleteButton.Click += DeleteButton_Click; toolbar.Controls.Add(deleteButton); logoutButton.Text = "Выйти"; logoutButton.Click += LogoutButton_Click; toolbar.Controls.Add(logoutButton); dataGridView.Dock = DockStyle.Fill; dataGridView.AllowUserToAddRows = false; dataGridView.AllowUserToDeleteRows = false; dataGridView.AutoGenerateColumns = true; dataGridView.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill; dataGridView.SelectionMode = DataGridViewSelectionMode.FullRowSelect; dataGridView.MultiSelect = true; root.Controls.Add(toolbar, 0, 0); root.Controls.Add(dataGridView, 0, 1); mainPanel.Controls.Add(root); Controls.Add(mainPanel); AcceptButton = loginButton; } public void LoginButton_Click(object? sender, EventArgs e) { loginStatusLabel.Text = ""; var login = loginTextBox.Text.Trim(); var password = passwordTextBox.Text; if (login == AdminLogin && password == AdminPassword) { isAdmin = true; currentLogin = login; OpenMain(); return; } try { using var connection = new NpgsqlConnection(ConnectionString); connection.Open(); using var command = new NpgsqlCommand( $"select count(*) from {Q(SchemaName)}.{Q("users")} " + $"where login = @login and password_hash = crypt(@password, password_hash)", connection); command.Parameters.AddWithValue("login", login); command.Parameters.AddWithValue("password", password); var exists = Convert.ToInt32(command.ExecuteScalar()) > 0; if (!exists) { loginStatusLabel.Text = "Неверный логин или пароль"; return; } isAdmin = false; currentLogin = login; OpenMain(); } catch (Exception ex) { loginStatusLabel.Text = ex.Message; } } public void OpenMain() { loginPanel.Visible = false; mainPanel.Visible = true; currentUserLabel.Text = isAdmin ? $"Админ: {currentLogin}" : $"Пользователь: {currentLogin}"; AcceptButton = saveButton; tableComboBox.Items.Clear(); foreach (var table in tables.Where(x => isAdmin || !x.Value.AdminOnly).Select(x => x.Key)) { tableComboBox.Items.Add(table); } if (tableComboBox.Items.Count > 0) { tableComboBox.SelectedIndex = 0; } } public void ShowLogin() { mainPanel.Visible = false; loginPanel.Visible = true; loginTextBox.Text = ""; passwordTextBox.Text = ""; loginStatusLabel.Text = ""; dataGridView.DataSource = null; currentTable = null; AcceptButton = loginButton; loginTextBox.Focus(); } public void TableComboBox_SelectedIndexChanged(object? sender, EventArgs e) { LoadCurrentTable(); } public void RefreshButton_Click(object? sender, EventArgs e) { LoadCurrentTable(); } public void AddButton_Click(object? sender, EventArgs e) { if (currentTable is null || SelectedTable().ReadOnly) { return; } currentTable.Rows.Add(currentTable.NewRow()); } public void DeleteButton_Click(object? sender, EventArgs e) { if (currentTable is null || SelectedTable().ReadOnly) { return; } foreach (DataGridViewRow gridRow in dataGridView.SelectedRows) { if (gridRow.DataBoundItem is DataRowView rowView) { rowView.Row.Delete(); } } } public void SaveButton_Click(object? sender, EventArgs e) { if (currentTable is null) { return; } dataGridView.EndEdit(); if (BindingContext is { } context && context[currentTable] is { } manager) { manager.EndCurrentEdit(); } try { var tableName = SelectedTableName(); var table = SelectedTable(); if (table.ReadOnly) { throw new InvalidOperationException("Эту таблицу нельзя редактировать"); } using var connection = new NpgsqlConnection(ConnectionString); connection.Open(); using var transaction = connection.BeginTransaction(); foreach (DataRow row in currentTable.Rows.Cast().Where(x => x.RowState == DataRowState.Deleted).ToList()) { DeleteRow(connection, transaction, tableName, table, row); } foreach (DataRow row in currentTable.Rows.Cast().Where(x => x.RowState == DataRowState.Added).ToList()) { InsertRow(connection, transaction, tableName, table, row); } foreach (DataRow row in currentTable.Rows.Cast().Where(x => x.RowState == DataRowState.Modified).ToList()) { UpdateRow(connection, transaction, tableName, table, row); } transaction.Commit(); LoadCurrentTable(); MessageBox.Show("Сохранено", "Готово", MessageBoxButtons.OK, MessageBoxIcon.Information); } catch (Exception ex) { MessageBox.Show(ex.Message, "Ошибка сохранения", MessageBoxButtons.OK, MessageBoxIcon.Error); } } public void LogoutButton_Click(object? sender, EventArgs e) { isAdmin = false; currentLogin = ""; tableComboBox.Items.Clear(); ShowLogin(); } public void LoadCurrentTable() { if (tableComboBox.SelectedItem is not string tableName) { return; } try { var table = tables[tableName]; var orderBy = table.PrimaryKeys.Count > 0 ? " order by " + string.Join(", ", table.PrimaryKeys.Select(Q)) : ""; using var connection = new NpgsqlConnection(ConnectionString); connection.Open(); using var command = new NpgsqlCommand($"select * from {Q(SchemaName)}.{Q(tableName)}{orderBy}", connection); using var reader = command.ExecuteReader(); currentTable = new DataTable(tableName); currentTable.Load(reader); PrepareTable(tableName, currentTable); dataGridView.DataSource = currentTable; dataGridView.ReadOnly = table.ReadOnly; addButton.Enabled = !table.ReadOnly; saveButton.Enabled = !table.ReadOnly; deleteButton.Enabled = !table.ReadOnly; } catch (Exception ex) { MessageBox.Show(ex.Message, "Ошибка загрузки", MessageBoxButtons.OK, MessageBoxIcon.Error); } } public void PrepareTable(string tableName, DataTable dataTable) { var table = tables[tableName]; foreach (DataColumn column in dataTable.Columns) { column.ReadOnly = false; column.AllowDBNull = true; } foreach (var key in table.PrimaryKeys) { if (dataTable.Columns.Contains(key)) { dataTable.Columns[key]!.ReadOnly = table.AutoIncrementKeys.Contains(key); } } } public void InsertRow(NpgsqlConnection connection, NpgsqlTransaction transaction, string tableName, TableInfo table, DataRow row) { var columns = currentTable!.Columns .Cast() .Select(x => x.ColumnName) .Where(x => !table.AutoIncrementKeys.Contains(x) && row[x] != DBNull.Value) .ToList(); if (columns.Count == 0) { throw new InvalidOperationException("Нет данных для добавления"); } var columnList = string.Join(", ", columns.Select(Q)); var valueList = string.Join(", ", columns.Select((_, index) => $"@p{index}")); using var command = new NpgsqlCommand( $"insert into {Q(SchemaName)}.{Q(tableName)} ({columnList}) values ({valueList})", connection, transaction); AddParameters(command, columns, row, DataRowVersion.Current, 0); command.ExecuteNonQuery(); } public void UpdateRow(NpgsqlConnection connection, NpgsqlTransaction transaction, string tableName, TableInfo table, DataRow row) { var columns = currentTable!.Columns .Cast() .Select(x => x.ColumnName) .Where(x => !table.PrimaryKeys.Contains(x)) .ToList(); if (columns.Count == 0) { return; } var setSql = string.Join(", ", columns.Select((column, index) => $"{Q(column)} = @p{index}")); var whereSql = BuildWhere(table, row, DataRowVersion.Original, columns.Count); using var command = new NpgsqlCommand( $"update {Q(SchemaName)}.{Q(tableName)} set {setSql} where {whereSql}", connection, transaction); AddParameters(command, columns, row, DataRowVersion.Current, 0); AddParameters(command, table.PrimaryKeys, row, DataRowVersion.Original, columns.Count); command.ExecuteNonQuery(); } public void DeleteRow(NpgsqlConnection connection, NpgsqlTransaction transaction, string tableName, TableInfo table, DataRow row) { var whereSql = BuildWhere(table, row, DataRowVersion.Original, 0); using var command = new NpgsqlCommand( $"delete from {Q(SchemaName)}.{Q(tableName)} where {whereSql}", connection, transaction); AddParameters(command, table.PrimaryKeys, row, DataRowVersion.Original, 0); command.ExecuteNonQuery(); } public static string BuildWhere(TableInfo table, DataRow row, DataRowVersion version, int offset) { if (table.PrimaryKeys.Count == 0) { throw new InvalidOperationException("Для CRUD нужен первичный ключ"); } return string.Join(" and ", table.PrimaryKeys.Select((key, index) => $"{Q(key)} = @p{offset + index}")); } public static void AddParameters(NpgsqlCommand command, List columns, DataRow row, DataRowVersion version, int offset) { for (var i = 0; i < columns.Count; i++) { var value = row[columns[i], version]; command.Parameters.AddWithValue($"p{offset + i}", value is DBNull ? DBNull.Value : value); } } public string SelectedTableName() { return tableComboBox.SelectedItem as string ?? throw new InvalidOperationException("Таблица не выбрана"); } public TableInfo SelectedTable() { return tables[SelectedTableName()]; } public static string Q(string identifier) { return "\"" + identifier.Replace("\"", "\"\"") + "\""; } public sealed class TableInfo { public TableInfo(params string[] primaryKeys) { PrimaryKeys = primaryKeys.ToList(); AutoIncrementKeys = primaryKeys.ToList(); } public List PrimaryKeys { get; init; } public List AutoIncrementKeys { get; init; } public bool AdminOnly { get; init; } public bool ReadOnly { get; init; } } } }