Oracle
 sql >> Database >  >> RDS >> Oracle

Utilizzo di Dapper QueryMultiple in Oracle

L'OP probabilmente ha risolto il problema da tempo ormai, ma al momento della stesura di questo articolo, questa domanda ha solo una risposta e non risolve davvero il problema dell'utilizzo di QueryMultiple() di Dapper metodo con Oracle. Come afferma correttamente @Kamolas81, utilizzando la sintassi degli esempi ufficiali, si otterrà effettivamente il comando ORA-00933: SQL command not properly ended messaggio di errore. Ho passato un po' di tempo a cercare una sorta di documentazione su come fare QueryMultiple() con Oracle, ma sono rimasto sorpreso dal fatto che non ci fosse davvero un posto che avesse una risposta. Avrei pensato che fosse un compito abbastanza comune. Ho pensato di pubblicare una risposta qui per salvarmi me :) qualcuno in futuro, nel caso qualcuno abbia lo stesso problema.

Dapper sembra semplicemente passare il comando SQL direttamente ad ADO.NET e qualunque provider db stia eseguendo il comando. Nella sintassi degli esempi, in cui ogni comando è separato da un'interruzione di riga, SQL Server lo interpreterà come query multiple da eseguire sul database ed eseguirà ciascuna delle query e restituirà i risultati in output separati. Non sono un esperto di ADO.NET, quindi potrei incasinare la terminologia, ma l'effetto finale è che Dapper ottiene gli output di query multiple e quindi fa la sua magia.

Oracle, tuttavia, non riconosce le query multiple; pensa che il comando SQL non sia corretto e restituisce ORA-00933 Messaggio. La soluzione consiste nell'usare i cursori e restituire l'output in una raccolta DynamicParameters. Ad esempio, mentre la versione di SQL Server sarebbe simile a questa:

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

la versione Oracle della query dovrebbe essere simile a questa:

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;";

Per le query eseguite su SQL Server, Dapper può gestirlo da lì. Tuttavia, poiché stiamo restituendo i set di risultati nei parametri del cursore, dovremo utilizzare un IDynamicParameters raccolta per specificare i parametri per il comando. Per aggiungere una ruga in più, il normale DynamicParameters.Add() metodo in Dapper utilizza un System.Data.DbType per il parametro dbType facoltativo, ma i parametri del cursore per la query devono essere di tipo Oracle.ManagedDataAccess.Client.OracleDbType.RefCursor . Per risolvere questo problema, ho utilizzato la soluzione proposta da @Daniel Smith in questa risposta e creato un'implementazione personalizzata di IDynamicParameters interfaccia:

    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());
            }
        }
    }

Quindi tutto il codice insieme va in questo modo:

    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();
    }