using System.ComponentModel; using trakker.Models; namespace trakker.Data { /// /// Provides data access methods for the entity. /// This class encapsulates database operations such as upsert, delete and ad-hoc /// SQL execution for clients. It inherits from which /// provides connection management. /// internal class ClientData(string connectionString) : DataAccess(connectionString) { public BindingList Get(string? clientId = null) { var results = new BindingList(); string whereClause = "1 = 1"; if (clientId != null) { whereClause = "client_id = $client_id"; } string sql = $@" SELECT client_id, name, company, email, phone, address_street, address_city, address_state, address_postal, notes, is_active FROM clients WHERE {whereClause} ORDER BY name ASC ; "; using var conn = OpenConnection(); using var cmd = conn.CreateCommand(); cmd.CommandText = sql; if (clientId != null) { cmd.Parameters.AddWithValue("$client_id", clientId); } using var reader = cmd.ExecuteReader(); var _var1 = reader.GetOrdinal("client_id"); var _var2 = reader.GetOrdinal("name"); var _var3 = reader.GetOrdinal("company"); var _var4 = reader.GetOrdinal("email"); var _var5 = reader.GetOrdinal("phone"); var _var6 = reader.GetOrdinal("address_street"); var _var7 = reader.GetOrdinal("address_city"); var _var8 = reader.GetOrdinal("address_state"); var _var9 = reader.GetOrdinal("address_postal"); var _var10 = reader.GetOrdinal("notes"); var _var11 = reader.GetOrdinal("is_active"); while (reader.Read()) { results.Add(new Client { ClientId = reader.GetString(_var1), Name = reader.GetString(_var2), Company = reader.GetString(_var3), Email = reader.GetString(_var4), Phone = reader.GetString(_var5), AddressStreet = reader.GetString(_var6), AddressCity = reader.GetString(_var7), AddressState = reader.GetString(_var8), AddressPostal = reader.GetString(_var9), Notes = reader.GetString(_var10), IsActive = reader.GetString(_var11), }); } return results; } /// /// Inserts a new client record or updates an existing one (upsert) using /// the provided model. This method executes /// a single SQL statement inside a transaction and will commit on /// success or roll back on failure. /// /// The model to insert or update. Must not be null. /// /// The SQL statement uses an ON CONFLICT clause to perform the update when a /// matching client_id already exists. Parameter names correspond to the /// client model property names. /// public void Upsert(Client client) { const string sql = @" INSERT INTO clients ( client_id, name, company, email, phone, address_street, address_city, address_state, address_postal, notes, is_active ) VALUES ( $client_id, $name, $company, $email, $phone, $address_street, $address_city, $address_state, $address_postal, $notes, $is_active ) ON CONFLICT(client_id) DO UPDATE SET name = excluded.name, company = excluded.company, email = excluded.email, phone = excluded.phone, address_street = excluded.address_street, address_city = excluded.address_city, address_state = excluded.address_state, address_postal = excluded.address_postal, notes = excluded.notes, is_active = excluded.is_active, updated_at = CURRENT_TIMESTAMP; "; using var conn = OpenConnection(); using var tx = conn.BeginTransaction(); try { using (var cmd = conn.CreateCommand()) { cmd.Transaction = tx; cmd.CommandText = sql; cmd.Parameters.AddWithValue("$client_id", client.ClientId); cmd.Parameters.AddWithValue("$name", client.Name); cmd.Parameters.AddWithValue("$company", client.Company); cmd.Parameters.AddWithValue("$email", client.Email); cmd.Parameters.AddWithValue("$phone", client.Phone); cmd.Parameters.AddWithValue("$address_street", client.AddressStreet); cmd.Parameters.AddWithValue("$address_city", client.AddressCity); cmd.Parameters.AddWithValue("$address_state", client.AddressState); cmd.Parameters.AddWithValue("$address_postal", client.AddressPostal); cmd.Parameters.AddWithValue("$notes", client.Notes); cmd.Parameters.AddWithValue("$is_active", client.IsActive); cmd.ExecuteNonQuery(); } tx.Commit(); } catch { tx.Rollback(); throw; } } /// /// Deletes the client with the specified from the /// database. /// /// The identifier of the client to delete. /// An optional integer representing any scalar value returned by the /// command executed after deletion (if applicable). May be null. /// /// The method executes within a transaction. The current implementation attempts /// to read a scalar value after the delete; that value depends on surrounding /// database triggers or commands and may be null. /// public int? Delete(string clientId) { const string sql = @" DELETE FROM clients WHERE client_id = $client_id ; "; using var conn = OpenConnection(); using var tx = conn.BeginTransaction(); int? result = 0; try { using (var cmd = conn.CreateCommand()) { cmd.Transaction = tx; cmd.CommandText = sql; cmd.Parameters.AddWithValue("$client_id", clientId); cmd.ExecuteNonQuery(); } using var idCmd = conn.CreateCommand(); idCmd.Transaction = tx; result = (int?)idCmd.ExecuteScalar() ; tx.Commit(); } catch { tx.Rollback(); throw; } return result; } /// /// Executes arbitrary, ad-hoc SQL against the database inside a transaction. /// /// A SQL statement to execute. The caller is responsible for /// ensuring the SQL is safe and properly parameterized to avoid SQL injection. /// /// This method is intended for one-off maintenance or administrative commands. /// It does not return any result; if a scalar value is produced by the SQL, /// the current implementation captures it but does not expose it to the caller. /// public void Adhoc(string sql) { using var conn = OpenConnection(); using var tx = conn.BeginTransaction(); int? result = 0; try { using (var cmd = conn.CreateCommand()) { cmd.Transaction = tx; cmd.CommandText = sql; cmd.ExecuteNonQuery(); } using var idCmd = conn.CreateCommand(); idCmd.Transaction = tx; result = (int?)idCmd.ExecuteScalar() ; tx.Commit(); } catch { tx.Rollback(); throw; } } } }