sql >> Base de Datos >  >> RDS >> Oracle

Usando Dapper QueryMultiple en Oracle

Es probable que el OP haya resuelto el problema hace mucho tiempo, pero al momento de escribir, esta pregunta solo tiene una respuesta y realmente no resuelve el problema de usar QueryMultiple() de Dapper método con Oracle. Como dice @Kamolas81 correctamente, al usar la sintaxis de los ejemplos oficiales, se obtendrá el ORA-00933: SQL command not properly ended mensaje de error. Pasé un tiempo buscando algún tipo de documentación sobre cómo hacer QueryMultiple() con Oracle, pero me sorprendió que realmente no hubiera un solo lugar que tuviera una respuesta. Hubiera pensado que esto era una tarea bastante común. Pensé que publicaría una respuesta aquí para salvarme me :) alguien en el futuro en caso de que alguien tenga el mismo problema.

Dapper parece simplemente pasar el comando SQL directamente a ADO.NET y cualquier proveedor de base de datos que esté ejecutando el comando. En la sintaxis de los ejemplos, donde cada comando está separado por un salto de línea, el servidor SQL interpretará eso como múltiples consultas para ejecutar en la base de datos y ejecutará cada una de las consultas y devolverá los resultados en salidas separadas. No soy un experto en ADO.NET, por lo que podría estar confundiendo la terminología, pero el efecto final es que Dapper obtiene múltiples resultados de consulta y luego hace su magia.

Sin embargo, Oracle no reconoce las múltiples consultas; piensa que el comando SQL está mal formado y devuelve el ORA-00933 mensaje. La solución es usar cursores y devolver la salida en una colección DynamicParameters. Por ejemplo, mientras que la versión de SQL Server se vería así:

var sql = 
@"
select * from Customers where CustomerId = @id
select * from Orders where CustomerId = @id
select * from Returns where CustomerId = @id";

la versión de Oracle de la consulta debería verse así:

var sql = "BEGIN OPEN :rslt1 FOR SELECT * FROM customers WHERE customerid = :id; " +
                "OPEN :rslt2 FOR SELECT * FROM orders WHERE customerid = :id; " +
                "OPEN :rslt3 FOR SELECT * FROM returns Where customerid = :id; " +
          "END;";

Para las consultas que se ejecutan en SQL Server, Dapper puede manejarlas desde allí. Sin embargo, debido a que estamos devolviendo los conjuntos de resultados a los parámetros del cursor, necesitaremos usar un IDynamicParameters colección para especificar parámetros para el comando. Para agregar una arruga adicional, el DynamicParameters.Add() normal El método en Dapper usa un System.Data.DbType para el parámetro opcional dbType, pero los parámetros del cursor para la consulta deben ser del tipo Oracle.ManagedDataAccess.Client.OracleDbType.RefCursor . Para resolver esto, utilicé la solución que @Daniel Smith propuso en esta respuesta y creó una implementación personalizada de IDynamicParameters interfaz:

    using Dapper;
    using Oracle.ManagedDataAccess.Client;
    using System.Data;
    
    public class OracleDynamicParameters : SqlMapper.IDynamicParameters
    {
        private readonly DynamicParameters dynamicParameters = new DynamicParameters();

        private readonly List<OracleParameter> oracleParameters = new List<OracleParameter>();

        public void Add(string name, OracleDbType oracleDbType, ParameterDirection direction, object value = null, int? size = null)
        {
            OracleParameter oracleParameter;
            if (size.HasValue)
            {
                oracleParameter = new OracleParameter(name, oracleDbType, size.Value, value, direction);
            }
            else
            {
                oracleParameter = new OracleParameter(name, oracleDbType, value, direction);
            }

            oracleParameters.Add(oracleParameter);
        }

        public void Add(string name, OracleDbType oracleDbType, ParameterDirection direction)
        {
            var oracleParameter = new OracleParameter(name, oracleDbType, direction);
            oracleParameters.Add(oracleParameter);
        }

        public void AddParameters(IDbCommand command, SqlMapper.Identity identity)
        {
            ((SqlMapper.IDynamicParameters)dynamicParameters).AddParameters(command, identity);

            var oracleCommand = command as OracleCommand;

            if (oracleCommand != null)
            {
                oracleCommand.Parameters.AddRange(oracleParameters.ToArray());
            }
        }
    }

Así que todo el código en conjunto es algo como esto:

    using Dapper;
    using Oracle.ManagedDataAccess.Client;
    using System.Data;
    
    int selectedId = 1;
    var sql = "BEGIN OPEN :rslt1 FOR SELECT * FROM customers WHERE customerid = :id; " +
                    "OPEN :rslt2 FOR SELECT * FROM orders WHERE customerid = :id; " +
                    "OPEN :rslt3 FOR SELECT * FROM returns Where customerid = :id; " +
              "END;";
    
    OracleDynamicParameters dynParams = new OracleDynamicParameters();
    dynParams.Add(":rslt1", OracleDbType.RefCursor, ParameterDirection.Output);
    dynParams.Add(":rslt2", OracleDbType.RefCursor, ParameterDirection.Output);
    dynParams.Add(":rslt3", OracleDbType.RefCursor, ParameterDirection.Output);
    dynParams.Add(":id", OracleDbType.Int32, ParameterDirection.Input, selectedId);
    
    using (IDbConnection dbConn = new OracleConnection("<conn string here>"))
    {
        dbConn.Open();
        var multi = dbConn.QueryMultiple(sql, param: dynParams);
        
        var customer = multi.Read<Customer>().Single();
        var orders = multi.Read<Order>().ToList();
        var returns = multi.Read<Return>().ToList();
        ...
        dbConn.Close();
    }