var cmd = conn.CreateCommand();
cmd.CommandText = "SELECT DrinkId, Name, Price FROM Drink;";
var drinks = new List<Drink>();
await using (var reader = await cmd.ExecuteReaderAsync())
{
while (await reader.ReadAsync())
{
drinks.Add(new Drink {
DrinkId = reader.GetInt32(0),
Name = reader.GetString(1),
Price = reader.IsDBNull(2)
? null : reader.GetDouble(2)
});
}
}
Sådan så læsning ud før Dapper: hver kolonne hentes i hånden — indeks, datatype og NULL skal selv håndteres. Sammenlign med afsnit 5: Dapper gør præcis dette arbejde for dig, automatisk.
ORM = Object-Relational Mapping: et værktøj, der oversætter mellem to verdener — databasens tabeller/rækker og programmets objekter. Uden en ORM står du selv for oversættelsen, som i afsnit 1.
SQL → objrække i tabel → objekt med properties
obj → SQLobjekt → parametre i INSERT/UPDATE
Full ORMf.eks. EF Core — skriver næsten al SQL for dig
Micro-ORMf.eks. Dapper — du skriver SQL, den mapper objekter
Dapper er et micro-ORM: du skriver selv SQL'en — Dapper
oversætter resultat-rækkerne til C#-objekter (og parametre den anden vej).
Du slipper altså for al den manuelle SqliteDataReader-læsning.
CreateExecuteAsync — INSERT
ReadQueryAsync / QuerySingleAsync — SELECT
UpdateExecuteAsync — UPDATE
DeleteExecuteAsync — DELETE
dotnet add package Dapper
dotnet add package Microsoft.Data.Sqlite
To pakker, hver sin rolle: Dapper er selve
micro-ORM'et — det tilføjer Query/Execute-metoderne.
Microsoft.Data.Sqlite er driveren: ADO.NET-adgangen til
SQLite-filen. Dapper kender selv ingen database — den bygger oven på en
driver som denne.
var cs = new SqliteConnectionStringBuilder {
// connection-streng = "adressen" på databasen:
// navn=værdi;navn=værdi:
DataSource = "db.sqlite",
// "db.sqlite" er databasens FIL — oprettes,
// hvis den ikke findes
// PRAGMA foreign_keys = ON (pr. forbindelse!):
ForeignKeys = true
}.ToString();
// SqliteConnection definerer forbindelsen,
// leveret af driver-pakken:
await using var conn = new SqliteConnection(cs);
// await using sørger for at forbindelsen lukkes automatisk
// (også ved fejl)
// Nu åbnes forbindelsen, og derefter kan der køres SQL:
await conn.OpenAsync();
Connection-strengen fortæller hvor databasen er (her: en fil på disken) og hvordan der logges ind — på en server-database står host, bruger og adgangskode også dér.
public class Drink {
public int DrinkId { get; set; }
public string Name { get; set; } = "";
public double? Price { get; set; } // NULL tilladt
}
var drinks = (await conn.QueryAsync<Drink>(
"SELECT * FROM Drink ORDER BY Name;")).ToList();
QueryAsync giver 0–n rækker — en tom liste, hvis ingen
matcher.
Brug .ToList(), hvis samlingen skal ændres efterfølgende.
var d = await conn.QuerySingleOrDefaultAsync<Drink>(
"SELECT * FROM Drink WHERE DrinkId = @Id;",
new { Id = 5 });
QuerySingleAsync kræver præcis én række og kaster
ellers en exception; QuerySingleOrDefaultAsync giver null
(for reference-typer), hvis ingen række matcher.
int n = await conn.ExecuteAsync(
"UPDATE Drink SET Price = @Price WHERE DrinkId = @Id;",
new { Price = 42.5, Id = 5 });
Returnerer antal berørte rækker — tjek n for at
vide, om noget faktisk blev ændret (n == 0 → ingen match).
long antal = await conn.ExecuteScalarAsync<long>(
"SELECT COUNT(*) FROM Drink;");
Bruges når resultatet er én skalarværdi:
COUNT, MAX, SUM osv.
// RIGTIGT — parametre er sikre mod SQL-injection
new { Id = id, Name = navn }
// FORKERT — aldrig interpolation/concat i SQL!
$"SELECT * FROM Drink WHERE DrinkId = {id}";
@Navn-parametre + anonymt objekt —
aldrig string-interpolation i SQL. Det er din beskyttelse mod SQL-injection.
Bemærk: @ kan kun bruges til værdier, ikke kolonne-/tabelnavne.
const string sql = @"
INSERT INTO Drink (Name, Price) VALUES (@Name, @Price);
SELECT last_insert_rowid();";
long id = await conn.QuerySingleAsync<long>(
sql, new { Name = "Mojito", Price = 55.0 });
await conn.ExecuteAsync(
"INSERT INTO Drink (Name) VALUES (@Name);",
drinks.Select(d => new { d.Name }));
ExecuteAsync kører SQL'en én gang pr. element i listen.
await using var tx = await conn.BeginTransactionAsync();
try {
await conn.ExecuteAsync(sql1, args1, tx);
await conn.ExecuteAsync(sql2, args2, tx);
await tx.CommitAsync(); // gennemfør alt
} catch {
await tx.RollbackAsync(); // fortryd det hele
throw;
}
DrinkId → DrinkId.
init-only properties.string?, int?).SELECT Price AS Pris FROM ….
Query<T>0–n rækker
QuerySingle<T>præcis 1 — ellers kastes exception
QuerySingleOrDefault<T>0 eller 1 (null)
ExecuteINSERT/UPDATE/DELETE → antal rækker
ExecuteScalarén skalarværdi (COUNT, MAX, …)
QueryMultipleflere resultatsæt i ét kald
Alle findes i en Async-udgave (QueryAsync, …) —
bland ikke sync og async i samme flow.
ForeignKeys = true pr. forbindelse.await using på connection og transaktion.Query vs.
QueryAsync).
n == 0 fra ExecuteAsync betyder "intet skete" —
tjek det, før du rapporterer succes.