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!?
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.
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?
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:
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>.
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.
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.
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:
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???
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.
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.
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();
}
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:
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å?
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