none
Korrektes Abrufen von Rückgabewert/temporärer Tabelle von Stored-Procedure RRS feed

  • Frage

  • Hallo,

    ich habe in einem 2008-R2-Server eine Stored-Procedure, die entweder 0 ("erfolgreich") oder 1 ("Fehler") zurückliefert.

    Falls ein Fehler auftritt, wird zusätzlich in eine temporäre Tabelle "#error" geschrieben:

      Fehlercode | Beschreibung
    ----------------| -----------------
          1            | Fehler bei XYZ.

    Ich kann aber nur ENTWEDER den Rückgabewert der SP ODER die Tabelle auslesen, wie mir scheint:

    SqlCommand.ExecuteScalar( ) gibt zurück "(byte) 0" im Erfolgsfall, und "null", wenn ein Fehler auftritt.
    Verwende ich stattdessen SqlCommand.ExecuteReader( ), dann ist jedoch SqlDataReader.Read( ) "false" im Erfolgsfall - im Fehlerfall habe ich dort jedoch eine Zeile wie oben, mit Fehlercode und Beschreibung (aus der "#error"-Tabelle).

    Wie kann ich denn auch im Fehlerfall den Rückgabewert der SP bekommen?
    Ich möchte nämlich den Fall abfangen, dass es zwar einen Rückgabewert von "1" gab - aber trotzdem keine detaillierte Beschreibung...

    Danke!


    Hypnose Berlin

    Montag, 13. Februar 2012 12:16

Antworten

  • Hallo Marvin,

    mit ExecuteScalacr wird das nie was werden, denn das liefert nur die erste Spalte aus dem ersten Ergebnis.
    Einen Rückgabewert (also einen Parameter) kriegst Du damit nie in die Finger.

    Deine Prozedur etwas erleichtert und um einen Dummy Paramter ergänzt:

    CREATE PROCEDURE dbo.MarvinSP
    	@withError bit = 0
    AS
    BEGIN
    	SET NOCOUNT ON;
    	DECLARE @Error bit = 0;
    
    	CREATE TABLE #errors (
    		errorcode		tinyint,
    		errormess		varchar(80) COLLATE database_default);
    
    	IF (@withError = 1)
    		INSERT #errors VALUES (1,'Fehler bei XYZ.');
    
    	IF (SELECT COUNT(*) FROM #errors) > 0
    	BEGIN
    		SELECT errorcode,errormess FROM #errors ORDER BY errorcode;
    		SET @error = 1;
    	END
    
    	IF @error <> 0
    		RETURN 1;
    	ELSE
    		RETURN 0;
    END
    GO
    DECLARE @rc int;
    EXEC @rc = dbo.MarvinSP 0;
    SELECT @rc AS OhneFehler;
    
    EXEC @rc = dbo.MarvinSP 1;
    SELECT @rc AS MitFehler;
    GO 

    Die Methode zum Verarbeiten dazu -  wobei Du die Do While (NextResult) Schleife rauswerfen kannst,
    wenn es nur ein Resultset hier die Errors gibt:

            private void ExecuteMarvin(bool withError)
            {
                using (var connection = new SqlConnection(Properties.Settings.Default.DefaultConnectionString))
                {
                    connection.Open();
                    var command = new SqlCommand("dbo.MarvinSP", connection);
                    command.CommandType = CommandType.StoredProcedure;
                    command.Parameters.Add("@withError", SqlDbType.Bit).Value = withError;
                    command.Parameters.Add("@rc", SqlDbType.Int).Direction = ParameterDirection.ReturnValue;
    
                    using (var reader = command.ExecuteReader())
                    {
                        int result = 0;
                        do 
                        {
                            while (reader.Read())
                            {
                                if (result == 0)
                                {
                                    // Sinnvoller wäre: List<...>
                                    Console.WriteLine("Error: {0} '{1}'", reader.GetByte(0), reader.GetString(1));
                                }
                            }
                            result++;
                        } while (reader.NextResult());
                    }
                    Console.WriteLine("Return Value: {0}", command.Parameters["@rc"].Value);
                }
            }
    

    Und ausführen als:

                ExecuteMarvin(false);
                ExecuteMarvin(true);

    Gruß Elmar

    Montag, 13. Februar 2012 16:41
    Beantworter
  • Hallo Marvin,

    der withErrors Parameter war nur zum Zeigen beider Ausgabe-Varianten,
    bei Dir müsstest Du die "realen" Parameter zusätzlich zum ReturnValue Parameter übergeben.

    Die Errors könnte man als out Parameter bei die C#-Methode definieren,
    die dann einen bool liefern könnte.

    Ob man "@" verwendet ist bei den Parameternamen egal - das biegt der Sql Client dann hin,
    beim Verwenden im SQL (CommandText) jedoch nicht.
    Ich persönlich verwende es immer (ggf. durch eine Fixup-Methode ergänzt).

    Gruß Elmar

    Montag, 13. Februar 2012 18:18
    Beantworter

Alle Antworten

  • Die SP sieht übrigens ungefähr so aus:

    CREATE PROCEDURE dbo.MeineSP
    
    -- [lauter Parameter]
    
    --WITH ENCRYPTION
    AS
    --$Date: 10-07-05 17:48 $
    BEGIN
    	SET NOCOUNT ON
    	SET XACT_ABORT ON
    
    	CREATE TABLE #errors (
    		errorcode		tinyint,
    		errormess		varchar(80) COLLATE database_default)
    
    	IF (irgendeine Prüfung)
    		INSERT #errors VALUES (1,'Fehler bei XYZ.')
    
    
            -- Mehr Prüfungen
    
            IF (SELECT COUNT(*) FROM #errors) > 0
    	BEGIN
    		SELECT errorcode,errormess FROM #errors ORDER BY errorcode
    		SET @error = 1
    	END
    
    
            IF (alles OK)
            BEGIN
    
                    IF @@ERROR <> 0
    			SET @error = 1
    			
    		DECLARE	@return_value int
    
    		EXEC	@return_value = dbo.AndereSP
    		
    		IF @return_value <> 0 OR @@ERROR <> 0
    			SET @error = 1
    	END
    
    	IF @error <> 0
    		BEGIN
    			ROLLBACK TRAN
    			RETURN 1
    		END
    	ELSE
    		BEGIN
    			COMMIT TRAN
    			RETURN 0
    		END
    END
    GO


    Hypnose Berlin

    Montag, 13. Februar 2012 12:50
  • Und ich kann die SP nicht ändern, die ist so von einem Dritthersteller vorgegeben...

    Hypnose Berlin

    Montag, 13. Februar 2012 12:50
  • Hallo Marvin,

    der Rückgabewert wie auch alle Ausgabeparameter (OUT) stehen erst nach dem letzten Resultset zur Verfügung.
    Wenn die Prozedur im Fehlerfall die #errors ausgibt, wirst Du die erst verarbeiten müssen.

    Gibt es in echt mehrere Ergebnisse müsstest Du Dich NextResult bis zum Ende durcharbeiten,
    siehe Abrufen von Daten mit einem 'DataReader' (ADO.NET)

    BTW:
    Das so ziemlich am Schluss stehende

     IF @@ERROR <> 0
        SET @error = 1

    wird nie zutreffen, da @@ERROR nach jeder Anweisung - und IF (alles ok) ist auch eine - zurückgesetzt wird.
    Siehe Error Handling in SQL 2000 – a Background

    Falls das im Original ähnlich vorkommt, wäre dort eine Überarbeitung fällig.

    Gruß Elmar


    Montag, 13. Februar 2012 13:21
    Beantworter
  • Hallo Elmar,

    wenn ein Fehler auftritt, bekomme ich aber leider nur die eine Zeile mit dem Fehler (reader.Read() ist nur 1x true), weitere Werte kommen da nicht...

    Muss ich explizit den Rückgabewert als OUT-Parameter deklarieren?
    (Und falls ja, hast Du dafür gerade ein Beispiel zur Hand? Sonst google ich selbst...)

    Zu @@ERROR:
    Nein, sorry, ich hatte die SP halt radikal gekürzt, damit man die Grundstruktur hier versteht.

    Viele Grüße

    Marvin


    Hypnose Berlin

    Montag, 13. Februar 2012 14:10
  • Ah, pardon, hatte jetzt erst gesehen, dass der MSDN-Artikel da noch weiterging mit NextResult().

    Moment, teste kurz...


    Hypnose Berlin

    Montag, 13. Februar 2012 14:11
  • NextResult() ändert nichts, es gibt trotzdem nur einmal die Zeile mit Fehlercode und Fehlermeldung.
    Weitere Werte kommen da nicht...

    Gut, der Fehlercode stimmt hier ja mit dem Rückgabewert der SP überein ("1"), trotzdem wäre es schön, wenn man den Wert auch dann bekommen könnte, wenn keine ganze Zeile zurückkommt, eben so wie ExecuteScalar( ) beim erfolgreichen Versuch "0" zurückgibt...

    Sinngemäß will ich also:

    // Behandlung von DbNull.Value bitte dazudenken
    byte? retVal = command.ExecuteScalar( );
    
    if( retVal == 0 )
    {
      // Alles OK
    }
    else
    {
      // retVal == 1 oder retVal == null --> Fehler
    
      using( SqlDataReader reader = command.ExecuteReader( ) )
      {
        while( reader.Read( ) )
        {
            int errorCode = reader[0];
            string message = reader[1];
    
            // Fehler behandeln
        }
      }
    }

    Nur dass das natürlich nicht geht, weil das Kommando im Fehlerfall dann ja 2x ausgeführt würde...


    Hypnose Berlin

    Montag, 13. Februar 2012 14:17
  • Hallo Marvin,

    mit ExecuteScalacr wird das nie was werden, denn das liefert nur die erste Spalte aus dem ersten Ergebnis.
    Einen Rückgabewert (also einen Parameter) kriegst Du damit nie in die Finger.

    Deine Prozedur etwas erleichtert und um einen Dummy Paramter ergänzt:

    CREATE PROCEDURE dbo.MarvinSP
    	@withError bit = 0
    AS
    BEGIN
    	SET NOCOUNT ON;
    	DECLARE @Error bit = 0;
    
    	CREATE TABLE #errors (
    		errorcode		tinyint,
    		errormess		varchar(80) COLLATE database_default);
    
    	IF (@withError = 1)
    		INSERT #errors VALUES (1,'Fehler bei XYZ.');
    
    	IF (SELECT COUNT(*) FROM #errors) > 0
    	BEGIN
    		SELECT errorcode,errormess FROM #errors ORDER BY errorcode;
    		SET @error = 1;
    	END
    
    	IF @error <> 0
    		RETURN 1;
    	ELSE
    		RETURN 0;
    END
    GO
    DECLARE @rc int;
    EXEC @rc = dbo.MarvinSP 0;
    SELECT @rc AS OhneFehler;
    
    EXEC @rc = dbo.MarvinSP 1;
    SELECT @rc AS MitFehler;
    GO 

    Die Methode zum Verarbeiten dazu -  wobei Du die Do While (NextResult) Schleife rauswerfen kannst,
    wenn es nur ein Resultset hier die Errors gibt:

            private void ExecuteMarvin(bool withError)
            {
                using (var connection = new SqlConnection(Properties.Settings.Default.DefaultConnectionString))
                {
                    connection.Open();
                    var command = new SqlCommand("dbo.MarvinSP", connection);
                    command.CommandType = CommandType.StoredProcedure;
                    command.Parameters.Add("@withError", SqlDbType.Bit).Value = withError;
                    command.Parameters.Add("@rc", SqlDbType.Int).Direction = ParameterDirection.ReturnValue;
    
                    using (var reader = command.ExecuteReader())
                    {
                        int result = 0;
                        do 
                        {
                            while (reader.Read())
                            {
                                if (result == 0)
                                {
                                    // Sinnvoller wäre: List<...>
                                    Console.WriteLine("Error: {0} '{1}'", reader.GetByte(0), reader.GetString(1));
                                }
                            }
                            result++;
                        } while (reader.NextResult());
                    }
                    Console.WriteLine("Return Value: {0}", command.Parameters["@rc"].Value);
                }
            }
    

    Und ausführen als:

                ExecuteMarvin(false);
                ExecuteMarvin(true);

    Gruß Elmar

    Montag, 13. Februar 2012 16:41
    Beantworter
  • Ahhh, ParameterDirection.ReturnValue war der Trick, danke!!

    Die SP kann ich nicht ändern, daher muss quasi "withErrors" halt immer true sein.

    Noch zur Parameter-Schreibweise:
    Ob ich in .NET ein '@' davor schreibe, ist egal, oder?
    Ich meine nämlich, auch viele Beispiele gesehen zu haben, wo die Parameter ohne '@' übergeben werden, und es funktioniert ja auch ohne...

    Danke!


    Hypnose Berlin

    Montag, 13. Februar 2012 17:54
  • Hallo Marvin,

    der withErrors Parameter war nur zum Zeigen beider Ausgabe-Varianten,
    bei Dir müsstest Du die "realen" Parameter zusätzlich zum ReturnValue Parameter übergeben.

    Die Errors könnte man als out Parameter bei die C#-Methode definieren,
    die dann einen bool liefern könnte.

    Ob man "@" verwendet ist bei den Parameternamen egal - das biegt der Sql Client dann hin,
    beim Verwenden im SQL (CommandText) jedoch nicht.
    Ich persönlich verwende es immer (ggf. durch eine Fixup-Methode ergänzt).

    Gruß Elmar

    Montag, 13. Februar 2012 18:18
    Beantworter
  • Hallo Marvin M4ss1h,

    Ich gehe davon aus, dass die Antworten Dir weitergeholfen haben.
    Solltest Du noch "Rückfragen" dazu haben, so gib uns bitte Bescheid.

    Grüße,
    Robert


    Robert Breitenhofer, MICROSOFT  Twitter Facebook
    Bitte haben Sie Verständnis dafür, dass im Rahmen dieses Forums, welches auf dem Community-Prinzip „Entwickler helfen Entwickler“ beruht, kein technischer Support geleistet werden kann oder sonst welche garantierten Maßnahmen seitens Microsoft zugesichert werden können.

    Donnerstag, 1. März 2012 12:39
    Moderator
  • Hallo Robert, (und auch Elmar)

    ich sehe gerade, ich hatte in der Hektik ganz vergessen, Feedback zu geben und mich zu bedanken:

    Vielen Dank, ja, es funktioniert super, die Antworten von Elmar haben mir hervorragend weitergeholfen.

    Viele Grüße
    Marvin


    Hypnose Berlin

    Donnerstag, 1. März 2012 20:48