Avatar billede baitianlong Nybegynder
09. maj 2010 - 13:01 Der er 18 kommentarer og
1 løsning

Korrekt brug af using, try, catch og finally med SQL?

Jeg koder p.t. et website i asp.net C# med Sql Server Database.

Jeg vil gerne anvende den bedste kode i mine metoder, men jeg synes mange sider paa nettet har forskellige forslag og forskellige implementationer. De fleste er dog enige om at using er den rette maade til SqlConnection, men hvordan er den HELT KORREKTE / BEDSTE struktur paa en metode?

EKSEMPEL ( fra MSDN )

private static void ReadOrderData(string connectionString)
{
    string queryString =
        "SELECT OrderID, CustomerID FROM dbo.Orders;";
    using (SqlConnection connection = new SqlConnection(
              connectionString))
    {
        SqlCommand command = new SqlCommand(
            queryString, connection);
        connection.Open();
        SqlDataReader reader = command.ExecuteReader();
        try
        {
            while (reader.Read())
            {
                Console.WriteLine(String.Format("{0}, {1}",
                    reader[0], reader[1]));
            }
        }
        finally
        {
            // Always call Close when done reading.
            reader.Close();
        }
    }
}

-Hvorfor er den metode static? Man maa jo ikke bruge static i f.eks JSP?
-Burde der ikke vaere Using rundt om SqlCommand, saa den ogsaa automatisk bliver disposed efter brug?
-Hvorfor er connection.Open() og command.ExecuteReader() ikke inde i try/catch? Det kan da gaa galt?
-Der er faktisk ikke nogen catch?

Er det bedste ikke at bruge using paa SqlConnection'en og paa SqlCommand'en osv..?

Jeg haaber der er en ekspert med den klokke-klare definition paa hvordan man bedst strukturerer sine metoder saa man kan have flest mulige brugere og hurtigst/sikrest kode.
Avatar billede arne_v Ekspert
09. maj 2010 - 15:47 #1
Der er sjældent et entydigt svar på hvad der er bedst.

Men lidt blandede kommentarer.

Du bør bruge using statement i alle tilfælde hvor:
- du har noget som implemengerer IDisposable
- det eneste som du er interesseret i er at få lukket

SqlCommand og SqlDataReader kan derfor også puttes i deres egne using.

Imidlertid er der generelt en antagelse om at hvos connection bliver disposed så bliver alle de afledte objekter command og data reader også lukket.

Så vidt jeg ved holder den antagelse i praksis. Det er imidlertid ikke specielt pænt at lave den slags antagelser.
Avatar billede arne_v Ekspert
09. maj 2010 - 15:49 #2
static er helt fint når der ikke er nogen kontekst og der heller aldrig vil blive en kontekst. Og det gælder sådan set både JSP og ASP.NET - det er bare ret sjældent at det er tilfældet.
Avatar billede arne_v Ekspert
09. maj 2010 - 15:53 #3
finally uden catch boer bruges meget sjaeldent i C#, da det jo er hvad using goer.

Open er OK - fordi connecition bliver disposed men reader goer ikke, hvilket giver mening.

Al haandtering af exceptions er flyttet ud af den metode og skal haandteres et andet sted. Det kan vaere godt eller det kan vaere skidt.
Avatar billede baitianlong Nybegynder
09. maj 2010 - 17:50 #4
Hej Arne,

Jeg syntes ogsaa at try uden catch var lidt underlig...

Du er saadan-set med mig paa de fleste punkter. SqlCommand burde ogsaa vaere i sin egen using men det kan pludselig vaere svaert at definere objekterne udenfor try catch med mindre man definerer en sqlconnection, en sqlcommand og en sqldatareader til null inden man begynder paa noget som helst... er det det man skal goere ??

Jeg er saadan set ude efter en skudsikker struktur af using/try/catch/finally som jeg saa vil implementere i alle metoder i projektet.
Avatar billede baitianlong Nybegynder
09. maj 2010 - 17:51 #5
og som jeg laeser dit indlaeg er static ikke saa smart nogen som helst steder i web projekter..  Og det er ogsaa hvad jeg har laert med jsp/java
Avatar billede arne_v Ekspert
09. maj 2010 - 18:03 #6
Hvis exception håndtering skal ske højere oppe, så vil 3 nestede using være en oplagt mulighed.
Avatar billede arne_v Ekspert
09. maj 2010 - 18:06 #7
Static kan godt bruges i web sammenhæng også. Hvis det pågældende er kontekst frit.

DAAB bruger f.eks. nogle static metoder meget lig din metode.

Men i det fleste tilfælde er det ikke godt. Et med den form for database kode er at der ikke er nogen mulighed for virtuelle metoder og polymorfisme.
Avatar billede baitianlong Nybegynder
09. maj 2010 - 19:10 #8
ok Arne, Jeg vil gerne have en struktur, som jeg kan bruge i min webapp og i flere andre webapps... Hvornaar skal sqlconnections, sqlcommands, sqldatareaders lukkes eller disposes etc...

Giv mig en stil, jeg kan bruge i alle mine metoder som snaker med databasen. Og dette er ikke
NASA website eller lign (som du formodentlig kan gaette). En pointer om en fornuftig struktur af using og try catch er saadanset hvad jeg er ude efter i et eksempel der kan daekke en normal webapp paa et hosted domain med ca. 100 besoegende samtidig.

Det er .net til SqlServer og det burde klargoere tingene
Avatar billede arne_v Ekspert
09. maj 2010 - 20:40 #9
Persistering er nomalt et sted mellem 25 og 33% af en web app.

Det er noget af en opgave at skulle komme med et pattern som løser dette problem.

:-)

Men det følgende indeholder flere gode aspekter:


using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;

namespace E
{
    public class T1
    {
        public int F1 { get; set; }
        public string F2 { get; set; }
    }
    public class DBException : Exception
    {
        public DBException(Exception ex) : base("Implementation specific exception", ex)
        {
        }
    }
    public interface IT1DB
    {
        IList<T1> GetAll();
        T1 GetOne(int f1);
        void AddOne(T1 o);
        void DeleteOne(T1 o);
    }
    public class T1SQLServer : IT1DB
    {
        private string constr;
        public T1SQLServer(string constr)
        {
            this.constr = constr;
        }
        public IList<T1> GetAll()
        {
            try
            {
                List<T1> res = new List<T1>();
                using(SqlConnection con = new SqlConnection(constr))
                {
                    con.Open();
                    using(SqlCommand cmd = new SqlCommand("SELECT f1,f2 FROM t1", con))
                    {
                        using(SqlDataReader rdr = cmd.ExecuteReader())
                        {
                            while(rdr.Read())
                            {
                                res.Add(new T1 { F1 = (int)rdr[0], F2 = (string)rdr[1] });
                            }
                        }
                    }
                }
                return res;
            }
            catch (SqlException ex)
            {
                throw new DBException(ex);
            }
        }
        public T1 GetOne(int f1)
        {
            try
            {
                T1 res = null;
                using(SqlConnection con = new SqlConnection(constr))
                {
                    con.Open();
                    using(SqlCommand cmd = new SqlCommand("SELECT f1,f2 FROM t1 WHERE f1 = @f1", con))
                    {
                        cmd.Parameters.Add("@f1", SqlDbType.Int);
                        cmd.Parameters["@f1"].Value = f1;
                        using(SqlDataReader rdr = cmd.ExecuteReader())
                        {
                            if(rdr.Read())
                            {
                                res = new T1 { F1 = (int)rdr[0], F2 = (string)rdr[1] };
                            }
                        }
                    }
                }
                return res;
            }
            catch (SqlException ex)
            {
                throw new DBException(ex);
            }
        }
        public void AddOne(T1 o)
        {
            try
                {
                using(SqlConnection con = new SqlConnection(constr))
                {
                    con.Open();
                    using(SqlCommand cmd = new SqlCommand("INSERT INTO t1(f1,f2) VALUES(@f1,@f2)", con))
                    {
                        cmd.Parameters.Add("@f1", SqlDbType.Int);
                        cmd.Parameters.Add("@f2", SqlDbType.VarChar, 50);
                        cmd.Parameters["@f1"].Value = o.F1;
                        cmd.Parameters["@f2"].Value = o.F2;
                        cmd.ExecuteNonQuery();
                    }
                }
            }
            catch (SqlException ex)
            {
                throw new DBException(ex);
            }
        }
        public void DeleteOne(T1 o)
        {
            try
            {
                using(SqlConnection con = new SqlConnection(constr))
                {
                    con.Open();
                    using(SqlCommand cmd = new SqlCommand("DELETE FROM t1 WHERE f1 = @f1", con))
                    {
                        cmd.Parameters.Add("@f1", SqlDbType.Int);
                        cmd.Parameters["@f1"].Value = o.F1;
                        cmd.ExecuteNonQuery();
                    }
                }
            }
            catch (SqlException ex)
            {
                throw new DBException(ex);
            }
        }
    }
    public class Program
    {
        public static void Main(string[] args)
        {
            try
            {
                IT1DB db = new T1SQLServer(@"Server=ARNEPC3\SQLEXPRESS;Database=Test;Integrated Security=SSPI");
                T1 o = db.GetOne(2);
                Console.WriteLine(o.F2);
                foreach(T1 r in db.GetAll())
                {
                    Console.WriteLine(r.F2);
                }
                T1 z = new T1 { F1 = 77, F2 = "ZZZZ" };
                db.AddOne(z);
                foreach(T1 r in db.GetAll())
                {
                    Console.WriteLine(r.F2);
                }
                db.DeleteOne(z);
                foreach(T1 r in db.GetAll())
                {
                    Console.WriteLine(r.F2);
                }
            }
            catch (DBException ex)
            {
                Console.WriteLine(ex.Message + " -> " + ex.InnerException.Message);
            }
        }
    }
}
Avatar billede arne_v Ekspert
09. maj 2010 - 20:43 #10
Database uafhængigt:
- interface
- exception
- data klasse

Implementation klasse for SQLServer.

Standard operationer som kun bruger simple typer, data klasser og collections af data klasser.

Database specifikke exceptions wrappes så de er nemme at catche i en catch clause.

Using på connection/command/reader.

Parameter.

Connection string som input (kalder kan hente den fra web.config).
Avatar billede arne_v Ekspert
09. maj 2010 - 20:43 #11
Når du vokser fra dette approach skal du nok til at kigge på ORM.
Avatar billede janus_007 Nybegynder
09. maj 2010 - 21:12 #12
Ja jeg skulle lige til at sige det :) , det er efterhånden lidt oldschool sådan at sidde og skrive datalag selv, medmindre man har brug for noget virkelig specielt.

baitianlong -> Kig på Linq to Sql/ Entities.

Btw. istedet for at skrive nested usings kan de også skrives som :

using(SqlConnection con = new SqlConnection(constr))
using(SqlCommand cmd = new SqlCommand())
{
.... code :)

}
Det gør koden lidt nemmere at læse synes jeg :)

Ellers har jeg ingen kommentarer til arnes forslag, det vil du kunne komme i land med uden problemer.
Avatar billede baitianlong Nybegynder
10. maj 2010 - 07:47 #13
Tja, jeg er nok lidt oldschool. Jeg vil typisk skrive:

UserFunctions uf = new UserFunctions();
uf.LoginUser(username, password);

og have alle database kald i diverse klasser i back-enden.

Konklusionen her maa vaere try - using - using - using - catch. Jeg siger mange tak for de gode raad og eksemplet. Og jeg skal nok kigge lidt paa Linq, det ligger jo lige for med .net og visual studio etc.

Smider du et svar Arne?
Avatar billede baitianlong Nybegynder
10. maj 2010 - 14:04 #14
Janus... Jeg kan godt lide din stil med using ovenover hinanden uden klammer

using(SqlConnection con = new SqlConnection(constr))
using(SqlCommand cmd = new SqlCommand())
{
.... code :)

}

Men SqlConnection bliver jo dermed ikke open() inden du laver SqlCommand !?

Gaar det?
Avatar billede janus_007 Nybegynder
10. maj 2010 - 14:57 #15
Det er SqlConnectionen der er instancieret, dvs. du kan blot lave en con.Open(), begge har en default constructor jo :)

http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlcommand.connection%28v=VS.100%29.aspx

Altså...

using(SqlConnection con = new SqlConnection(constr))
using(SqlCommand cmd = new SqlCommand())
{
con.Open(); //some additional logic maybe :)
cmd.Connection = con;

}
Avatar billede baitianlong Nybegynder
10. maj 2010 - 15:21 #16
Ja ok.. Der kommer saa stadig klammer om using sqldatareader saaledes:

try
        {
            using (SqlConnection con = new SqlConnection(ConfigurationManager.ConnectionStrings["rcs"].ToString()))
            using (SqlCommand cmd = new SqlCommand())
            {
                con.Open();
                cmd.CommandText = sql;
                cmd.Connection = con;
                using (SqlDataReader rdr = cmd.ExecuteReader())
                {
                    if (rdr.Read()) result = true;
                }
            }
           
        }
        catch (Exception e)
        {
            throw e;
        }

Jeg syntes ellers det var 'fancy' paa denne maade:

using (SqlConnection con = new SqlConnection(ConfigurationManager.ConnectionStrings["rcs"].ToString()))
            using (SqlCommand cmd = new SqlCommand(sql, con))
            using (SqlDataReader rdr = cmd.ExecuteReader())
            {
                rdr.Read();

Men den holder ikke helt :))

Anyway. Tak for info'en. Hvis du smider et svar og Arne smider et svar kan jeg fordele pointsne.
Avatar billede janus_007 Nybegynder
10. maj 2010 - 15:28 #17
Ja det har du ret i :)

Det er arnes point, jeg kom blot med lidt kommentarer.
Avatar billede arne_v Ekspert
10. maj 2010 - 16:47 #18
Reglerne for {} eller ej er de samme for using som for if/for/while - hvis der kun er en statement, saa kan de undlades.

De fleste coding conventions kraever {} selv ved enkelt saetninger.
Avatar billede arne_v Ekspert
10. maj 2010 - 16:47 #19
Og et svar.
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview
Kategori
Kurser inden for grundlæggende programmering

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester