webForumDet fria alternativet

Vad är en O/R mapper!?

.NET

25 svar · 1 247 visningar · startad av inspiro

Medlem sedan sep. 2005673 inlägg
Frågan#1

Jag börjar få en hel del kopplingar mot databasen och varje gång jag behöver använda databasen gör jag typ så här:

        SqlConnection myConn = new SqlConnection(ConnStr);
        SqlCommand myCommand = new SqlCommand("procedure", myConn);
        myCommand.CommandType = CommandType.StoredProcedure;

        SqlDataAdapter da = new SqlDataAdapter(myCommand);
        DataTable dt = new DataTable();
        da.Fill(dt);

Men jag börjar förstå att det inte är så snyggt att ha alla dessa databaskopplingar och lägga resultatet i datatables... och "O/R mappers" dyker ofta upp när ni duktiga programmerare pratar om hur man ska hantera data som hämtas från databasen...

Fråga. Vad är en O/R mapper och hur använder jag den? Hur ska jag hantera uppkopplingarna mot databasen nu när applikationen växer!?

Medlem sedan dec. 19996 721 inlägg
#2

En O/R-mapper hjälper till att koppla ihop objekt (O) med deras lagring i relationsdatabasen (R). Om du har en Customer-klass

public class Customer
{
   public string Name
   {
      ......
   }
}

...så kan du berätta för O/R-mappern att Customer-objekt ska lagras i en tabell som heter "Customers" och att Name-egenskapen ska lagras i en kolumn som heter "Name". I stället för att skriva bökig kod som via SQL-satser hämtar upp data från tabellen för att bygga upp Customer-objekt och andra bökiga SQL-satser för att uppdatera och lägga till dem i databasen, så låter man O/R-mappern göra allt.

Pseduo-kod:

Customer c=MinMapper.GetById(typeof(Customer),kundNummerFrånFormulär) as Customer;
c.Name="Kundens nya namn";
MinMapper.Save(c);

Oerhört smidigt, och man kan helt plötsligt koncentrera sig på de viktiga aspekterna av ariktekturen, i stället för att lägga timmar/dagar/månader på att skriva SQL-satser.

Rent principiellt finns det inget som säger att en O/R-mapper är bra på att hantera databaskopplingar eller att den ens kan hantera det överhuvud taget, men i praktiken så är de flesta tillgängliga alternativen rätt bra på det.

Medlem sedan sep. 2005673 inlägg
#3

Men det är alltså inte i själva O/R mappern i sig som man har kopplingarna mot databasen, utan det är något slags interface? Känns inte som jag är på den nivån än. :)

Mitt problem är att jag behöver något slags dal som först och främst kan hantera frågor (via procedurer) till databasen på ett smidigt sätt. Alltså skapa någon slags klass där man stoppar in namnet på proceduren, eventuella parametrar och får tillbaka en datatable. Är inte det vettigt eller tänker jag fel?

Men hur hanterar jag exempelvis parametrarna? Ibland är det ingen parameter, ibland 20. Hur gör man det skalbart lixom?

Medlem sedan dec. 19996 522 inlägg
#4

Sök på DAL, har varit uppe ett antal gånger, kanske är lättare än att hoppa på en OR-mapper, annars är nHibernate ett urmärkt val som OR-mapper

Medlem sedan dec. 19996 721 inlägg
#5

Du skulle kunna ta en titt på Microsofts Data Application Block

Medlem sedan dec. 19996 522 inlägg
Medlem sedan sep. 2005673 inlägg
#7

Ok, nu har jag gjort ett första försök att få en snyggare lösning för mina databasanrop. Kan man få en kommentar... bra, dåligt? :) Så här har jag gjort:

Jag har gjort en klass som heter Database. Där har jag funktionen GetDataTable med en overload (heter det så???). Båda funktionerna tar emot namnet på en stored procedure och den ena även en collection med parametrar:

    public DataTable GetDataTable(string procName)
    {
        SqlCommand cmd = CreateCommand(procName, null);
        SqlDataAdapter da = new SqlDataAdapter(cmd);
       
        DataTable dt = new DataTable();
        da.Fill(dt);
        
        this.Close();
        
        return dt;
    }

Hör finns också en privat funktion som öppnar en connections och hanterar eventuella parametrar:

    private SqlCommand CreateCommand(string procName, SqlParameter[] prams)
    {
        Open();

        SqlCommand cmd = new SqlCommand(procName, con);
        cmd.CommandType = CommandType.StoredProcedure;

        if (prams != null)
        {
            foreach (SqlParameter parameter in prams)
                cmd.Parameters.Add(parameter);
        }

        return cmd;
    }

Vad tror ni? Är det vettigt att returnera en datatable (jag använder ofta dem)? Hur kan jag vidareutveckla mitt DAL, vad är så att säga nästa steg?

Medlem sedan mars 20023 561 inlägg
#8

Börja använda using {}-block! :)

public DataTable GetDataTable(string procName)
{
    using (SqlDataAdapter da = new SqlDataAdapter(CreateCommand(procName, null)))
    {
        DataTable dt = new DataTable();
        da.Fill(dt);
    }
    return dt;
}

Sedan vore det nog snyggare att ha en SqlParameterCollection som in-parameter på CreateCommand-metoden, eller möjligvis List<SqlParameter>.

Medlem sedan sep. 2005673 inlägg
#9

Ok tack! "Using" är ungefär samma som att använda try-catch eller?

Fråga två... hur sätter man bäst värdet på parametern som ska skickas in i SPn? Hur skulle man göra för att använda en sqlParameterCollection? Jag gjorde så här nu men det känns ju fuligast.

    public SqlParameter MakeParam(string ParamName, SqlDbType DbType, string ParamValue)
    {
        SqlParameter param = new SqlParameter(ParamName, DbType);
        param.Value = ParamValue;
        return param;
    }
Medlem sedan dec. 19996 721 inlägg
#10

inspiro skrev:

Ok tack! "Using" är ungefär samma som att använda try-catch eller?

Nej, ett using-block använder man på en klass som implementerar interfacet IDisposable, och det som händer är att man säkerställer att objektet "Disposas", oavsett vad som händer inne i using-blocket. Man kan likna det vid ett try/finally-block, men det är en snyggare konstruktion.

using(GlassBåt a=new GlassBåt())
{
   //gör saker med "a"
} <--- Här kommer a.Dispose() att anropas automatiskt, oavsett om saker gick åt pipan inne i blocket

vilket motsvarar

try
{
   GlassBåt a=new GlassBåt();
   //gör saker med "a"
}
finally
{
   a.Dispose();
}

Det fina i kråksången är att Dispose-metoden på vissa klasser gör en del extra vettigt, och då är det framför allt värt att nämna SqlConnection, som kör en Close() på uppkopplingen.

using(SqlConnection conn=new SqlConnection(connStr))
{
   conn.Open();
   //Gör en massa databasanrop och annat skojigt.
}

Genom att använda using-blocket kan vi strunta i att anropa Close-metoden, och kan dessutom vara säkra på att den körs, oavsett vad som händer.

Att använda "using" på en SqlDataAdapter ger dock inga sådana fördelar.

Medlem sedan sep. 2005673 inlägg
#11

Har råkat in i lite problem också... :)

När jag anropar funktionen makeparam i min db-klass så fungerar det bra. Men försöker jag återanvända "prams" så får jag fel. Fattar inte varför! Koden:

Database db = new Database();
SqlParameter[] prams = {db.MakeParam("@UserID", SqlDbType.Int, value)};

repeater1.DataSource = db.GetDataTable("SP1", prams).DefaultView;
repeater2.DataSource = db.GetDataTable("SP2", prams).DefaultView;

Felet jag får är "The SqlParameter is already contained by another SqlParameterCollection. " Men hur kan det komma sig? Jag anropar den ju bara en gång? Koden det blir fel i:

private SqlCommand CreateCommand(string procName, SqlParameter[] prams)
    {
        Open();
        SqlCommand cmd = new SqlCommand(procName, con);
        cmd.CommandType = CommandType.StoredProcedure;

        if (prams != null)
        
[b]{
            foreach (SqlParameter parameter in prams)
                cmd.Parameters.Add(parameter);
        }
[/b]        return cmd;
    }
Medlem sedan sep. 2005673 inlägg
#12

Jag tror jag har förstått att jag måste köra en "command.Parameters.Clear()" men hur gör jag det!? Jag har ju en funktion som returnerar parametrarna så jag kan ju inte cleara dem i den? Ska jag göra det i mitt using-block på nåt sätt... vet inte hur man skriver i så fall...

using (SqlDataAdapter da = new SqlDataAdapter(CreateCommand(procName, prams)))
            da.Fill(dt);
Clear Parameters???
Medlem sedan dec. 19996 721 inlägg
#13

När du lägger till parametrarna med cmd.Parameters.Add, så lägger du till dem till SqlCommandets egen SqlParameterCollection. Vid nästa anrop kommen den att försöka lägga till den till ett annat kommandos SqlParameterCollection, och det får man inte (felmeddelandet säger ju faktiskt det).

Antingen får du skicka en en färdig instans av SqlParameterCollection, i stället för arrayen med kommandon, eller så får du rensa Parameters med en Parameters.Clear(), så snart parametrarna är använda.

Medlem sedan dec. 19996 721 inlägg
#14

inspiro skrev:

Jag tror jag har förstått att jag måste köra en "command.Parameters.Clear()" men hur gör jag det!? Jag har ju en funktion som returnerar parametrarna så jag kan ju inte cleara dem i den? Ska jag göra det i mitt using-block på nåt sätt... vet inte hur man skriver i så fall...

using (SqlDataAdapter da = new SqlDataAdapter(CreateCommand(procName, prams)))
            da.Fill(dt);
Clear Parameters???

I detta fall:

da.SelectCommand.Parameters.Clear();

BTW. Jag rekommenderar att du minskar ner på nästlandet av metodanrop. Koden blir mer svårläst, hopplös att debugga med breakpoints, och variabelscopen (som i detta fall) kan bli knepiga. Ibland är det smidigt, men oftast inte.

Medlem sedan sep. 2005673 inlägg
#15

En till nybörjarfråga men hur fyller man en SqlParameterCollection? Det går inte så bra...

 
SqlParameterCollection sqlParams = new SqlParameterCollection();
sqlParams.Add("@test", SqlDbType.NVarChar).Value = "test";
Medlem sedan dec. 19996 721 inlägg
#16

SqlParameterCollections konstruktor är internal, så den approachen kan du visst skippa.

Medlem sedan sep. 2005673 inlägg
#17

Men hur gör man då för att använda SqlParameterCollections? Hur lägger man in parametrar i en SqlParameterCollections och hur använder man dem? Det fungerade i vilket fall med da.SelectCommand.Parameters.Clear();

:)

SPinner vidare på mitt DAL... är det vettigt att returnea en datatable? Exempel, hur gör man i en sån här situation. Detta känns inte optimalt:

min function {
  Database db = new Database();
  DataTable dt = db.GetDataTable("sp", params);
  string s = dt.Rows[0]["test"].ToString();
}
Medlem sedan dec. 19996 721 inlägg
#18

http://msdn2.microsoft.com/en-us/library/system.data.sqlclient.sqlparametercollection.add.aspx

Om man bara ska ha tillbaka ett enda värde så ska man använda ExecuteScalar på kommandot, och inte gå en dyr omväg via DataTable.

Data Application Block skulle kunna ge dig en bra start till ett DAL.

Medlem sedan sep. 2005673 inlägg
#19

Yep, executescalar använder jag också, det har jag lärt mig. :) Men jag tänkte mer på den konstruktionen att du returnerar en datatable och skapar en ny datatable som du lägger den i, alltså detta:

DataTable dt = db.GetDataTable("sp", params);

Säg att man använder en massa data så här:

string 1 = dt.Rows[0]["a"].ToString();
string 2 = dt.Rows[1]["b"].ToString();
string 3 = dt.Rows[2]["c"].ToString();

Då behöver man ju ha datatable-objektet och då tvingas man väl göra som jag gjorde ovan om man returnerar en datatable, DataTable dt = db.GetDataTable("sp", params). Får man inte två datatableobjekt då?

Medlem sedan dec. 19996 721 inlägg
#20

inspiro skrev:

Då behöver man ju ha datatable-objektet och då tvingas man väl göra som jag gjorde ovan om man returnerar en datatable, DataTable dt = db.GetDataTable("sp", params). Får man inte två datatableobjekt då?

Nej, det blir bara en instans.

263 ms totalt · 4 externa anrop · v20260731065814-full.e96017d9
122 ms — deklarationer (db)
0 ms — hämta statistik (cache)
133 ms — hämta tråd, inlägg och bilagor (db)
127 ms — ändringar (db)