# Reglas obligatorias de mapeo de resultados Estas reglas complementan `AGENTS.md` y `AGENTS_STORED_PROCEDURES.md`. ## Extensión estándar - Todo resultado obtenido con `IDataReader` debe convertirse mediante `AppCode/Utilities/DataReaderExtensions.cs`. - No repetir bloques manuales como `Propiedad = Convert.ToInt32(reader["Columna"])` en repositorios o servicios. - Resultados admitidos: ```csharp T item = reader.ToObject(); List items = reader.ToList(); DataTable table = reader.ToDataTable(); JArray json = reader.ToJsonArray(); ``` - `ToObject()` devuelve `null` cuando el procedimiento no devuelve filas. - `ToList()` devuelve una lista vacía cuando no hay filas; no devuelve `null`. - `ToDataTable()` devuelve un `DataTable` vacío cuando no hay filas. - `ToJsonArray()` devuelve un `JArray` vacío cuando no hay filas. ## Operaciones asíncronas - Para código nuevo, preferir las versiones asíncronas cuando el proveedor exponga `DbCommand` y `DbDataReader`. - Métodos disponibles: `ToObjectAsync()`, `ToListAsync()`, `ToDataTableAsync()` y `ToJsonArrayAsync()`. - Los métodos asíncronos deben recibir y propagar un `CancellationToken` cuando la llamada superior disponga de uno. - Abrir el lector con `ExecuteReaderAsync`; no envolver operaciones síncronas de base de datos en `Task.Run`. - Usar `ConfigureAwait(false)` dentro de infraestructura, repositorios y servicios que no necesiten el contexto de WebForms. - Cerrar o desechar el lector antes de leer `@ErrorMostrar`, `@ErrorTipo` y `@Return`. - Una página WebForms que espere directamente debe declarar `Async="true"` y usar `RegisterAsyncTask` o un evento asíncrono compatible. - Mantener los métodos síncronos únicamente para flujos existentes que todavía no se hayan migrado de extremo a extremo. Ejemplo: ```csharp List usuarios; using (DbDataReader reader = await command.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false)) { usuarios = await reader.ToListAsync(cancellationToken).ConfigureAwait(false); } StoredProcedureCommand.EnsureSuccess(command); return usuarios; ``` ## Convención de columnas y propiedades - El mapeo se realiza por nombre de columna y propiedad sin distinguir mayúsculas/minúsculas. - Los procedimientos deben usar alias para que las columnas coincidan con el modelo. - Cuando no sea posible usar el mismo nombre, decorar la propiedad con `[Column("NombreColumna")]`. - Las columnas adicionales se ignoran. - Las propiedades sin columna conservan su valor predeterminado. - `DBNull` se convierte en `null` para propiedades anulables o de referencia; para propiedades no anulables conserva el valor predeterminado. - Se admiten tipos anulables, enumeraciones, `Guid`, `TimeSpan`, `DateTimeOffset`, booleanos y conversiones primitivas. - Una conversión incompatible debe producir una excepción para que `DbExecutor` la registre; no ocultar errores de contrato entre el PA y el modelo. ## Uso correcto con procedimientos almacenados - El repositorio crea y configura el comando mediante `StoredProcedureCommand`. - El repositorio abre el lector dentro de `using`. - Debe cerrar o desechar el lector antes de llamar `StoredProcedureCommand.EnsureSuccess(command)`, porque los parámetros OUTPUT se reciben al finalizar el lector. - La extensión sólo mapea datos; no abre conexiones, no crea comandos, no captura excepciones y no decide mensajes para el usuario. - Páginas ASPX, controles y master pages no deben manejar `IDataReader` directamente. Ejemplo para un objeto: ```csharp UserModel user; using (IDataReader reader = command.ExecuteReader()) { user = reader.ToObject(); } StoredProcedureCommand.EnsureSuccess(command); return user; ``` Ejemplo para una lista: ```csharp List items; using (IDataReader reader = command.ExecuteReader()) { items = reader.ToList(); } StoredProcedureCommand.EnsureSuccess(command); return items; ``` ## Elección del tipo de resultado - Preferir modelos tipados para reglas de negocio, seguridad, usuarios, permisos, menús y módulos nuevos. - Usar `DataTable` sólo para compatibilidad con componentes WebForms o migración de código heredado que realmente lo requiera. - Usar `JArray` únicamente cuando el contrato de salida sea JSON dinámico y no exista un modelo estable. - No convertir primero a `DataTable` para después crear objetos o JSON; mapear directamente desde `IDataReader`.