© 2020, Developed by Hieu Dev

Làm quen với Dapper trong .NET Core API - Thêm CRUD

Như các bài hướng dẫn CRUD trước đó, hôm ta ta sẽ tìm hiểu một cách mới hơn đối với những bạn chưa từng tiếp cận, đó chính là Dapper, một ORM dựa trên .NET, nhẹ, nhanh và đơn giản để sử dụng.

Làm quen với Dapper trong .NET Core API - Thêm CRUD

Trước hết, chúng ta hãy có một cái nhìn rõ ràng, tổng quan về Dapper và nó sẽ hữu ích như thế nào trong project .NET Core của chúng ta. Tôi nghĩ hầu hết chúng ta đều biết Dapper là gì, nhưng bài viết này dành cho những ai chưa biết về Dapper và đang bắt đầu làm quen với chúng.

Dapper là gì?

Dapper là một Object Mapper đơn giản và không có gì khác ngoài Object-relational mapping (ORM) và chịu trách nhiệm ánh xạ giữa cơ sở dữ liệu và ngôn ngữ lập trình và nó cũng sở hữu danh hiệu King of Micro ORM về tốc độ.

Dapper rất hay là nó hoạt động với bất kỳ cơ sở dữ liệu nào. Vì vậy, nó không chỉ dành cho SQL Server mà có thể sử dụng nó với PostgreSQL, MySQL hoặc những thứ khác.

Trong bài này, mình sẽ hướng dẫn đến các bạn 2 cách để sử dụng Dapper, đó là áp dụng kết hợp repository pattern với dapper để thực hiện CRUD các thực thể, cách thứ hai sẽ nhanh chóng hơn khi sử dụng với Visual Studio Extension, đó là Dapper Crud Generator.  

CRUD với Dapper và Repository pattern

Như được tìm hiểu ở các bài trước, ta biết được Repository pattern là một cách tổ chức source code trong ASP.NET, là lớp trung gian giữa tầng Data Access và Business Logic, đóng vai trò là một lớp kết nối giữa tầng Business và Model của ứng dụng, giúp cho việc truy cập dữ liệu chặt chẽ và bảo mật hơn.

Bây giờ ta sẽ bắt đầu thực hiện một ví dụ nhỏ về Dapper như sau:

1. Tạo các thực thể với Code First

Như đã đề cập trước đó, trong ví dụ sau mình sẽ tạo ra entity là Category. Cụ thể các bước tuần tự như sau:

Bước 1: Tạo mới một project ASP.NET Core Web API từ solution với tên DapperDemo.API. Sau đó cài đặt các NuGet Package sau:
  • Microsoft.EntityFrameworkCore
  • Microsoft.EntityFrameworkCore.SqlServer
  • Microsoft.EntityFrameworkCore.Tools
  • Dapper

Bước 2: Tạo như mục Entities để thực hiện chứa các class entity. Sau đó tạo file Category.cs với code sau:

Entities/Category.cs:

public class Category
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        public Guid? ParentId { get; set; }
        public DateTime CreatedDate { get; set; }
        public bool Status { get; set; }

    }


Bước 3: Ta sẽ tiến hành tạo ra các file để thực hiện validation cho các field ở các thực thể. Cụ thể ta tạo ra thư mục Configurations với các file bên trong sau:

Configurations/CategoryConfiguration.cs:

public class CategoryConfiguration : IEntityTypeConfiguration<Category>
    {
        public void Configure(EntityTypeBuilder<Category> builder)
        {
            builder.ToTable("Categories");
            builder.HasKey(x => x.Id);
            builder.Property(x => x.Name).IsRequired();
            builder.Property(x => x.Status).HasDefaultValue(true);

        }
    }


Bước 4: Bây giờ, ta sẽ thực hiện seed ra các dữ liệu mẫu, các dữ liệu mẫu này sẽ được sinh ra khi chúng ta thực hiện migration. Cụ thể, ta sẽ tạo ra thư mục Extensions, và tạo file ModelBuilderExtensions.cs bên trong với code sau: 

Extensions/ModelBuilderExtensions.cs:

public static class ModelBuilderExtensions
    {
        public static void Seed(this ModelBuilder modelBuilder)
        {
            var shoeCategoryId = new Guid("1b60fd43-a1b5-4214-9ccc-d239f0f4c97b");
            var clothingCategoryId = new Guid("1b60fd43-a1b5-4214-9ccc-d239f0f4c97c");

            modelBuilder.Entity<Category>().HasData(
                new Category()
                {
                    Id = shoeCategoryId,
                    Name = "Shoes",
                    ParentId = null,
                    Status = true,
                },
                 new Category()
                 {
                     Id = clothingCategoryId,
                     Name = "Clothing",
                     ParentId = null,
                     Status = true,
                 });

        }
    }

Bước 5: Tiếp đến ta cần tạo ra Db Context, cụ thể bạn cần tạo thư mục EF và chứa 2 file lần lượt như sau:

EF/DataDbContext.cs:
public class DataDbContext : DbContext
    {
        public DataDbContext(DbContextOptions options) : base(options)
        {
        }
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            //Configure using Fluent API
            modelBuilder.ApplyConfiguration(new CategoryConfiguration());


            //Data seeding
            modelBuilder.Seed();
            //base.OnModelCreating(modelBuilder);
        }

        public DbSet<Category> Categories { get; set; }
    }

EF/DataDbContextFactory.cs:
public class DataDbContextFactory : IDesignTimeDbContextFactory<DataDbContext>
    {
        public DataDbContext CreateDbContext(string[] args)
        {
            IConfigurationRoot configuration = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json")
                .Build();

            var connectionString = configuration.GetConnectionString("ConnectionString");

            var optionsBuilder = new DbContextOptionsBuilder<DataDbContext>();
            optionsBuilder.UseSqlServer(connectionString);

            return new DataDbContext(optionsBuilder.Options);
        }
    }


Bước 6: Bây giờ bạn cần cấu hình chuỗi kết nối tới SQL Server trong appsetting.json:

"ConnectionStrings": {
    "ConnectionString": "Server=HIUPC;Database=DbDapperSolution;Trusted_Connection=True;MultipleActiveResultSets=true"
  },


Và bạn cần thay đổi server name, database name cho phù hợp với cấu hình của bạn. 

Bước 7: Ta vào Program.cs và thêm đoạn code sau bên trong:
builder.Services.AddDbContext<DataDbContext>(options => options.UseSqlServer(
                            builder.Configuration.GetConnectionString("ConnectionString")));


Bước 8: Vào Tools > NuGet Package Manager > NuGet Package Console và thực thi lần lượt 2 command sau: 
  • Add-Migration InitialCreate
  • Update-database 

Bước 9: Sau khi thực hiện bước 8, database của bạn đã được tự động sinh ra, và bước cuối cùng là các bạn vào SQL Server kiểm tra database đã được sinh ra hay chưa.

2. Thêm Repository pattern và Dapper

Trước tiên ta cần tạo ra các interfaces repository, và các interface của các thực thể mình sẽ tổ chức bên trong một thư mục riêng biệt để dễ quản lý. 

Bước 1: Tạo thư mục Infrastructure để chứa các interface repository, và lần lượt tạo 2 files với code sau:

Infrastructure/IGenericRepository.cs:

public interface IGenericRepository<T> where T : class
    {
        Task<T> Get(Guid id);
        Task<IEnumerable<T>> GetAll();
        Task<int> Add(T entity);
        Task<int> Delete(Guid id);
        Task<int> Update(T entity);
    }


Infrastructure/ICategoryRepository.cs:
public interface ICategoryRepository : IGenericRepository<Category> { }


Như code trên, ta định nghĩa ra một Generic repository để dùng chung, định nghĩa ra các phương thức cần cho project. Như vậy, khi bạn thêm mới một interface repository khác vào, chỉ cần kế thừa từ Generic repository và chỉ định cho một entity cụ thể. Đây cũng là một best practice về OOP và pattern đến các bạn.

Bước 2: Có interface rồi, bây giờ chúng ta tạo ra các repository kế thừa interface và implement các phương thức mà chúng ta đã định nghĩa. Cụ thể ta tạo thư mục Bussiness chứa file và code sau:

Bussiness/CategoryRepository.cs:

public class CategoryRepository : ICategoryRepository
    {
        private readonly IConfiguration _configuration;

        public CategoryRepository(IConfiguration configuration)
        {
            _configuration = configuration;
        }
        public async Task<int> Add(Category entity)
        {
            entity.Id = Guid.NewGuid();
            entity.CreatedDate = DateTime.Now;
            var sql = "INSERT INTO Categories (Id, Name, ParentId, Status, CreatedDate) Values (@Id, @Name, @ParentId, @Status, @CreatedDate);";
            using (var connection = new SqlConnection(_configuration.GetConnectionString("ConnectionString")))
            {
                connection.Open();
                var affectedRows = await connection.ExecuteAsync(sql, entity);
                return affectedRows;
            }
        }

        public async Task<int> Delete(Guid id)
        {
            var sql = "DELETE FROM Categories WHERE Id = @Id;";
            using (var connection = new SqlConnection(_configuration.GetConnectionString("ConnectionString")))
            {
                connection.Open();
                var affectedRows = await connection.ExecuteAsync(sql, new { Id = id });
                return affectedRows;
            }
        }

        public async Task<Category> Get(Guid id)
        {
            var sql = "SELECT * FROM Categories WHERE Id = @Id;";
            using (var connection = new SqlConnection(_configuration.GetConnectionString("ConnectionString")))
            {
                connection.Open();
                var result = await connection.QueryAsync<Category>(sql, new { Id = id });
                return result.FirstOrDefault();
            }
        }

        public async Task<IEnumerable<Category>> GetAll()
        {
            var sql = "SELECT * FROM Categories;";
            using (var connection = new SqlConnection(_configuration.GetConnectionString("ConnectionString")))
            {
                connection.Open();
                var result = await connection.QueryAsync<Category>(sql);
                return result;
            }
        }

        public async Task<int> Update(Category entity)
        {
            var sql = "UPDATE Categories SET Name = @Name, ParentId  = @ParentId, Status = @Status WHERE Id = @Id;";
            using (var connection = new SqlConnection(_configuration.GetConnectionString("ConnectionString")))
            {
                connection.Open();
                var affectedRows = await connection.ExecuteAsync(sql, entity);
                return affectedRows;
            }
        }
    }


Như code trên, bạn có thể thấy thay vì sử dụng EF thì ta sử dụng chuỗi SQL query để thực hiện fetch các record từ thể hay thêm, sử, xóa record. 

Bên trong mỗi phương thức, ta sử dụng câu lệnh using ta sử dụng DapperContext để tạo các SQLConnection bằng cách gọi phương thức CreateConnection(), và bên trong ta gọi lại Connection String của chúng ta đã định nghĩa trong appsettings.json. 

Khi chúng ta tạo một connection, chúng ta gọi phương thức QueryAsync và chuyển query làm argument. 

Bước 3: Bây giờ ta thêm controller và gọi lại các repository mà chúng ta đã định nghĩa:

Controller/CategoryController.cs:

[Route("api/categories")]
    [ApiController]
    public class CategoriesController : ControllerBase
    {
        private readonly ICategoryRepository _categoryRepository;    
        public CategoriesController(ICategoryRepository categoryRepository)
        {
            _categoryRepository = categoryRepository;
        }

        [HttpGet("")]
        public async Task<IActionResult> Index()
        {
            return Ok(await _categoryRepository.GetAll());
        }

        [HttpGet("{id}")]
        public async Task<ActionResult> Get(Guid id)
        {

            var item = await _categoryRepository.Get(id);

            if (item == null)
            {
                return NotFound();
            }

            return Ok(item);
        }

        [HttpPost("create")]
        public async Task<IActionResult> Post(Category model)
        {
            var result = await _categoryRepository.Add(model);

            if (result > 0)
            {
                return Ok(model);
            }
            else
            {
                return BadRequest();
            }
        }

        [HttpPut("update/{id}")]
        public async Task<IActionResult> Put(Category model, Guid id)
        {
            var item = await _categoryRepository.Get(id);
            if (item == null)
                return NotFound();

            if (id == model.ParentId)
            {
                return BadRequest();
            }
            model.Id = id;


            var result = await _categoryRepository.Update(model);

            if (result > 0)
            {
                return Ok(model);
            }
            else
            {
                return BadRequest();
            }
        }

        [HttpDelete("delete/{id}")]
        public async Task<IActionResult> Delete(Guid id)
        {
            var item = _categoryRepository.Get(id);

            if (item == null)
                return NotFound();

            var result = await _categoryRepository.Delete(id);

            if (result > 0)
            {
                return Ok();
            }
            else
            {
                return BadRequest();
            }

        }

    }


Bước 4: Bây giờ ta vào Program.cs và thêm DI sau:

builder.Services.AddTransient<ICategoryRepository, CategoryRepository>();


Và đó cũng là bước cuối cùng, khi bạn tạo mới project .NET 6 Web API, thì mặc định nó đã tích hợp sẵn Swagger cho bạn, bây giờ bạn chỉ chạy project lên và trải nghiệm.

Lời kết

Trong bài, chúng ta chỉ làm quen với Dapper, và dùng chúng với SQL query string, nhưng trong các dự án thực tế, ít có ai dùng như vậy, vì hệ thống rất dễ bị tấn công hay hack bằng SQL Injection. Để ngăn ngừa việc này, bạn có thể tiếp tục triển khai các code để phát hiện các ký tự và mã hóa chúng. Các bạn có thể tham khảo các hàm static hữu ích dưới đây:


Cách thứ 2 để ngăn ngừa đó là sử dụng stored procedure, mình sẽ để cập trong bài viết sắp tới. 

Các bạn có thể tham khảo source code trên github theo link dưới đây:


Mong bài viết hữu ích đến các bạn, chúc các bạn thành công,

Hieu Ho.

4 Nhận xét

Mới hơn Cũ hơn