Egymást követő több SQL hiba kezelése

Felmerült egy érdekes kérdés Twitteren és a Stackoverflow-n is, nevezetesen: hogyan lehet több hibát "elkapni" SQL kódban. A szomorú hír az, hogy T-SQL-ben a TRY/CATCH nem tudja ezt, pedig volt rá igény, lásd Connect bejegyzés.

Lássuk rá egy konkrét példát:

1SELECT 1/0
2RAISERROR ('második hiba',16,1)
3GO

Ennél az esetnél két hiba is lesz:

Msg 8134, Level 16, State 1, Line 18
Divide by zero error encountered.
Msg 50000, Level 16, State 1, Line 19
második hiba

Lássuk mindezt TRY/CATCH használatával:

1BEGIN TRY
2SELECT 1/0
3RAISERROR ('második hiba',16,1)
4END TRY
5BEGIN CATCH
6    SELECT ERROR_NUMBER(), ERROR_MESSAGE()
7END CATCH
8 
9GO

Ebben az esetben csak a nullával való osztásra fogjuk megkapni a 8134-es hibát. Látható, hogy a CATCH csak 1 hibát ad vissza. Ennek ellenére és az XACT_ABORT beállítástól függően, több hibánk is lehet. Lássunk egy másik példát is: egy mentést szeretnék csinálni, azonban nincs joga az SQL Server-nek írni az adott mappába. Ilyenkor két hibát is kapok:

Msg 3201, Level 16, State 1, Line 5
Cannot open backup device 'C:\temp\backup\aw.bak'. Operating system error 5(Access is denied.).
Msg 3013, Level 16, State 1, Line 5
BACKUP DATABASE is terminating abnormally.

Ezt az SSMS simán visszaadja, de egy TRY/CARCH esetén már csak a 3013-as hiba jön vissza. Akkor ezt most hogyan is lehet szépen megfogni? Sajnos csak olyan alkalmazásból lehet elkapni, ami az SQLException .NET objektummal tud dolgozni. Ez akár lehetne gy CLR sproc is, de az már nem a leghatékonyabb :) Én egy parancssori alkalamzás segítségével mutatnám ezt be:

 1using System;
 2using System.Collections.Generic;
 3using System.Linq;
 4using System.Text;
 5using System.Threading.Tasks;
 6using System.Data.SqlClient;
 7using System.Data;
 8 
 9namespace multipleSqlExceptions
10{
11    class Program
12    {
13        static void Main(string[] args)
14        {
15            using (SqlConnection conn = new SqlConnection(@"Data Source=.;Initial Catalog=master;Integrated Security=True"))
16            {
17                conn.Open();
18 
19                using (SqlCommand comm = conn.CreateCommand())
20                {
21                    comm.CommandText = @"BACKUP DATABASE AdventureWorks TO DISK = N'C:\temp\backup\aw.bak';";
22                    comm.CommandType = CommandType.Text;
23 
24                    try
25                    {          
26                        comm.ExecuteNonQuery();
27                    }
28                    catch (SqlException e)
29                    {
30                        foreach (SqlError err in e.Errors)
31                        {  
32                            Console.WriteLine("ERROR:");
33                            Console.WriteLine("Code: " + err.Number);
34                            Console.WriteLine("Message: " + err.Message);
35                            Console.WriteLine("-----------------------------------------------");
36                        }
37                    }
38 
39                }
40            }
41 
42            Console.ReadKey();
43        }
44    }
45}

Az SSMS persze mindezt azért tudja, mert egy .NET alkalmazás és pontosan ezzel a módszerrel dolgozik.