# Dinaup.Database

`Dinaup.Database` es el módulo de acceso a PostgreSQL incluido dentro del paquete `Dinaup`. Ofrece una interfaz directa para trabajar con la base de datos sin lidiar con `DataReader` ni boilerplate de ADO.NET. Cubre:

* Conexión a PostgreSQL con SSL y cadenas en formato estándar.
* Lecturas tipadas (listas, diccionarios, modelos) sin lidiar con `DataReader`.
* Inserción, actualización y **upsert** (insertar o actualizar según exista el registro).
* Lectura por lotes de grandes volúmenes de datos.
* Mapeo automático a clases de negocio mediante interfaces simples.
* Acceso multihilo seguro y reutilización de conexión.

### Instalación <a href="#instalaci-n" id="instalaci-n"></a>

`Dinaup.Database` forma parte del paquete principal de Dinaup. Ya no se distribuye como paquete independiente: instalando `Dinaup` ya tienes `PGClient` y todas las utilidades de base de datos disponibles.

```bash
dotnet add package Dinaup
```

```csharp
using Dinaup.Database;
```

{% hint style="info" %}
En versiones anteriores `Dinaup.Database` era un NuGet aparte (`dotnet add package Dinaup.Database`). Si actualizas un proyecto antiguo, basta con eliminar esa referencia: el namespace y la API son los mismos.
{% endhint %}

### Clase Principal: PGClient <a href="#clase-principal-pgclient" id="clase-principal-pgclient"></a>

La clase `PGClient` proporciona métodos para:

* Conexión y reconexión a PostgreSQL (con SSL si se requiere).
* Ejecución de sentencias SQL.
* Lectura de resultados (listas, diccionarios, modelos).
* Inserción, actualización, y operaciones de upsert.
* Lectura de datos en lotes.

#### Propiedades Principales <a href="#propiedades-principales" id="propiedades-principales"></a>

* `IsConnected` (bool): Indica si la conexión está abierta.
* `Host`, `Port`, `UserName`, `Password`, `DatabaseName`: Parámetros de conexión.
* `UseSSL`: Indica si se usa SSL.
* `Description`: Descripción opcional de la conexión.
* `TablesAndColumns`: Diccionario opcional de tablas a columnas.

#### Establecer Conexión <a href="#establecer-conexi-n" id="establecer-conexi-n"></a>

Conexión con parámetros separados:

```csharp
var client = new PGClient();
client.Connect("localhost", 5432, "myuser", "mypassword", "mydatabase");

if (client.IsConnected)
{
    Console.WriteLine("Conexión establecida correctamente.");
}
```

Con SSL (cadena estilo URL):

```csharp
string connectionString = "postgres://user:password@host:port/dbname?sslmode=require";
client.ConnectWithSSL(connectionString);
```

Con SSL (parámetros separados):

```csharp
client.ConnectWithSSL("host", 5432, "user", "password", "databaseName");
```

#### Lectura de Datos <a href="#lectura-de-datos" id="lectura-de-datos"></a>

* `ReadValue(string SQL)`: Retorna un único valor (cadena).
* `ReadList(string SQL)`: Retorna una lista de cadenas (asumiendo una sola columna).
* `ReadDictionary(string SQL)`: Retorna un `Dictionary<string, string>` a partir de pares clave-valor.
* `ReadDictionaryList(string SQL)`: Retorna una lista de diccionarios (una fila por diccionario).
* `ReadObjectList<T>(string SQL)` (donde T: BaseModelConverter): Retorna una lista de objetos T mapeados desde la BD.

Ejemplos:

```csharp
// Leer un valor único
var countStr = client.ReadValue("SELECT COUNT(*) FROM test_table;");
int totalRegistros = int.Parse(countStr);

// Leer una lista (una columna)
var nombres = client.ReadList("SELECT name FROM test_table ORDER BY id;");
foreach (var nombre in nombres)
{
    Console.WriteLine(nombre);
}

// Leer una lista de diccionarios
var registros = client.ReadDictionaryList("SELECT id, name, value FROM test_table WHERE id < 10;");
foreach (var reg in registros)
{
    Console.WriteLine($"ID: {reg["id"]}, Name: {reg["name"]}, Value: {reg["value"]}");
}
```

#### Lectura de Modelos <a href="#lectura-de-modelos" id="lectura-de-modelos"></a>

Si se dispone de clases que heredan de `BaseModelConverter`, puede mapearse directamente:

```csharp
public class TestModel : BaseModelConverter
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Value { get; set; }

    public override void FromDic(Dictionary<string, string> dic)
    {
        this.Id = dic.GetM("id").INT(0);
        this.Name = dic.GetM("name");
        this.Value = dic.GetM("value").INT(0);
    }

    public override string Table => "test_table";
    public override string[] Fields => new[] { "id", "name", "value" };
    public override string LastModifiedFieldDatetimeUTC => ""; // No usado en este ejemplo
}

// Ejemplo de uso
var modelos = client.ReadObjectList<TestModel>("SELECT * FROM test_table ORDER BY id;");
foreach (var m in modelos)
{
    Console.WriteLine($"{m.Id} - {m.Name} - {m.Value}");
}
```

#### Inserción, Actualización y Upsert <a href="#inserci-n-actualizaci-n-y-upsert" id="inserci-n-actualizaci-n-y-upsert"></a>

* `InsertRecord(tableName, record)`: Inserta un diccionario como registro.
* `InsertRecords(tableName, records)`: Inserta múltiples registros a la vez.
* `UpdateRecord(tableName, dataDict, idField, idValue)`: Actualiza el registro cuyo campo `idField` coincide con `idValue`.
* `InsertOrIgnoreRecord(tableName, dataDict)`: Inserta el registro si no hay conflicto.
* `InsertOrUpdateRecord(tableName, dataDict, idField)`: Inserta o actualiza según exista el registro.
* `InsertOrUpdateRecords(tableName, dataDicts, idField)`: Inserta o actualiza múltiples registros.

Ejemplo: Insertar un registro

```csharp
var nuevoRegistro = new Dictionary<string, string>
{
    {"name", "NuevoNombre"},
    {"value", "123"}
};

int rowsAffected = client.InsertRecord("test_table", nuevoRegistro);
Console.WriteLine("Filas insertadas: " + rowsAffected);
```

Ejemplo: Actualizar un registro

```csharp
var datosActualizar = new Dictionary<string, string>
{
    {"value", "999"}
};
int filasActualizadas = client.UpdateRecord("test_table", datosActualizar, "id", "1");
Console.WriteLine("Filas actualizadas: " + filasActualizadas);
```

Ejemplo: Upsert (insertar o actualizar)

```csharp
var registroUpsert = new Dictionary<string, string>
{
    {"id", "100"},
    {"name", "Registro100"},
    {"value", "1000"}
};

int affected = client.InsertOrUpdateRecord("test_table", registroUpsert, "id");
Console.WriteLine("Filas modificadas: " + affected);
```

#### Lectura en Lotes (Batch Reading) <a href="#lectura-en-lotes-batch-reading" id="lectura-en-lotes-batch-reading"></a>

Para grandes cantidades de datos:

* `BatchReadDictionaries(description, countSQL, dataSQL, batchSize)`: Iterador que produce lotes de diccionarios.
* `BatchReadObjects<T>(description, countSQL, dataSQL, batchSize)`: Igual que el anterior pero para objetos de tipo T.

Ejemplo:

```csharp
var batches = client.BatchReadDictionaries(
    "Lectura en lotes",
    "SELECT COUNT(*) FROM test_table",
    "SELECT id, name, value FROM test_table ORDER BY id",
    1000
);

foreach (var batch in batches)
{
    Console.WriteLine("Lote de " + batch.Count + " registros");
    foreach (var reg in batch)
    {
        Console.WriteLine($"{reg["id"]} - {reg["name"]} - {reg["value"]}");
    }
}
```

#### Clonar Conexiones <a href="#clonar-conexiones" id="clonar-conexiones"></a>

`DuplicateConnection()` crea una nueva instancia `PGClient` con la misma configuración y abre la conexión.

```csharp
var repoClonado = client.DuplicateConnection();
if (repoClonado.IsConnected)
{
    Console.WriteLine("Conexión clonada y funcionando.");
}
```

#### Manejo de Errores <a href="#manejo-de-errores" id="manejo-de-errores"></a>

Se pueden capturar excepciones con `try...catch`.

```csharp
try
{
    client.ExecuteNonQuery("INSERT INTO test_table (name, value) VALUES ('Test', 1)");
}
catch (Exception ex)
{
    Console.WriteLine("Error en la inserción: " + ex.Message);
}
```

#### Liberación de Recursos <a href="#liberaci-n-de-recursos" id="liberaci-n-de-recursos"></a>

`PGClient` implementa `IDisposable`, por lo que es recomendable usar `using`:

```csharp
using (var client = new PGClient())
{
    client.Connect("localhost", 5432, "user", "pass", "db");
    // ... Operaciones ...
}
// Aquí se libera la conexión automáticamente
```

### Ejemplos Completos <a href="#ejemplos-completos" id="ejemplos-completos"></a>

#### Ejemplo 1: Leer todos los registros (c#) <a href="#ejemplo-1-leer-todos-los-registros-c" id="ejemplo-1-leer-todos-los-registros-c"></a>

```csharp
var client = new PGClient();
client.Connect("localhost", 5432, "user", "password", "mydb");

var listaNombres = client.ReadList("SELECT name FROM test_table ORDER BY id;");
foreach (var nombre in listaNombres)
{
    Console.WriteLine(nombre);
}
```

#### Ejemplo 2: Insertar y luego leer modelos (c#) <a href="#ejemplo-2-insertar-y-luego-leer-modelos-c" id="ejemplo-2-insertar-y-luego-leer-modelos-c"></a>

```csharp
var client = new PGClient();
client.ConnectWithSSL("postgres://user:pass@host:port/mydb?sslmode=require");

// Insertar un registro
var nuevo = new Dictionary<string, string>
{
    {"name", "NuevoRegistro"},
    {"value", "100"}
};
client.InsertRecord("test_table", nuevo);

// Leer como objetos
var objetos = client.ReadObjectList<TestModel>("SELECT * FROM test_table WHERE name='NuevoRegistro'");
foreach (var obj in objetos)
{
    Console.WriteLine($"{obj.Id}: {obj.Name} - {obj.Value}");
}
```

#### Ejemplo 3: Actualizar o Ignorar si existe (c#) <a href="#ejemplo-3-actualizar-o-ignorar-si-existe-c" id="ejemplo-3-actualizar-o-ignorar-si-existe-c"></a>

```csharp
var client = new PGClient();
client.Connect("localhost", 5432, "user", "password", "mydb");

var registro = new Dictionary<string, string>
{
    {"id", "1"},
    {"name", "Test1"},
    {"value", "999"}
};

// InsertOrUpdate
int afectadas = client.InsertOrUpdateRecord("test_table", registro, "id");
Console.WriteLine("Filas afectadas: " + afectadas);
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://doc.dinaup.com/desarrollo/sdk/dinaup.database.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
