Oracle
 sql >> Datenbank >  >> RDS >> Oracle

Verwenden von Dapper QueryMultiple in Oracle

Das OP hat das Problem wahrscheinlich längst gelöst, aber zum Zeitpunkt des Schreibens hat diese Frage nur eine Antwort und löst das Problem der Verwendung von Dappers QueryMultiple() nicht wirklich Methode mit Oracle. Wie @Kamolas81 korrekt feststellt, erhält man bei Verwendung der Syntax aus den offiziellen Beispielen tatsächlich den ORA-00933: SQL command not properly ended Fehlermeldung. Ich verbrachte eine Weile damit, nach einer Art Dokumentation darüber zu suchen, wie man QueryMultiple() macht bei Oracle, aber ich war überrascht, dass es nicht wirklich einen Ort gab, der eine Antwort hatte. Ich hätte gedacht, dass dies eine ziemlich häufige Aufgabe ist. Ich dachte, dass ich hier eine Antwort poste, um mich zu retten :) Jemand irgendwann in der Zukunft, nur für den Fall, dass irgendjemand das gleiche Problem hat.

Dapper scheint den SQL-Befehl direkt an ADO.NET und den Datenbankanbieter weiterzugeben, der den Befehl ausführt. In der Syntax aus den Beispielen, in der jeder Befehl durch einen Zeilenumbruch getrennt ist, interpretiert SQL Server dies als mehrere Abfragen, die für die Datenbank ausgeführt werden sollen, führt jede der Abfragen aus und gibt die Ergebnisse in separaten Ausgaben zurück. Ich bin kein ADO.NET-Experte, daher bringe ich vielleicht die Terminologie durcheinander, aber der Endeffekt ist, dass Dapper die mehreren Abfrageausgaben erhält und dann seine Magie entfaltet.

Oracle erkennt jedoch die mehreren Abfragen nicht; es denkt, dass der SQL-Befehl fehlerhaft ist und gibt den ORA-00933 zurück Botschaft. Die Lösung besteht darin, Cursor zu verwenden und die Ausgabe in einer DynamicParameters-Sammlung zurückzugeben. Während die SQL Server-Version beispielsweise so aussehen würde:

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

die Oracle-Version der Abfrage müsste folgendermaßen aussehen:

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

Für Abfragen, die gegen SQL Server ausgeführt werden, kann Dapper sie von dort aus verarbeiten. Da wir die Ergebnismengen jedoch in Cursorparameter zurückgeben, müssen wir einen IDynamicParameters verwenden -Auflistung, um Parameter für den Befehl anzugeben. Um eine zusätzliche Falte hinzuzufügen, verwenden Sie das normale DynamicParameters.Add() -Methode in Dapper verwendet einen System.Data.DbType für den optionalen dbType-Parameter, aber die Cursorparameter für die Abfrage müssen vom Typ Oracle.ManagedDataAccess.Client.OracleDbType.RefCursor sein . Um dies zu lösen, habe ich die Lösung verwendet, die @Daniel Smith in dieser Antwort vorgeschlagen hat und eine benutzerdefinierte Implementierung der IDynamicParameters erstellt Schnittstelle:

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

Der gesamte Code zusammen sieht also etwa so aus:

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