ZetCode

C# Dapper

last modified October 18, 2026

In this article we show how to program databases in C# with Dapper. In the examples we work with PostgreSQL.

Dapper

Dapper is a simple object mapper for the .NET platform. It is a micro-ORM: it adds extension methods to IDbConnection that run SQL and map the results to objects. Unlike a full ORM such as Entity Framework Core, it does not generate SQL, track changes, or manage a schema. We write the SQL, and Dapper takes care of the parameters and the mapping.

Dapper's goals are code simplicity and performance. It was created by the team at Stack Overflow.

Dapper has no database-specific implementation details; it works across all .NET ADO.NET providers, including SQLite, Firebird, Oracle, MySQL, PostgreSQL, and SQL Server.

$ dotnet add package Dapper

To use Dapper, we add the package reference to the project with the dotnet tool.

$ dotnet add package Npgsql

We also add the driver for PostgreSQL. The examples need a server with a testdb database; the easiest way is a Docker container.

$ docker run -d --name pg -e POSTGRES_PASSWORD=passwd -e POSTGRES_DB=testdb \
    -p 5432:5432 postgres:18
cars_postgre.sql
DROP TABLE IF EXISTS orders;
DROP TABLE IF EXISTS cars;
DROP TABLE IF EXISTS customers;

CREATE TABLE cars(id serial PRIMARY KEY, name VARCHAR(255), price INT);
INSERT INTO cars(name, price) VALUES('Audi', 52642);
INSERT INTO cars(name, price) VALUES('Mercedes', 57127);
INSERT INTO cars(name, price) VALUES('Skoda', 9000);
INSERT INTO cars(name, price) VALUES('Volvo', 29000);
INSERT INTO cars(name, price) VALUES('Bentley', 350000);
INSERT INTO cars(name, price) VALUES('Citroen', 21000);
INSERT INTO cars(name, price) VALUES('Hummer', 41400);
INSERT INTO cars(name, price) VALUES('Volkswagen', 21600);

CREATE TABLE customers(id serial PRIMARY KEY, full_name VARCHAR(255));
INSERT INTO customers(full_name) VALUES('John Doe'), ('Jane Doe');

CREATE TABLE orders(id serial PRIMARY KEY,
    customer_id INT REFERENCES customers(id),
    car_id INT REFERENCES cars(id),
    order_date DATE);
INSERT INTO orders(customer_id, car_id, order_date) VALUES
    (1, 1, '2026-01-10'), (1, 3, '2026-02-11'), (2, 2, '2026-03-12');

In our examples, we use these tables. The examples run one after another and change the data, so the outputs below follow that order; run the script again to start from the beginning.

C# Dapper ExecuteScalar

The ExecuteScalar method executes a query that selects a single value.

Program.cs
using Dapper;
using Npgsql;

string cs = "User ID=postgres;Password=passwd;Host=localhost;Database=testdb";

using var con = new NpgsqlConnection(cs);
con.Open();

var ver = con.ExecuteScalar<string>("SELECT version()");
Console.WriteLine(ver);

int count = con.ExecuteScalar<int>("SELECT count(*) FROM cars");
Console.WriteLine(count);

The example retrieves the version of the PostgreSQL database and the number of cars.

string cs = "User ID=postgres;Password=passwd;Host=localhost;Database=testdb";

We provide the connection string for the PostgreSQL database. In a real application, the password comes from configuration or a secret store.

using var con = new NpgsqlConnection(cs);
con.Open();

We create a new NpgsqlConnection and open it. Dapper opens a closed connection by itself for the duration of a call, but an explicit Open is needed for transactions and avoids reopening it.

var ver = con.ExecuteScalar<string>("SELECT version()");

The ExecuteScalar method executes the query, which returns a single value, and converts it to the requested type.

$ dotnet run
PostgreSQL 18.6 (Debian 18.6-1.pgdg13+2) on x86_64-pc-linux-gnu, compiled by gcc (Debian 14.2.0-19) 14.2.0, 64-bit
8

C# Dapper Query

The Query method executes a query and maps the rows to a sequence of objects. With a type argument, the rows are mapped to that type; without one, they are mapped to dynamic objects.

Program.cs
using Dapper;
using Npgsql;

string cs = "User ID=postgres;Password=passwd;Host=localhost;Database=testdb";

using var con = new NpgsqlConnection(cs);
con.Open();

var cars = con.Query<Car>("SELECT * FROM cars").ToList();
cars.ForEach(car => Console.WriteLine(car));

// without a type, Query returns dynamic objects
foreach (var row in con.Query("SELECT name, price FROM cars WHERE price > 100000"))
{
    Console.WriteLine($"{row.name} {row.price}");
}

record Car(int Id, string Name, int Price);

The example retrieves all rows from the cars table, and then a subset as dynamic objects.

var cars = con.Query<Car>("SELECT * FROM cars").ToList();

The Query method executes the statement and returns the objects. Dapper matches the column names to the property names or to the constructor parameters of the record, ignoring the case. By default, the result is buffered into a list, so the connection is free immediately.

foreach (var row in con.Query("SELECT name, price FROM cars WHERE price > 100000"))
{
    Console.WriteLine($"{row.name} {row.price}");
}

For a quick look at the data, dynamic rows are convenient. The columns are properties of the row, but without compile-time checking.

$ dotnet run
Car { Id = 1, Name = Audi, Price = 52642 }
Car { Id = 2, Name = Mercedes, Price = 57127 }
Car { Id = 3, Name = Skoda, Price = 9000 }
Car { Id = 4, Name = Volvo, Price = 29000 }
Car { Id = 5, Name = Bentley, Price = 350000 }
Car { Id = 6, Name = Citroen, Price = 21000 }
Car { Id = 7, Name = Hummer, Price = 41400 }
Car { Id = 8, Name = Volkswagen, Price = 21600 }
Bentley 350000

C# Dapper ExecuteReader

The ExecuteReader method executes the SQL and returns an IDataReader. This is typically used when the results of a query are not processed by Dapper, for example, to fill a DataTable or a DataSet.

Program.cs
using Dapper;
using Npgsql;

string cs = "User ID=postgres;Password=passwd;Host=localhost;Database=testdb";

using var con = new NpgsqlConnection(cs);
con.Open();

using (var reader = con.ExecuteReader("SELECT id, name, price FROM cars ORDER BY id LIMIT 3"))
{
    while (reader.Read())
    {
        int id = reader.GetInt32(0);
        string name = reader.GetString(1);
        int price = reader.GetInt32(2);

        Console.WriteLine($"{id} {name} {price}");
    }
}

// the reader can fill a DataTable
using var reader2 = con.ExecuteReader("SELECT id, name FROM cars");
var table = new System.Data.DataTable();
table.Load(reader2);
Console.WriteLine($"{table.Rows.Count} rows, {table.Columns.Count} columns");

In the example, we retrieve rows from the table with ExecuteReader, and load all rows into a DataTable.

using (var reader = con.ExecuteReader("SELECT id, name, price FROM cars ORDER BY id LIMIT 3"))
{
    while (reader.Read())
    {

Here, no mapping is done by Dapper. We process the columns manually, by their ordinal positions. The reader must be disposed before the next command is run on the same connection; PostgreSQL allows only one active reader per connection, which is why the first reader is in its own block.

$ dotnet run
1 Audi 52642
2 Mercedes 57127
3 Skoda 9000
8 rows, 2 columns

C# Dapper Execute

The Execute method executes an SQL statement. It is used for INSERT, UPDATE, and DELETE statements.

Program.cs
using Dapper;
using Npgsql;

string cs = "User ID=postgres;Password=passwd;Host=localhost;Database=testdb";

using var con = new NpgsqlConnection(cs);
con.Open();

int affectedRows = con.Execute("UPDATE cars SET price = 52000 WHERE id = 1");
Console.WriteLine($"UPDATE affected rows: {affectedRows}");

int none = con.Execute("UPDATE cars SET price = 1 WHERE id = 999");
Console.WriteLine($"UPDATE affected rows: {none}");

The example updates the price of a car, and then tries to update a row that does not exist.

int affectedRows = con.Execute("UPDATE cars SET price = 52000 WHERE id = 1");

The UPDATE statement changes the price of a car. The Execute method returns the number of affected rows. Zero tells us that no row matched.

$ dotnet run
UPDATE affected rows: 1
UPDATE affected rows: 0

C# Dapper parameterized query

Parameterized queries increase security and performance. When we write parameterized queries, we use placeholders instead of directly writing the values into the queries. Never build SQL by concatenating or interpolating values; that is how SQL injection happens. Dapper takes the parameters from the properties of an object, usually an anonymous one.

Program.cs
using Dapper;
using Npgsql;

string cs = "User ID=postgres;Password=passwd;Host=localhost;Database=testdb";

using var con = new NpgsqlConnection(cs);
con.Open();

var car = con.QueryFirst<Car>("SELECT * FROM cars WHERE id = @id", new { id = 3 });
Console.WriteLine(car);

var cars = con.Query<Car>("SELECT * FROM cars WHERE id IN (@id1, @id2)", new { id1 = 1, id2 = 2 });
Console.WriteLine(string.Join("\n", cars));

// QueryFirstOrDefault returns null when nothing is found
var none = con.QueryFirstOrDefault<Car>("SELECT * FROM cars WHERE id = @id", new { id = 999 });
Console.WriteLine(none is null);

// Single throws unless there is exactly one row
try
{
    con.QuerySingle<Car>("SELECT * FROM cars WHERE price > @p", new { p = 1 });
}
catch (InvalidOperationException e)
{
    Console.WriteLine(e.Message);
}

record Car(int Id, string Name, int Price);

The example selects specific rows from the table and shows the different Query... methods.

var car = con.QueryFirst<Car>("SELECT * FROM cars WHERE id = @id", new { id = 3 });

The QueryFirst method returns the first row of the result. The @id is a placeholder, and the anonymous object provides its value.

var none = con.QueryFirstOrDefault<Car>("SELECT * FROM cars WHERE id = @id", new { id = 999 });

The OrDefault variant returns null when no row is found; the plain QueryFirst throws in that case.

con.QuerySingle<Car>("SELECT * FROM cars WHERE price > @p", new { p = 1 });

The Single methods demand exactly one row (or at most one, for SingleOrDefault) and throw an InvalidOperationException otherwise. This is how to express "this query must identify one row".

$ dotnet run
Car { Id = 3, Name = Skoda, Price = 9000 }
Car { Id = 1, Name = Audi, Price = 52000 }
Car { Id = 2, Name = Mercedes, Price = 57127 }
True
Sequence contains more than one element

C# Dapper list parameters

A common need is a condition such as "the id is one of these". For PostgreSQL, the Npgsql driver passes .NET arrays directly, so we use = ANY(@ids). Dapper also expands list parameters for providers that do not have array parameters: there we write IN @ids.

Program.cs
using Dapper;
using Npgsql;

string cs = "User ID=postgres;Password=passwd;Host=localhost;Database=testdb";

using var con = new NpgsqlConnection(cs);
con.Open();

int[] ids = [1, 2, 5];

// Npgsql passes arrays natively, so ANY works; other databases expand "IN @ids"
var cars = con.Query<Car>("SELECT * FROM cars WHERE id = ANY(@ids)", new { ids });
Console.WriteLine(string.Join(", ", cars.Select(c => c.Name)));

string[] names = ["Audi", "Volvo"];
var byName = con.Query<Car>("SELECT * FROM cars WHERE name = ANY(@names) ORDER BY name", new { names });
Console.WriteLine(string.Join(", ", byName.Select(c => c.Id)));

// LIKE patterns belong in the parameter value, not in the SQL
var like = con.Query<Car>("SELECT * FROM cars WHERE name LIKE @pattern", new { pattern = "V%" });
Console.WriteLine(string.Join(", ", like.Select(c => c.Name)));

record Car(int Id, string Name, int Price);

The example selects cars by a list of ids and by a list of names, and uses a LIKE pattern.

var like = con.Query<Car>("SELECT * FROM cars WHERE name LIKE @pattern", new { pattern = "V%" });

The wildcard characters belong in the value of the parameter, not in the SQL text. That way the value is still a parameter and cannot change the structure of the query.

$ dotnet run
Audi, Mercedes, Bentley
1, 4
Volvo, Volkswagen

C# Dapper QueryMultiple

The QueryMultiple method executes a command that returns multiple result sets and lets us read each in turn. It saves round trips to the database.

Program.cs
using Dapper;
using Npgsql;

string cs = "User ID=postgres;Password=passwd;Host=localhost;Database=testdb";

using var con = new NpgsqlConnection(cs);
con.Open();

var sql = @"SELECT * FROM cars WHERE id = @id1;
            SELECT * FROM cars WHERE id = @id2;
            SELECT * FROM customers";

using var multi = con.QueryMultiple(sql, new { id1 = 1, id2 = 2 });

var c1 = multi.Read<Car>().Single();
var c2 = multi.Read<Car>().Single();
var customers = multi.Read<Customer>().ToList();

Console.WriteLine(c1);
Console.WriteLine(c2);
Console.WriteLine(string.Join(", ", customers.Select(c => c.Full_Name)));

record Car(int Id, string Name, int Price);
record Customer(int Id, string Full_Name);

The example sends three SELECT statements in one go.

var sql = @"SELECT * FROM cars WHERE id = @id1;
            SELECT * FROM cars WHERE id = @id2;
            SELECT * FROM customers";

We define a multiple select statement. The statements are separated by a semicolon.

using var multi = con.QueryMultiple(sql, new { id1 = 1, id2 = 2 });

var c1 = multi.Read<Car>().Single();
var customers = multi.Read<Customer>().ToList();

The statements are executed with QueryMultiple, and the result sets are read in the order of the statements. Each Read consumes one result set. The GridReader must be disposed, which the using declaration does.

$ dotnet run
Car { Id = 1, Name = Audi, Price = 52000 }
Car { Id = 2, Name = Mercedes, Price = 57127 }
John Doe, Jane Doe

C# Dapper DynamicParameters

The DynamicParameters class is a bag of parameters that can be passed to the Dapper Query and Execute methods. It gives control over the type, size, and direction of each parameter, and the parameters can be added step by step.

Program.cs
using System.Data;
using Dapper;
using Npgsql;

string cs = "User ID=postgres;Password=passwd;Host=localhost;Database=testdb";

using var con = new NpgsqlConnection(cs);
con.Open();

var query = "INSERT INTO cars(name, price) VALUES(@name, @price) RETURNING id";

var dp = new DynamicParameters();
dp.Add("@name", "Lada", DbType.AnsiString, ParameterDirection.Input, 255);
dp.Add("@price", 36600);

int id = con.ExecuteScalar<int>(query, dp);
Console.WriteLine($"row inserted, id {id}");

// a parameter can be added in steps
var dp2 = new DynamicParameters();
dp2.Add("minPrice", 40000);
dp2.Add("maxPrice", 60000);

var cars = con.Query<string>("SELECT name FROM cars WHERE price BETWEEN @minPrice AND @maxPrice ORDER BY name", dp2);
Console.WriteLine(string.Join(", ", cars));

The example inserts a new row into the cars table and gets its id, and then builds a query with parameters added separately.

var query = "INSERT INTO cars(name, price) VALUES(@name, @price) RETURNING id";

The RETURNING clause is a PostgreSQL feature that makes the INSERT return the generated key. We read it with ExecuteScalar. Other databases have their own ways, such as SELECT SCOPE_IDENTITY() in SQL Server or last_insert_rowid() in SQLite.

dp.Add("@name", "Lada", DbType.AnsiString, ParameterDirection.Input, 255);
dp.Add("@price", 36600);

We provide the parameter values and, for the first one, the type, the direction, and the size. An explicit size lets the database reuse the plan of the statement.

$ dotnet run
row inserted, id 9
Audi, Hummer, Mercedes

C# Dapper bulk insert

We can insert multiple rows with Execute. If the parameter is a list of objects, Dapper runs the command once for every element.

Program.cs
using Dapper;
using Npgsql;

string cs = "User ID=postgres;Password=passwd;Host=localhost;Database=testdb";

using var con = new NpgsqlConnection(cs);
con.Open();

var stm = "INSERT INTO cars(name, price) VALUES(@name, @price)";

var data = new List<Car>
{
    new Car(Name: "Lada 2", Price: 63444),
    new Car(Name: "Toyota 2", Price: 63330)
};

// one command is executed for every element of the list
int n = con.Execute(stm, data);

Console.WriteLine($"{n} cars inserted");
Console.WriteLine(con.ExecuteScalar<int>("SELECT count(*) FROM cars"));

record Car(string Name, int Price);

The program inserts two cars into the table.

int n = con.Execute(stm, data);

The Execute method takes the statement and the list of objects as parameters. The properties of the objects fill the placeholders. The returned value is the total number of affected rows.

$ dotnet run
2 cars inserted
11

This is a loop of commands, not a single bulk operation, so it is not suitable for a very large number of rows. In that case, use the bulk copy facility of the provider; for PostgreSQL, Npgsql has binary import with BeginBinaryImport. Wrapping the inserts in a transaction speeds them up considerably.

C# Dapper transactions

A transaction is created on the connection and passed to the Dapper methods. If any statement fails, we roll everything back.

Program.cs
using Dapper;
using Npgsql;

string cs = "User ID=postgres;Password=passwd;Host=localhost;Database=testdb";

using var con = new NpgsqlConnection(cs);
con.Open();

using var tx = con.BeginTransaction();

try
{
    con.Execute("UPDATE cars SET price = price - 1000 WHERE id = @id", new { id = 1 }, tx);
    con.Execute("UPDATE cars SET price = price + 1000 WHERE id = @id", new { id = 2 }, tx);

    // a statement that fails: the foreign key from orders blocks the delete
    con.Execute("DELETE FROM cars WHERE id = @id", new { id = 3 }, tx);

    tx.Commit();
}
catch (PostgresException e)
{
    tx.Rollback();
    Console.WriteLine($"rolled back: {e.MessageText}");
}

Console.WriteLine(con.QuerySingle<int>("SELECT price FROM cars WHERE id = 1"));

The program moves a price between two cars and then tries to delete a car that is referenced by an order. The delete fails and the whole transaction is rolled back.

con.Execute("UPDATE cars SET price = price - 1000 WHERE id = @id", new { id = 1 }, tx);

The transaction is the third argument of Execute. A command that is not given the transaction fails on a connection that has an active one.

$ dotnet run
rolled back: update or delete on table "cars" violates foreign key constraint "orders_car_id_fkey" on table "orders"
52000

The price of the Audi is still 52000, not 51000.

C# Dapper multi-mapping

A join returns columns of several tables in one row. Multi-mapping splits the row into several objects and lets us connect them.

Program.cs
using Dapper;
using Npgsql;

string cs = "User ID=postgres;Password=passwd;Host=localhost;Database=testdb";

using var con = new NpgsqlConnection(cs);
con.Open();

var sql = @"SELECT o.id, o.order_date, c.id, c.full_name, k.id, k.name, k.price
            FROM orders o
            JOIN customers c ON c.id = o.customer_id
            JOIN cars k ON k.id = o.car_id
            ORDER BY o.id";

// the splitOn columns tell Dapper where the next object starts
var orders = con.Query<Order, Customer, Car, Order>(sql,
    (order, customer, car) =>
    {
        order.Customer = customer;
        order.Car = car;
        return order;
    },
    splitOn: "id,id");

foreach (var o in orders)
{
    Console.WriteLine($"{o.Id}: {o.Customer!.Full_Name} bought {o.Car!.Name} on {o.Order_Date:d}");
}

class Order
{
    public int Id { get; set; }
    public DateTime Order_Date { get; set; }
    public Customer? Customer { get; set; }
    public Car? Car { get; set; }
}

class Customer
{
    public int Id { get; set; }
    public string Full_Name { get; set; } = "";
}

class Car
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
    public int Price { get; set; }
}

The program lists the orders with the customer and the car.

var orders = con.Query<Order, Customer, Car, Order>(sql,
    (order, customer, car) =>
    {
        order.Customer = customer;
        order.Car = car;
        return order;
    },
    splitOn: "id,id");

The type arguments are the types of the objects in the row, in order, and the type of the result. The function receives the objects built from each row and returns the result. splitOn lists the columns at which a new object starts; the default is Id, so here we state the two places explicitly: the second id begins the customer and the third begins the car. The column names must be in the same order as the types.

$ dotnet run
1: John Doe bought Audi on 1/10/2026
2: John Doe bought Skoda on 2/11/2026
3: Jane Doe bought Mercedes on 3/12/2026

When one object contains a list of others (one customer, many orders), the function must also gather the rows with the same parent, typically with a dictionary keyed by the parent id.

C# Dapper column mapping

The database uses full_name, while C# uses FullName. Dapper does not convert the names by itself, but it can be told to ignore underscores, or we can alias the columns.

Program.cs
using Dapper;
using Npgsql;

string cs = "User ID=postgres;Password=passwd;Host=localhost;Database=testdb";

using var con = new NpgsqlConnection(cs);
con.Open();

Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true;

var customers = con.Query<Customer>("SELECT id, full_name FROM customers").ToList();
customers.ForEach(Console.WriteLine);

// or alias the columns
var aliased = con.Query<Person>("SELECT id, full_name AS Name FROM customers").ToList();
aliased.ForEach(Console.WriteLine);

record Customer(int Id, string FullName);
record Person(int Id, string Name);

The program maps the full_name column in two ways.

Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true;

The global setting makes Dapper ignore underscores when matching names, so full_name fills FullName. Set it once at the start of the program. It works for properties and for the constructor parameters of records.

con.Query<Person>("SELECT id, full_name AS Name FROM customers")

An alias in the SQL is the simplest and most explicit way and works in all cases.

$ dotnet run
Customer { Id = 1, FullName = John Doe }
Customer { Id = 2, FullName = Jane Doe }
Person { Id = 1, Name = John Doe }
Person { Id = 2, Name = Jane Doe }

C# Dapper async and streaming

Every method has an asynchronous variant with the Async suffix. For big result sets, we can avoid loading all rows into memory by passing buffered: false, or by enumerating asynchronously.

Program.cs
using Dapper;
using Npgsql;

string cs = "User ID=postgres;Password=passwd;Host=localhost;Database=testdb";

using var con = new NpgsqlConnection(cs);
con.Open();

var cars = await con.QueryAsync<Car>("SELECT * FROM cars ORDER BY id LIMIT 3");
foreach (var car in cars)
{
    Console.WriteLine(car);
}

var price = await con.ExecuteScalarAsync<int>("SELECT max(price) FROM cars");
Console.WriteLine(price);

// buffered: false streams the rows instead of loading all of them into a list
foreach (var car in con.Query<Car>("SELECT * FROM cars ORDER BY id", buffered: false).Take(2))
{
    Console.WriteLine(car.Name);
}

// await foreach over an unbuffered query (Dapper 2.1)
await foreach (var car in con.QueryUnbufferedAsync<Car>("SELECT * FROM cars ORDER BY id LIMIT 2"))
{
    Console.WriteLine(car.Name);
}

record Car(int Id, string Name, int Price);

The program uses the async methods and two ways of streaming rows.

foreach (var car in con.Query<Car>("SELECT * FROM cars ORDER BY id", buffered: false).Take(2))

With buffered: false, the rows are read from the database as the sequence is enumerated. Stopping after two rows leaves the rest unread, but the connection stays busy until the enumeration finishes or is disposed, so no other command can run on it in the meantime.

await foreach (var car in con.QueryUnbufferedAsync<Car>("SELECT * FROM cars ORDER BY id LIMIT 2"))

QueryUnbufferedAsync returns an IAsyncEnumerable, which is consumed with await foreach.

$ dotnet run
Car { Id = 1, Name = Audi, Price = 52000 }
Car { Id = 2, Name = Mercedes, Price = 57127 }
Car { Id = 3, Name = Skoda, Price = 9000 }
350000
Audi
Mercedes
Audi
Mercedes

C# Dapper functions and stored procedures

Databases with stored procedures (SQL Server, MySQL, Oracle) are called with commandType: CommandType.StoredProcedure and the name of the procedure in place of the SQL text. PostgreSQL has functions, which are called with an ordinary SELECT.

Program.cs
using Dapper;
using Npgsql;

string cs = "User ID=postgres;Password=passwd;Host=localhost;Database=testdb";

using var con = new NpgsqlConnection(cs);
con.Open();

con.Execute(@"CREATE OR REPLACE FUNCTION cars_above(min_price int) RETURNS SETOF cars AS $$
    SELECT * FROM cars WHERE price > min_price ORDER BY id
$$ LANGUAGE sql");

// PostgreSQL functions are called with SELECT
var cars = con.Query<Car>("SELECT * FROM cars_above(@min)", new { min = 50000 });
Console.WriteLine(string.Join(", ", cars.Select(c => c.Name)));

// for databases with stored procedures: commandType: CommandType.StoredProcedure

record Car(int Id, string Name, int Price);

The program creates a function and calls it.

$ dotnet run
Audi, Mercedes, Bentley, Lada 2, Toyota 2

C# Dapper delete row

The following example deletes a row from the table.

Program.cs
using Dapper;
using Npgsql;

string cs = "User ID=postgres;Password=passwd;Host=localhost;Database=testdb";

using var con = new NpgsqlConnection(cs);
con.Open();

int delRows = con.Execute("DELETE FROM cars WHERE id = @id", new { id = 8 });

if (delRows > 0)
{
    Console.WriteLine("car deleted");
}

The example deletes the row with the id 8 with the Execute method. The parameter keeps the id out of the SQL text.

$ dotnet run
car deleted

Source

Dapper Github page

In this article we have shown how to program databases in C# with Dapper.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all C# tutorials.