Trong bài viết này, chúng ta sẽ tìm hiểu cách sử dụng Dapper trong dự án .NET Core Web API. Chúng ta sẽ nói về Dapper một cách tổng thể nhất vầ cách sử dụng các truy vấn và thực thi khác nhau, cách thực thi các thủ tục và cách tạo nhiều truy vấn. Để tránh sử dụng các truy vấn Dapper trực tiếp bên trong controller, chúng ta sẽ tạo một lớp repository đơn giản để bao bọc các logic.

Dapper là gì?
Các Methods mở rộng
- Execute: thực hiện lệnh một hoặc nhiều lần và trả về số hàng tương ứng.
- Query: thực hiện một truy vấn và ánh xạ kết quả.
- QueryFirst: thực hiện một truy vấn và ánh xạ kết quả đầu tiên.
- QueryFirstOrDefault: thực hiện một truy vấn và ánh xạ kết quả đầu tiên hoặc một giá trị mặc định nếu chuỗi không chứa phần tử.
- QuerySingle: thực hiện một truy vấn và ánh xạ kết quả. Nó “throws an exception” nếu không có chính xác một phần tử nào trong chuỗi.
- QuerySingleOrDefault: thực hiện một truy vấn và ánh xạ kết quả hoặc một giá trị mặc định nếu chuỗi trống. Nó trả về một ngoại lệ nếu có nhiều hơn một phần tử trong chuỗi
- QueryMultiple: thực thi nhiều truy vấn trong cùng một lệnh và ánh xạ kết quả.
Như đã để cập trước đó, Dapper cung cấp phiên bản bất đồng bộ (asynchronous) cho tất cả các phương thức này (ExecuteAsync, QueryAsync, QueryFirstAsync, QueryFirstOrDefaultAsync, QuerySingleAsync, QuerySingleOrDefaultAsync, QueryMultipleAsync).
Sử dụng Dapper Queries
public interface ICompanyRepository
{
public Task<IEnumerable<Company>> GetCompanies();
}
Sau đó, hãy triển khai phương thức này trong class CompanyRepository:
public async Task<IEnumerable<Company>> GetCompanies()
{
var query = "SELECT * FROM Companies";
using (var connection = _context.CreateConnection())
{
var companies = await connection.QueryAsync<Company>(query);
return companies.ToList();
}
}
[Route("api/companies")]
[ApiController]
public class CompaniesController : ControllerBase
{
private readonly ICompanyRepository _companyRepo;
public CompaniesController(ICompanyRepository companyRepo)
{
_companyRepo = companyRepo;
}
[HttpGet]
public async Task<IActionResult> GetCompanies()
{
try
{
var companies = await _companyRepo.GetCompanies();
return Ok(companies);
}
catch (Exception ex)
{
//log error
return StatusCode(500, ex.Message);
}
}
}
Sử dụng Parameters với Dapper Queries
public interface ICompanyRepository
{
public Task<IEnumerable<Company>> GetCompanies();
public Task<Company> GetCompany(int id);
}
[HttpGet("{id}"]
public async Task<IActionResult> GetCompany(int id)
{
try
{
var company = await _companyRepo.GetCompany(id);
if (company == null)
return NotFound();
return Ok(company);
}
catch (Exception ex)
{
//log error
return StatusCode(500, ex.Message);
}
}
Thêm thực thể mới (Create) sử dụng Execute(Async) Method
public class CompanyForCreationDto
{
public string Name { get; set; }
public string Address { get; set; }
public string Country { get; set; }
}
Sau khi tạo class ta cập nhật code trong interface repository:
public interface ICompanyRepository
{
public Task<IEnumerable<Company>> GetCompanies();
public Task<Company> GetCompany(int id);
public Task CreateCompany(CompanyForCreationDto company);
}
Tiếp theo, ta triển khai code với phương thức sau trong repository:
public async Task CreateCompany(CompanyForCreationDto company)
{
var query = "INSERT INTO Companies (Name, Address, Country) VALUES (@Name, @Address, @Country)";
var parameters = new DynamicParameters();
parameters.Add("Name", company.Name, DbType.String);
parameters.Add("Address", company.Address, DbType.String);
parameters.Add("Country", company.Country, DbType.String);
using (var connection = _context.CreateConnection())
{
await connection.ExecuteAsync(query, parameters);
}
}
Update và Delete thực thể
Làm việc với cập nhật và xóa hiện khá đơn giản vì chúng cũng tương tự và ta đã có tất cả kiến thức để thực hiện.
Đầu tiên ta tạo class Dto cho update:
public class CompanyForUpdateDto
{
public string Name { get; set; }
public string Address { get; set; }
public string Country { get; set; }
}
Sau đó cập nhật interface:
public interface ICompanyRepository
{
public Task<IEnumerable<Company>> GetCompanies();
public Task<Company> GetCompany(int id);
public Task<Company> CreateCompany(CompanyForCreationDto company);
public Task UpdateCompany(int id, CompanyForUpdateDto company);
public Task DeleteCompany(int id);
}
Và triển khai trong class repository:
public async Task UpdateCompany(int id, CompanyForUpdateDto company)
{
var query = "UPDATE Companies SET Name = @Name, Address = @Address, Country = @Country WHERE Id = @Id";
var parameters = new DynamicParameters();
parameters.Add("Id", id, DbType.Int32);
parameters.Add("Name", company.Name, DbType.String);
parameters.Add("Address", company.Address, DbType.String);
parameters.Add("Country", company.Country, DbType.String);
using (var connection = _context.CreateConnection())
{
await connection.ExecuteAsync(query, parameters);
}
}
public async Task DeleteCompany(int id)
{
var query = "DELETE FROM Companies WHERE Id = @Id";
using (var connection = _context.CreateConnection())
{
await connection.ExecuteAsync(query, new { id });
}
}
Như bạn có thể thấy, không có gì mới với hai phương thức này. Ta có một truy vấn, các tham số và thực thi các câu lệnh với phương thức ExecuteAsync.
Cuối cùng, chúng ta phải thêm hai actions này vào controller:
[HttpPut("{id}")]
public async Task<IActionResult> UpdateCompany(int id, CompanyForUpdateDto company)
{
try
{
var dbCompany = await _companyRepo.GetCompany(id);
if (dbCompany == null)
return NotFound();
await _companyRepo.UpdateCompany(id, company);
return NoContent();
}
catch (Exception ex)
{
//log error
return StatusCode(500, ex.Message);
}
}
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteCompany(int id)
{
try
{
var dbCompany = await _companyRepo.GetCompany(id);
if (dbCompany == null)
return NotFound();
await _companyRepo.DeleteCompany(id);
return NoContent();
}
catch (Exception ex)
{
//log error
return StatusCode(500, ex.Message);
}
}
Thực thi Stored Procedures trong Dapper
USE [DapperASPNetCore]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[ShowCompanyForProvidedEmployeeId] @Id int
AS
SELECT c.Id, c.Name, c.Address, c.Country
FROM Companies c JOIN Employees e ON c.Id = e.CompanyId
Where e.Id = @Id
GO
Thủ tục này trả về Name, Address, và Country từ một company có employee với giá trị Id được cung cấp.
public interface ICompanyRepository
{
public Task<IEnumerable<Company>> GetCompanies();
public Task<Company> GetCompany(int id);
public Task<Company> CreateCompany(CompanyForCreationDto company);
public Task UpdateCompany(int id, CompanyForUpdateDto company);
public Task DeleteCompany(int id);
public Task<Company> GetCompanyByEmployeeId(int id);
}
Tiếp theo, ta cập nhật code trong class repository:
public async Task<Company> GetCompanyByEmployeeId(int id)
{
var procedureName = "ShowCompanyForProvidedEmployeeId";
var parameters = new DynamicParameters();
parameters.Add("Id", id, DbType.Int32, ParameterDirection.Input);
using (var connection = _context.CreateConnection())
{
var company = await connection.QueryFirstOrDefaultAsync<Company>
(procedureName, parameters, commandType: CommandType.StoredProcedure);
return company;
}
}
Ở đây, chúng ta tạo một biến chứa tên procedure và tham số với một tham số duy nhất bên trong. Bởi vì thủ tục được lưu trữ trả về một giá trị, ta sử dụng phương thức QueryFirstOrDefaultAsync để thực thi nó. Hãy chú ý rằng nếu thủ tục của bạn không trả về giá trị, bạn có thể sử dụng phương thức ExecuteAsync này để thực thi.
[HttpGet("ByEmployeeId/{id}")]
public async Task<IActionResult> GetCompanyForEmployee(int id)
{
try
{
var company = await _companyRepo.GetCompanyByEmployeeId(id);
if (company == null)
return NotFound();
return Ok(company);
}
catch (Exception ex)
{
//log error
return StatusCode(500, ex.Message);
}
}
Thực thi nhiều câu lệnh SQL với Single Query
public interface ICompanyRepository
{
...
public Task<Company> GetCompanyEmployeesMultipleResults(int id);
}
Và class repository:
public async Task<Company> GetCompanyEmployeesMultipleResults(int id)
{
var query = "SELECT * FROM Companies WHERE Id = @Id;" +
"SELECT * FROM Employees WHERE CompanyId = @Id";
using (var connection = _context.CreateConnection())
using (var multi = await connection.QueryMultipleAsync(query, new { id }))
{
var company = await multi.ReadSingleOrDefaultAsync<Company>();
if (company != null)
company.Employees = (await multi.ReadAsync<Employee>()).ToList();
return company;
}
}
Như bạn có thể thấy, biến truy vấn chứa hai câu lệnh SELECT. Đầu tiên sẽ trả lại một company duy nhất và công ty thứ hai sẽ trả lại tất cả nhân viên cho công ty đó.
Sau đó, chúng ta tạo một kết nối và sau đó sử dụng kết nối đó để gọi phương thức QueryMultipleAsync. Khi chúng ta nhận được nhiều kết quả bên trong, chúng tôi có thể trích xuất cả hai kết quả (công ty và nhân viên của mỗi công ty đó) bằng cách sử dụng các phương thức ReadSignleOrDefaultAsync và ReadAsync. Phương thức đầu tiên trả về một kết quả duy nhất, trong khi phương thức thứ hai trả về một tập hợp.
Multiple Mapping
public interface ICompanyRepository
{
...
public Task<List<Company>> GetCompaniesEmployeesMultipleMapping();
}
Sau đó là class repository:
public async Task<List<Company>> GetCompaniesEmployeesMultipleMapping()
{
var query = "SELECT * FROM Companies c JOIN Employees e ON c.Id = e.CompanyId";
using (var connection = _context.CreateConnection())
{
var companyDict = new Dictionary<int, Company>();
var companies = await connection.QueryAsync<Company, Employee, Company>(
query, (company, employee) =>
{
if (!companyDict.TryGetValue(company.Id, out var currentCompany))
{
currentCompany = company;
companyDict.Add(currentCompany.Id, currentCompany);
}
currentCompany.Employees.Add(employee);
return currentCompany;
}
);
return companies.Distinct().ToList();
}
}
[HttpGet("MultipleMapping")]
public async Task<IActionResult> GetCompaniesEmployeesMultipleMapping()
{
try
{
var company = await _companyRepo.GetCompaniesEmployeesMultipleMapping();
return Ok(company);
}
catch (Exception ex)
{
//log error
return StatusCode(500, ex.Message);
}
}
Lời kết
- Làm cho việc tham số hóa các truy vấn một cách chính xác trở nên dễ dàng
- Làm cho việc thực thi các truy vấn trở nên dễ dàng (vô hướng, nhiều hàng,..)
- Biến kết quả thành đối tượng dễ dàng
- Rất hiệu quả và nhanh chóng
- Tạo ra các class model cho bạn
- Tạo các truy vấn cho bạn
- Theo dõi các đối tượng và những thay đổi của chúng, để bạn có thể gọi SubmitChanges()
Mong bài viết hữu ích, chúc các bạn thành công.Hieu Ho.