See Blackboard
Examples + Exercises:
Dapper makes it easy to work with a relational database from C#!
public class ActorInfo
{
public string Firstname { get; set; } = null!;
public string Lastname { get; set; } = null!;
}
public ActorInfo GetActorInfoById(int actorId)
{
string sql = "SELECT FirstName, LastName FROM Actors WHERE ActorId = @ActorId";
string connectionString = "server=localhost;port=3306;database=Movies;user=root;password=Test@1234!";
using var connection = new MySqlConnection(connectionString);
//Dapper in action
var actor = connection.QuerySingle<ActorInfo>(sql, new {ActorId = actorId});
return actor;
}Only one line! To interact with the database! :-)
Driver
MySqlConnector
dotnet add package Dapper
dotnet add package MySql.Data
var connectionString = "Server=127.0.0.1;Port=3306;Database=Examples;Uid=root;Pwd=Test@1234!;"
// Step 1
using var db = new MySqlConnection(connectionString);
// Step 2
List<Todo> todos = db.Query<Todo>("SELECT * FROM Todo")
.ToList();
/// Step 3
foreach(var todo in Todos)
{
Console.WriteLine(todo.Name);
}Column names and Properties must match!
Primary Key
public static List<Todo> Get()
{
using var connection = new MySqlConnection(GetConnectionString());
return connection.Query<Todo>("SELECT Id, Name, Completed FROM Todo")
.ToList();
} public static List<Todo> Get()
{
using var connection = new MySqlConnection(GetConnectionString());
return connection.Query<Todo>("SELECT Id, Name, Completed FROM Todo")
.ToList();
}
Demo & code completion (choose schema)
The homework assignments are mandatory:
On the exam (tentamen):
3 exercises (one from each exercise file/lesson)
Aftekenen in werkcollege of classrooms
public static Todo? Get(int id)
{
string sql = "SELECT Id, Name, Completed FROM Todo WHERE Id = @Id";
using var connection = new MySqlConnection(GetConnectionString());
return connection.QuerySingleOrDefault<Todo>(sql, new { Id = id });
} public static int NumberOfTodos()
{
using var connection = new MySqlConnection(GetConnectionString());
return connection.ExecuteScalar<int>("SELECT COUNT(*) FROM Todo");
} public static void Delete(int id)
{
using var connection = new MySqlConnection(GetConnectionString());
var sql = "DELETE FROM Todo WHERE Id = @Id";
connection.Execute(sql, new { Id = id });
}
public static int Create(Todo todo)
{
using var connection = new MySqlConnection(GetConnectionString());
var sql = "INSERT INTO Todo (Name, Completed) VALUES (@Name, @Completed); " +
"SELECT LAST_INSERT_ID();";
var id = connection.ExecuteScalar<int>(sql, todo);
return id;
} public static Todo? UpdateAndSelect(Todo todo)
{
var sql = "UPDATE Todo SET Name = @Name, Completed = @Completed WHERE Id = @Id; "
+"SELECT Id, Name, Completed FROM Todo WHERE Id = @Id";
using var connection = new MySqlConnection(GetConnectionString());
// var updatedTodo = TodoDemo.Get(todo.Id);
var updatedTodo = connection.QuerySingle<Todo>(sql);
return updatedTodo;
}| CRUD | Repository | SQL |
|---|---|---|
| Create | Add(...) | INSERT |
| Read | GetById(id), GetAll(), Get(), etc | SELECT |
| Update | Update(...) | UPDATE |
| Delete | Delete/Remove | DELETE |
SELECT CONCAT(FirstName, ' ', LastName) As Fullname
FROM ActorsSee Blackboard.
Try to make a start with the Exercises in Github Classrooms
If you need help: Werkcollege
Signing off on homework: Werkcollege.
Simple explanation:
Longer explanation:
public List<Todo> GetWithSQLInjection(string filter)
{
var todos = new List<Todo>();
try
{
//De code is anders, omdat SQL injectie anders alsnog wordt tegengehouden
using (var connection = Connect())
{
//Doe dit nooit zelf een querystring in elkaar zetten!
//!!!SQL INJECTIE!!! Alle gegevens kunnen gestolen worden, etc :-(
var sql = @"SELECT TodoId, Description, Done
FROM Todo WHERE Description LIKE '%" + filter + "%'";
//todos = connection.Query<Todo>(sql).ToList();
var reader = connection.ExecuteReader(sql);
while (reader.Read())
{
todos.Add(new Todo()
{
TodoId = reader.GetInt32(0),
Description = reader.GetString(1),
Done = reader.GetBoolean(2)
});
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return todos;
}Always, Use Prepared Statement (Dapper will do this for us!) in combination with Parameter Placeholders!
Use (Query) Parameter Placeholders
for example: @Description, @Done
Use a prepare statements
Creates an SQL statement that can be executed on the server (Dapper will do this for us)
CREATE VIEW BrouwerMetAantalBieren AS
(
SELECT br.brouwcode, br.naam, COUNT(b.naam) AS aantal
FROM bier b
JOIN brouwer br on b.brouwcode = br.brouwcode
GROUP BY br.brouwcode, br.naam
ORDER BY aantal DESC
);
SELECT brouwcode, naam, aantal
FROM
BrouwerMetAantalBieren
WHERE
aantal > 10;
public class BrouwerMetAantalBieren
{
public int Brouwcode { get; set; }
public string Naam { get; set; } = null!;
public int Aantal { get; set; }
}
public static List<BrouwerMetAantalBieren> GetBrouwersMetAantalBieren()
{
var sql = """
SELECT brouwcode, naam, aantal
FROM BrouwerMetAantalBieren
WHERE aantal > 10
""";
using var connection = new MySqlConnection(GetConnectionString());
return connection.Query<BrouwerMetAantalBieren>(sql).ToList();
}CREATE VIEW BrouwerMetAantalBieren AS
(
SELECT br.brouwcode, br.naam, COUNT(b.naam) AS aantal
FROM bier b
JOIN brouwer br on b.brouwcode = br.brouwcode
GROUP BY br.brouwcode, br.naam
ORDER BY aantal DESC
); public List<FilmListSlower> GetFilmListSlowerWithCategoryParameterAndRating
(string category, string rating)
{
using var connection = new MySqlConnection(GetConnectionString());
var sql = """
SELECT fid as FilmId, title as Title, description as Description, category as Category, price as Price,
length as Length, rating as Rating, actors as Actors
FROM nicer_but_slower_film_list
WHERE category = @Category
AND rating = @Rating
""";
var films = connection.Query<FilmListSlower>(sql, param:
new {Category = category, Rating = rating});
return films.ToList();
}Not optional
public List<FilmListSlower> GetFilmListSlowerWithCategoryParameterAndRatingOptionalParameter
(string category = null, string rating = null)
{
using var connection = new MySqlConnection(GetConnectionString());
var sql = """
SELECT fid as FilmId, title as Title, description as Description, category as Category, price as Price,
length as Length, rating as Rating, actors as Actors
FROM nicer_but_slower_film_list
WHERE
(@Category IS NULL OR category = @Category) -- trick with IS NULL and OR to make the parameter optional! Make sure to use parentheses!
AND
(@Rating IS NULL OR rating = @Rating)
""";
var films = connection.Query<FilmListSlower>(sql, param: new {Category = category, Rating = rating});
return films.ToList();
}SELECT address_id AS AddressId, address AS Address1, address2 AS Address2, district AS District,
city_id AS CityId, postal_code AS PostalCode, phone AS Phone, last_update AS LastUpdate
FROM addressSELECT a.*
FROM addressDapper doesn't know how to map from a database column to a property in a class
Address.SetTypeMapper();
using var connection = DbHelper.GetConnection();
return connection.Query<Address>("SELECT a.* FROM address a;").ToList();Alternative with attributes: https://gist.github.com/senjacob/8539127
DbHelper.SetTypeMapper<Address>(new Dictionary<string, string>()
{
{ "address_id", "AddressId" },
{ "address", "Address1" },
{ "address2", "Address2" },
{ "district", "District" },
{ "city_id", "CityId" },
{ "postal_code", "PostalCode" },
{ "phone", "Phone" },
{ "last_update", "LastUpdate" },
});The heavy lifting is done in the DbHelper.SetTypeMapper<Type>() method.
FK
Reference to Address
//Examples3_1to1Relationships.cs
public List<Customer> GetCustomersWithAddressNPlusOneProblem()
{
string sql =
$"""
SELECT customer_id AS {nameof(Customer.CustomerId)}, store_id AS {nameof(Customer.StoreId)},
first_name AS {nameof(Customer.FirstName)}, last_name as {nameof(Customer.LastName)},
address_id AS {nameof(Customer.AddressId)},
...
FROM customer
ORDER BY customer_id
LIMIT 3
""";
using var connection = new MySqlConnection(GetConnectionStringForShop());
var customers = connection.Query<Customer>(sql)
.ToList(); //1 Query
foreach (var customer in customers) // N Queries
{
var sqlAddress = """
SELECT address_id AS AddressId, address AS Address1, address2 AS Address2, district AS District,
...
FROM address
WHERE address_id = @AddressId
""";
//Every time we execute this query, we have to go to the database and get the address.
//This is called the N+1 problem, because we have 1 query to get the customers and N queries to get the addresses.
var address = connection.QuerySingle<Address>(sqlAddress, new
{
AddressId = customer.AddressId
});
customer.Address = address;
}
return customers.ToList();
}public class Customer
{
public int CustomerId { get; set; }
public int StoreId { get; set; }
public Store Store { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public int AddressId { get; set; }
public Address Address { get; set; }
public int Active { get; set; }
public DateTime CreateDate { get; set; }
public DateTime LastUpdate { get; set; }
}Result type
//Example3_1to1Relationships.cs
public List<Customer> GetCustomerIncludeAddress()
{
string sql =
"""
SELECT
c.customer_id AS CustomerId, -- rest of customer columns...
'AddressSplit' AS AddressSplit,
a.address_id AS AddressId, a.address AS Address1, -- rest of address columns...
FROM customer c
JOIN address a on a.address_id = c.address_id
ORDER BY c.customer_id
""";
using IDbConnection connection = DbHelper.GetConnection();
List<Customer> customers = connection.Query<Customer, Address, Customer>(
sql,
map: (customer, address) =>
{
customer.Address = address;
return customer;
},
splitOn: "AddressSplit")
.ToList();
return customers;
}Split
Dapper needs to know how to map columns to objects
List<Customer> customers = connection.Query<Customer, Address, Customer>(
sql,
map: (customer, address) =>
{
customer.Address = address;
return customer;
},
splitOn: "AddressSplit").ToList();
List<Customer> customers = connection.Query<Customer, Address, Customer>(
sql,
map: (customer, address) =>
{
customer.Address = address;
return customer;
},
splitOn: "AddressSplit").ToList();
How about 1-n and n-n relationships
That's a bit harder with Dapper
public class Customer
{
public int CustomerId { get; set; }
public int StoreId { get; set; }
public Store Store { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public int AddressId { get; set; }
public Address Address { get; set; }
public int Active { get; set; }
public DateTime CreateDate { get; set; }
public DateTime LastUpdate { get; set; }
}public class Store
{
public int StoreId { get; set; }
public int ManagerStaffId { get; set; }
public int AddressId { get; set; }
public DateTime LastUpdate { get; set; }
public List<Customer> Customers { get; set; }
= new List<Customer>();
}
SELECT
c.customer_id AS CustomerId,
c.store_id AS StoreId,
c.first_name AS FirstName,
-- rest of customer columns
'' AS 'StoreSplit',
s.store_id AS StoreId,
s.manager_staff_id AS ManagerStaffId
-- rest of store columns
FROM customer c JOIN store s ON c.store_id = s.store_id
ORDER BY c.customer_id
LIMIT 10!!Duplicate Stores!!
Key = store id (pk of Store)
Value = Store
Declaration:
Check if a value exists for a key:
Get value for a key:
else
Set value for a key:
Dictionary<int, Store> storeDictionary = new Dictionary<int, Store>(); store = storeDictionary[store.StoreId];if(storeDictionary.ContainsKey(store.StoreId)) {else {
storeDictionary.Add(store.StoreId, store);
} Dictionary<int, Store> storeDictionary = new Dictionary<int, Store>();
using IDbConnection connection = DbHelper.GetConnection();
List<Customer> customers = connection.Query<Customer, Store, Customer>(
sql,
map: (customer, store) =>
{
if(storeDictionary.ContainsKey(store.StoreId))
{
store = storeDictionary[store.StoreId];
}
else
{
storeDictionary.Add(store.StoreId, store);
}
//store maintains a list of customers
store.Customers.Add(customer);
customer.Store = store;
return customer;
},
splitOn: "StoreSplit").ToList();if we don't use a dictionary we get duplicated stores, they look the same but are not the same!!
1 store has many customers
Dictionary<int, Store> storeDictionary = new Dictionary<int, Store>();
using IDbConnection connection = DbHelper.GetConnection();
List<Store> stores = connection.Query<Store, Customer, Store>(
sql,
map: (store, customer) =>
{
if(storeDictionary.ContainsKey(store.StoreId))
{
store = storeDictionary[store.StoreId];
}
else
{
storeDictionary.Add(store.StoreId, store);
}
store.Customers.Add(customer);
return store;
},
splitOn: "CustomerSplit")
.Distinct() // remove duplicates, remember the SQL returns each store, customer combination
.ToList();
return stores;string sql =
"""
SELECT
s.store_id AS StoreId, -- rest of store columns
'' AS 'CustomerSplit',
c.customer_id AS CustomerId,
c.store_id AS StoreId -- rest of customer columns
FROM store s JOIN customer c
ON s.store_id = c.store_id
ORDER BY c.store_id
""";Remove duplicates:
...
},
splitOn: "CustomerSplit")
.Distinct() // remove duplicates, remember the SQL returns each store, customer combination
.ToList();
return stores;public class Film
{
public int FilmId { get; set; }
public string Title { get; set; }
// other properties
public List<Actor> Actors { get; set; }
= new List<Actor>();
}public class Actor
{
public int ActorId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime LastUpdate { get; set; }
public List<Film> Films { get; set; } =
new List<Film>();
}string sql =
"""
SELECT
a.actor_id AS ActorId,
-- rest of actor columns
'' AS 'FilmSplit',
f.film_id AS FilmId,
-- rest of film columns columns
FROM actor a
JOIN film_actor fa ON a.actor_id = fa.actor_id
JOIN film f ON fa.film_id = f.film_id
ORDER BY a.last_name, a.first_name, f.title
""";
using IDbConnection connection = DbHelper.GetConnection();
Dictionary<int, Actor> actorDictionary = new Dictionary<int, Actor>();
List<Actor> actors = connection.Query<Actor, Film, Actor>(
sql,
map: (actor, film) =>
{
if(actorDictionary.ContainsKey(actor.ActorId))
{
actor = actorDictionary[actor.ActorId];
}
else
{
actorDictionary.Add(actor.ActorId, actor);
}
actor.Films.Add(film);
return actor;
},
splitOn: "FilmSplit")
.Distinct() // remove duplicates, remember the SQL return each actor, film combination
.ToList();
return actors; string sql =
"""
SELECT
....
FROM customer c
LEFT JOIN rental r ON c.customer_id = r.customer_id
LEFT JOIN inventory i ON r.inventory_id = i.inventory_id
LEFT JOIN film f ON i.film_id = f.film_id
ORDER BY c.customer_id, f.title
""";
IDbConnection connection = DbHelper.GetConnection();
Dictionary<int, Customer> customerDictionary = new Dictionary<int, Customer>();
List<Customer> customers = connection.Query<Customer, Rental, Inventory, Film, Customer>(
sql,
map: (customer, rental, inventory, film) =>
{
if (customerDictionary.ContainsKey(customer.CustomerId))
{
customer = customerDictionary[customer.CustomerId];
}
else
{
customerDictionary.Add(customer.CustomerId, customer);
}
if (rental.RentalId != 0) // check if rental exists, some customers don't have rentals!!
{
rental.Inventory = inventory;
inventory.Film = film;
customer.Rentals.Add(rental);
}
return customer;
},
splitOn: "CustomerSplit, RentalSplit, InventorySplit") //the split on parameter is used to split the result set,
//not that multiple columns are used (seperated by a comma)
.Distinct() // don't forget to remove duplicates (each customer, rental, inventory, film combination)
.ToList();map: (customer, rental, inventory, film) =>
{
if (customerDictionary.ContainsKey(customer.CustomerId))
{
customer = customerDictionary[customer.CustomerId];
}
else
{
customerDictionary.Add(customer.CustomerId, customer);
}
if (rental.RentalId != 0) // check if rental exists, some customers don't have rentals!!
{
rental.Inventory = inventory;
inventory.Film = film;
customer.Rentals.Add(rental);
}
return customer;
},Some Customers don't have Rentals
Try to avoid it, it's not needed most of the time.
But if you want you need to check for duplicates in the map function. This can be done with Any(...) method.
List<Beer> beers = connection.Query<Beer, Brewer, Beer>(sql, (beer, brewer) =>
{
if (!breweryDictionary.TryGetValue(brewer.BrewerId, out Brewer? brewerEntry))
{
breweryDictionary.Add(brewer.BrewerId, brewer);
brewerEntry = brewer;
}
beer.Brewer = brewerEntry;
if (!brewerEntry.Beers.Any(x => x.BeerId == beer.BeerId))
{
brewerEntry.Beers.Add(beer);
}
return beer;
}, splitOn: "BrewerId")