El uso de procedimientos almacenados es una práctica muy extendida para centralizar y encapsular la lógica empresarial en un entorno seguro y optimizado. Permiten trasladar parte de la lógica de la aplicación al servidor de bases de datos, lo que reduce la complejidad del código del lado del cliente, mejora el rendimiento al reducir el tráfico de red y aumenta la seguridad al limitar el acceso directo a las tablas.
Vamos a ver cómo llamar a un procedimiento almacenado SQL alojado en un sistema IBM i desde una aplicación .NET, utilizando NTi. Utilizaremos un procedimiento almacenado tomado directamente de la documentación oficial IBM i: DB2 for i SQL Reference - (página 1109).
Este procedimiento calcula la mediana de los salarios del personal y devuelve la lista de empleados cuyo salario es superior a esta mediana.
Veremos dos enfoques para llamar desde una aplicación .NET:
- La primera implica el uso directo de un DataReader.
- La segunda, aprovechando Dapper, un micro-ORM ligero que simplifica aún más la implementación y el uso de los resultados.
Paso 1 - Preparación del entorno IBM i
Antes de llamar a un procedimiento almacenado desde .NET, necesitamos entender exactamente qué hace este procedimiento, qué elementos se necesitan para ejecutarlo (tablas, datos) y preparar nuestro entorno.
El procedimiento almacenado que vamos a utilizar viene directamente del manual oficial IBM i: DB2 for i SQL Reference - (página 1109). Aquí está el código SQL proporcionado:
CREATE PROCEDURE MEDIAN_RESULT_SET (OUT medianSalary DECIMAL(7,2))
LANGUAGE SQL
DYNAMIC RESULT SETS 1
BEGIN
DECLARE v_numRecords INTEGER DEFAULT 1;
DECLARE v_counter INTEGER DEFAULT 0;
DECLARE c1 CURSOR FOR
SELECT salary
FROM staff
ORDER BY salary;
DECLARE c2 CURSOR WITH RETURN FOR
SELECT name, job, salary
FROM staff
WHERE salary > medianSalary
ORDER BY salary;
DECLARE EXIT HANDLER FOR NOT FOUND
SET medianSalary = 6666;
SET medianSalary = 0;
SELECT COUNT(*) INTO v_numRecords FROM staff;
OPEN c1;
WHILE v_counter < (v_numRecords / 2 + 1) DO
FETCH c1 INTO medianSalary;
SET v_counter = v_counter + 1;
END WHILE;
CLOSE c1;
OPEN c2;
END
Este procedimiento calcula el salario medio de los empleados de la empresa. Devuelve este salario en forma de un parámetro de salida llamado salariomedio
, y abre un cursor c2
que devuelve una lista detallada de los empleados que ganan un salario superior a esta mediana calculada.
💡 Aquí no hay ningún parámetro de entrada, y el procedimiento calcula directamente a partir de una tabla específica: la tabla
staff
.
Para que el procedimiento funcione, debe existir una tabla llamada staff en el mismo esquema donde se creará el procedimiento. Debe contener al menos una columna DECIMAL salary
y otras columnas name
y job
necesarias para el cursor de resultados.
Ahora que sabemos lo que hace el procedimiento y lo que necesita, podemos preparar nuestro entorno IBM i paso a paso.
1️⃣ Creación de la biblioteca
Creamos una biblioteca MDSALARY
para aislar ordenadamente nuestros elementos de prueba. Ejecute el siguiente comando desde su IBM i, ya sea a través de ACS, o de la pantalla 5250
CRTLIB MDSALARY
2️⃣ Definir el esquema de la base de datos
A continuación, definimos el esquema actual como esquema por defecto para que todos los comandos SQL posteriores hagan referencia a él automáticamente:
SET CURRENT SCHEMA = MDSALARY;
A partir de ahora, cada tabla o procedimiento que creemos se colocará automáticamente en la LIB MDSALARY
.
3️⃣ Creación de la tabla STAFF
Como vimos anteriormente, el procedimiento almacenado espera una tabla específica llamada staff
. Vamos a crearla de acuerdo con las columnas requeridas por nuestro ejemplo:
CREATE TABLE staff (
name VARCHAR(50),
job VARCHAR(50),
salary DECIMAL(7,2)
);
- Una columna
name
para el nombre del empleado. - Una columna
job
para el cargo. - Una columna
salary
para el salario del empleado.
4️⃣ Inserción de un conjunto de datos de ejemplo
Para calcular un salario medio relevante, necesitamos insertar algunos datos representativos en esta tabla:
INSERT INTO staff (name, job, salary) VALUES ('Alice', 'Manager', 2000.00);
INSERT INTO staff (name, job, salary) VALUES ('Bob', 'Clerk', 3000.00);
INSERT INTO staff (name, job, salary) VALUES ('Charlie', 'Analyst', 4000.00);
INSERT INTO staff (name, job, salary) VALUES ('David', 'Developer', 5000.00);
INSERT INTO staff (name, job, salary) VALUES ('Eve', 'Designer', 6000.00);
INSERT INTO staff (name, job, salary) VALUES ('Frank', 'Tester', 7000.00);
5️⃣ Script completo listo para ejecutarse en ACS
Aquí tienes un script completo que puedes copiar, pegar y ejecutar de una sola vez desde la herramienta "SQL Script Executor" de ACS
-- Creación de la biblioteca
CL: CRTLIB MDSALARY;
-- Definición del esquema actual
SET CURRENT SCHEMA = MDSALARY;
-- Creación de la tabla STAFF
CREATE TABLE staff (
name VARCHAR(50),
job VARCHAR(50),
salary DECIMAL(7,2)
);
-- Inserción de datos de prueba
INSERT INTO staff (name, job, salary) VALUES ('Alice', 'Manager', 2000.00);
INSERT INTO staff (name, job, salary) VALUES ('Bob', 'Clerk', 3000.00);
INSERT INTO staff (name, job, salary) VALUES ('Charlie', 'Analyst', 4000.00);
INSERT INTO staff (name, job, salary) VALUES ('David', 'Developer', 5000.00);
INSERT INTO staff (name, job, salary) VALUES ('Eve', 'Designer', 6000.00);
INSERT INTO staff (name, job, salary) VALUES ('Frank', 'Tester', 7000.00);
-- Creación del procedimiento almacenado MEDIAN_RESULT_SET
CREATE PROCEDURE MEDIAN_RESULT_SET (OUT medianSalary DECIMAL(7,2))
LANGUAGE SQL
DYNAMIC RESULT SETS 1
BEGIN
DECLARE v_numRecords INTEGER DEFAULT 1;
DECLARE v_counter INTEGER DEFAULT 0;
DECLARE c1 CURSOR FOR
SELECT salary FROM staff ORDER BY salary;
DECLARE c2 CURSOR WITH RETURN FOR
SELECT name, job, salary FROM staff WHERE salary > medianSalary ORDER BY salary;
DECLARE EXIT HANDLER FOR NOT FOUND SET medianSalary = 6666;
SET medianSalary = 0;
SELECT COUNT(*) INTO v_numRecords FROM staff;
OPEN c1;
WHILE v_counter < (v_numRecords / 2 + 1) DO
FETCH c1 INTO medianSalary;
SET v_counter = v_counter + 1;
END WHILE;
CLOSE c1;
OPEN c2;
END;
6️⃣ Verificación en IBM i
Compruebe en su IBM i que la librería MDSALARY
existe, que contiene la tabla staff
con los datos insertados, y que contiene el procedimiento almacenado SQL llamado MEDIAN_RESULT_SET
(Tipo *PGM
).
Paso 2 - Llamada al procedimiento almacenado IBM i desde .NET
Ahora que nuestro entorno IBM i está listo, vamos a llamar al procedimiento desde nuestra aplicación .NET. Creamos un proyecto BlazorWebApp en .NET 8, e instalamos los siguientes paquetes desde NuGet:
dotnet add package Aumerial.Data.Nti
dotnet add package Dapper
1️⃣ Creación de un servicio de conexión
Para simplificar la gestión de las conexiones, hemos creado un servicio sencillo DB2Service.cs
.
using Aumerial.Data.Nti;
public class DB2Service
{
private readonly string _connectionString = "server=myserver;user=myuser;password=mypassword;trim=true";
public NTiConnection CreateConnection()
{
var conn = new NTiConnection(_connectionString);
conn.Open();
return conn;
}
}
A continuación, guarde este servicio en su archivo Program.cs
.
builder.Services.AddSingleton();
2️⃣ Llamada al procedimiento almacenado
Lo haremos:
- Definir una entidad que represente a nuestros empleados
- Llamar al procedimiento almacenado para recuperar los resultados
- Explotación de estos resultados con dos enfoques: DataReader y Dapper
- Mostrar resultados en una tabla Blazor
3️⃣ Creación de la entidad Empleado
public class Employee
{
public string Name { get; set; }
public string Job { get; set; }
public decimal Salary { get; set; }
}
4️⃣ Método 1 - Enfoque clásico (DataReader)
En este primer enfoque clásico, creamos explícitamente una conexión con el servidor IBM i a través de nuestro servicio dedicado DB2Service
y, a continuación, configuramos un comando NTi para llamar a nuestro procedimiento almacenado MEDIAN_RESULT_SET
.
Definimos claramente el parámetro de salida medianSalary
, que recuperará el salario medio calculado por el procedimiento. Una vez ejecutado el comando, un DataReader
nos permite leer secuencialmente cada resultado devuelto por el procedimiento, transformando cada línea en un objeto de tipo Employee
. Por último, extraemos directamente el valor del parámetro de salida para averiguar la mediana exacta.
private decimal median;
private List employees = new();
private async Task LoadDatawhithDataReader()
{
// Creación de la conexión a través de DB2Service
using var conn = Db2Service.CreateConnection();
using var cmd = new NTiCommand("MDSALARY.MEDIAN_RESULT_SET", conn);
// Crear el comando para llamar al procedimiento almacenado
cmd.CommandType = System.Data.CommandType.StoredProcedure;
// Definición del parámetro de salida "medianSalary
var param = new NTiParameter();
param.ParameterName = "medianSalary";
param.Direction = System.Data.ParameterDirection.Output;
// Añade el parámetro de salida al comando
cmd.Parameters.Add(param);
// Ejecutar el comando y recuperar los resultados a través de DataReader
using var reader = await cmd.ExecuteReaderAsync();
// Recuperar el salario medio (parámetro de salida)
median = Convert.ToDecimal(param.Value);
// Leer los datos de cada empleado desde el DataReader
while (await reader.ReadAsync())
{
employees.Add(new Employee
{
Name = reader.GetString(0),
Job = reader.GetString(1),
Salary = reader.GetDecimal(2)
});
}
}
5️⃣ Método 2 - Enfoque simplificado (Dapper)
El segundo enfoque utiliza Dapper, un ORM ligero, para simplificar el código necesario para llamar a nuestro procedimiento almacenado. Basta con definir el parámetro de salida mediante DynamicParameters y, a continuación, Dapper se encarga automáticamente de abrir la conexión, ejecutar el comando y recuperar directamente los resultados en forma de lista de objetos de tipo Employee
. Por último, podemos recuperar fácilmente el valor del parámetro de salida correspondiente al salario medio. Este enfoque simplifica enormemente el código manteniendo el rendimiento y la legibilidad.
private decimal median;
private List employees = new();
private async Task LoadDataWithDapper()
{
// Creación de la conexión a través de DB2Service
using var conn = Db2Service.CreateConnection();
// Definir el parámetro de salida 'medianSalary' con DynamicParameters
var parameters = new DynamicParameters();
parameters.Add("medianSalary", dbType: DbType.Decimal, direction: ParameterDirection.Output);
// Dapper gestiona automáticamente la apertura y el cierre de la conexión, así como la lectura de los resultados
employees = (await conn.QueryAsync(
"MDSALARY.MEDIAN_RESULT_SET",
parameters,
commandType: CommandType.StoredProcedure)).ToList();
// Obtención directa del salario medio (parámetro de salida)
median = parameters.Get("medianSalary");
}
6️⃣ Visualización de resultados en un componente Blazor
Una vez recuperados los datos, todo lo que tenemos que hacer es mostrarlos limpiamente en nuestra interfaz Blazor utilizando una tabla.
Conclusion
Ya ha visto cómo llamar a un procedimiento almacenado en su IBM i desde una aplicación .NET con NTi: control detallado mediante un DataReader, o implementación rápida con Dapper. En ambos casos, NTi le permite integrar sus procedimientos almacenados SQL de forma sencilla y eficaz en sus aplicaciones .NET. En unas pocas líneas de código, obtendrá un resultado inmediatamente utilizable y adaptado a las prácticas modernas de desarrollo .NET.
Quentin Destrade