有你在真好 的个人博客
AspNet Core 3.1.0 + 领域驱动设计(DDD) + Dapper + MySql
阅读:2049 添加日期:3/30/2021 5:40:38 PM
AspNet Core 3.1.0 + 领域驱动设计(DDD) + Dapper + MySql


1、前提说明:

1、源码参考

本文大部分代码参照
https://www.cnblogs.com/royzshare/p/9522127.html
以及其源码
https://github.com/zdz72113/NETCore_BasicKnowledge.Examples
结合分层经验。原文使用的是Net Core 2.1

2、分层依赖

参考:
https://www.cnblogs.com/Leo_wl/p/4343242.html
。其中给出多种分层结构,选择适合自己的方式。

2、搭建解决方案


AspNet Core 3.1.0 + 领域驱动设计(DDD) + Dapper + MySql


一、解决方案依赖的包

Install-Package MySql.Data 8.0.19 
Install-Package Dapper 2.0.30 
Install-Package Dapper.Contrib 2.0.30 
Install-Package Microsoft.Extensions.Options 3.1.1

二、领域层


AspNet Core 3.1.0 + 领域驱动设计(DDD) + Dapper + MySql


1、实体接口,领域模型中所有实体对象继承

public interface IEntity { public string Id { get; } } 

2、聚合根接口,聚合实体继承此接口

public interface IAggregateRoot : IEntity { }

3、包括领域事件,领域服务,和值对象等(省略)

4、示例:产品分类实体和产品实体

[Table("Category")]
public class Category:IAggregateRoot
{
    public int Id { get; set; }
    public string Name { get; set; }
}
[Table("Product")]
public class Product:IAggregateRoot
{
    [Key]
    public int Id { get; set; }
    public string Name { get; set; }
    public int Quantity { get; set; }
    public decimal Price { get; set; }
    public int CategoryId { get; set; }
    public virtual Category Category { get; set; }
}

三、仓储层

1、仓储泛型接口,泛型参数接口 约束-聚合根

public interface IRepository<TAggregateRoot> where TAggregateRoot : class, IAggregateRoot
{
    Task<bool> AddAsync(TAggregateRoot entity);
    Task<IEnumerable<TAggregateRoot>> GetAllAsync();
    Task<TAggregateRoot> GetByIDAsync(int id);
    Task<bool> DeleteAsync(int id);
    Task<bool> UpdateAsync(TAggregateRoot entity);
}

2、示例:定义产品分类和产品仓储接口

public interface ICategoryRepository : IRepository<Category> { }
public interface IProductRepository : IRepository<Product> { }

3、上下文IContext

提供对象列表的一切操作接口。在基础层去实现操作。示例:DapperDBContext

public interface IContext : IDisposable
{
    bool IsTransactionStarted { get; }
    void BeginTransaction();
    void Commit();
    void Rollback();
}

4、工作单元IUnitOfWork

统一工作单元,维护受业务事务影响的对象列表,并协调变化的写入和并发问题的解决

public interface IUnitOfWork : IDisposable
{
    void SaveChanges();
}

定义工作单元工厂,创建工厂实例实现工作单元,关联对象操作上下文。

 public interface IUnitOfWorkFactory
{
    IUnitOfWork Create();
}

四、基础层

1、实现IContext实例DapperDBContext和Dapper操作扩展Execute&Query

public abstract class DapperDBContext : IContext
{
    private IDbConnection _connection;
    private IDbTransaction _transaction;
    private int? _commandTimeout = null;
    private readonly DapperDBContextOptions _options;
    public bool IsTransactionStarted { get; private set; }
    protected abstract IDbConnection CreateConnection(string connectionString);
    public DapperDBContext(IOptions<DapperDBContextOptions> optionsAccessor)
    {
        _options = optionsAccessor.Value;
        _connection = CreateConnection(_options.Configuration);
        _connection.Open();
        DebugPrint("Connection started.");
    }
    private void DebugPrint(string message)
    {
#if DEBUG
        Debug.Print(">>> UnitOfWorkWithDapper - Thread {0}: {1}", Thread.CurrentThread.ManagedThreadId, message);
#endif
    }
    public void BeginTransaction()
    {
        if (IsTransactionStarted)
            throw new InvalidOperationException("Transaction is already started.");
        _transaction = _connection.BeginTransaction();
        IsTransactionStarted = true;
        DebugPrint("Transaction started.");
    }
    public void Commit()
    {
        if (!IsTransactionStarted)
            throw new InvalidOperationException("No transaction started.");
        _transaction.Commit();
        _transaction = null;
        IsTransactionStarted = false;
        DebugPrint("Transaction committed.");
    }
    public void Dispose()
    {
        if (IsTransactionStarted)
            Rollback();
        _connection.Close();
        _connection.Dispose();
        _connection = null;
        DebugPrint("Connection closed and disposed.");
    }
    public void Rollback()
    {
        if (!IsTransactionStarted)
            throw new InvalidOperationException("No transaction started.");
        _transaction.Rollback();
        _transaction.Dispose();
        _transaction = null;
        IsTransactionStarted = false;
        DebugPrint("Transaction rollbacked and disposed.");
    }
    #region Dapper Execute & Query
    public async Task<int> ExecuteAsync(string sql, object param = null, CommandType commandType = CommandType.Text)
    {
        return await _connection.ExecuteAsync(sql, param, _transaction, _commandTimeout, commandType);
    }
    public async Task<IEnumerable<T>> QueryAsync<T>(string sql, object param = null, CommandType commandType = CommandType.Text)
    {
        return await _connection.QueryAsync<T>(sql, param, _transaction, _commandTimeout, commandType);
    }
    public async Task<T> QueryFirstOrDefaultAsync<T>(string sql, object param = null, CommandType commandType = CommandType.Text)
    {
        return await _connection.QueryFirstOrDefaultAsync<T>(sql, param, _transaction, _commandTimeout, commandType);
    }
    public async Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TReturn>(string sql, Func<TFirst, TSecond, TReturn> map, object param = null, string splitOn = "Id", CommandType commandType = CommandType.Text)
    {
        return await _connection.QueryAsync(sql, map, param, _transaction, true, splitOn, _commandTimeout, commandType);
    }
    #endregion Dapper Execute & Query
}

IOptions配置实例

public class DapperDBContextOptions : IOptions<DapperDBContextOptions>
{
    public string Configuration { get; set; }
    public DapperDBContextOptions Value 
    {
        get { return this; }
    }
}    

服务扩展类:
DapperDBContextServiceCollectionExtensions

public static class DapperDBContextServiceCollectionExtensions
{
    public static IServiceCollection AddDapperDBContext<T>(this IServiceCollection services, Action<DapperDBContextOptions> setupAction) where T : DapperDBContext
    {
        if (services == null)
        {
            throw new ArgumentNullException(nameof(services));
        }
        if (setupAction == null)
        {
            throw new ArgumentNullException(nameof(setupAction));
        }
        services.AddOptions();
        services.Configure(setupAction);
        services.AddScoped<DapperDBContext, T>();
        services.AddScoped<IUnitOfWorkFactory, DapperUnitOfWorkFactory>();
        return services;
    }
}

上下文配置的实现

public class TestDBContext : DapperDBContext
{
    public TestDBContext(IOptions<DapperDBContextOptions> options) : base(options){}
    protected override IDbConnection CreateConnection(string connectionString)
    {
         IDbConnection conn = new MySqlConnection(connectionString);
         return conn;
    }
}


2、数据持久化操作

仓储实现

public class UowProductRepository : IProductRepository
{
    private readonly DapperDBContext _context;
    public UowProductRepository(DapperDBContext context)
    {
        _context = context;
    }

    public async Task<Product> GetByIDAsync(int id)
    {
        string sql = @"SELECT Id,Name,Quantity,Price,CategoryId FROM Product WHERE Id = @Id";
        return await _context.QueryFirstOrDefaultAsync<Product>(sql, new { Id = id });
    }

    public async Task<bool> AddAsync(Product prod)
    {
        string sql = @"INSERT INTO Product(Name,Quantity,Price,CategoryId) VALUES (@Name ,@Quantity,@Price @CategoryId)";
        return await _context.ExecuteAsync(sql, prod) > 0;
    }

    public async Task<IEnumerable<Product>> GetAllAsync()
    {
        return await _context.QueryAsync<Product>(@"SELECT Id,Name,Quantity,Price,CategoryId FROM Product");
    }

    public async Task<bool> DeleteAsync(int id)
    {
        string sql = @"DELETE FROM Product WHERE Id = @Id";
        return await _context.ExecuteAsync(sql, new { Id = id }) > 0;
    }

    public async Task<bool> UpdateAsync(Product prod)
    {
        string sql = @"UPDATE Product SET Name = @Name,Quantity = @Quantity,Price= @Price ,CategoryId= @CategoryId   WHERE Id = @Id";
        return await _context.ExecuteAsync(sql, prod) > 0;
    }
}

工作单元实现数据提交持久化执行

public class UnitOfWork : IUnitOfWork
{
    private readonly IContext _context;
    public UnitOfWork(IContext context)
    {
        _context = context;
        _context.BeginTransaction();
    }
    public void SaveChanges()
    {
        if (!_context.IsTransactionStarted)
            throw new InvalidOperationException("Transaction have already been commited or disposed.");

        _context.Commit();
    }

    public void Dispose()
    {
        if (_context.IsTransactionStarted)
            _context.Rollback();
    }
}

五、应用层(服务层)

构建常用公共服务

public interface IAppService<TEntity> where TEntity:class,IEntity
{
    Task<bool> AddAsync(TEntity entity);
    Task<IEnumerable<TEntity>> GetAllAsync();
    Task<TEntity> GetByIDAsync(int id);
    Task<bool> DeleteAsync(int id);
    Task<bool> UpdateAsync(TEntity entity);
}

产品服务和品种服务的定义

public interface IProductService : IAppService<Product> { }
public interface ICategoryService : IAppService<Category> { }

实现方法

品种服务实现

public class CategoryService : ICategoryService
{
    private readonly IUnitOfWorkFactory _uowFactory;
    private readonly ICategoryRepository _categoryRepository;
    public CategoryService(IUnitOfWorkFactory uowFactory, ICategoryRepository categoryRepository)
    {
        _uowFactory = uowFactory;
        _categoryRepository = categoryRepository;
    }

    public async Task<bool> AddAsync(Category entity)
    {
        using (var uow = _uowFactory.Create())
        {
            var result = await _categoryRepository.AddAsync(entity);
            uow.SaveChanges();
            return result;
        }
    }

    public async Task<bool> DeleteAsync(int id)
    {
        using (var uow = _uowFactory.Create())
        {
            var result = await _categoryRepository.DeleteAsync(id);
            uow.SaveChanges();
            return result;
        }
    }

    public async Task<IEnumerable<Category>> GetAllAsync()
    {
        return await _categoryRepository.GetAllAsync();
    }

    public async Task<Category> GetByIDAsync(int id)
    {
        return await _categoryRepository.GetByIDAsync(id);
    }

    public async Task<bool> UpdateAsync(Category entity)
    {
        using (var uow = _uowFactory.Create())
        {
            var result = await _categoryRepository.UpdateAsync(entity);
            uow.SaveChanges();
            return result;
        }
    }
}

产品服务实现

public class ProductService : IProductService
{
    private readonly IUnitOfWorkFactory _uowFactory;
    private readonly IProductRepository _productRepository;
    private readonly ICategoryRepository _categoryRepository;
    public ProductService(IUnitOfWorkFactory uowFactory, IProductRepository productRepository, ICategoryRepository categoryRepository)
    {
        _uowFactory = uowFactory;
        _productRepository = productRepository;
        _categoryRepository = categoryRepository;
    }

    public async Task<bool> AddAsync(Product entity)
    {
        using (var uow = _uowFactory.Create())
        {
            var result = await _productRepository.AddAsync(entity);
            uow.SaveChanges();
            return result;
        }
    }

    public async Task<bool> DeleteAsync(int id)
    {
        using (var uow = _uowFactory.Create())
        {
            var result =  await _productRepository.DeleteAsync(id);
            uow.SaveChanges();
            return result;
        }
    }

    public async Task<IEnumerable<Product>> GetAllAsync()
    {
        return await _productRepository.GetAllAsync();
    }

    public async Task<Product> GetByIDAsync(int id)
    {
        return await _productRepository.GetByIDAsync(id);
    }

    public async Task<bool> UpdateAsync(Product entity)
    {
        using (var uow = _uowFactory.Create())
        {
            var result = await _productRepository.UpdateAsync(entity);
            uow.SaveChanges();
            return result;
        }
    }
}

六、WebApi层代表的服务调用层

[Route("api/[controller]")]
    [ApiController]
    public class ProductController : ControllerBase
    {
        private IProductService _productservice;
        public ProductController(IProductService productservice)
        {
            _productservice = productservice;
        }

        [HttpGet("{id}")]
        public async Task<IActionResult> Get(int id)
        {
            var data = await _productservice.GetByIDAsync(id);
            return Ok(data);
        }

        [HttpPost]
        public async Task<IActionResult> Post([FromBody] Product prod)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }
            await _productservice.AddAsync(prod); 
            return NoContent();
        }

        [HttpPut("{id}")]
        public async Task<IActionResult> Put(int id, [FromBody] Product prod)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            var model = await _productservice.GetByIDAsync(id);
            model.Name = prod.Name;
            model.Quantity = prod.Quantity;
            model.Price = prod.Price;
            await _productservice.UpdateAsync(model);

            return NoContent();
        }

        [HttpDelete("{id}")]
        public async Task<IActionResult> Delete(int id)
        {
            await _productservice.DeleteAsync(id);
            return NoContent();
        }
    }

服务的注入:

通过注入的方式实现数据库链接语句的配置

public void ConfigureServices(IServiceCollection services)
{
    ////IUnitofWork是以工厂的形式创建的,不需要服务的注入
    //SQL配置    
    services.AddDapperDBContext<TestDBContext>(options =>
    {
        options.Configuration = @"server=127.0.0.1;database=orange;uid=root;pwd=root;SslMode=none;";
    });

    //Service
    services.AddScoped<IProductService, ProductService>();
    services.AddScoped<ICategoryService, CategoryService>();

    //Repository
    services.AddScoped<IProductRepository, UowProductRepository>();
    services.AddScoped<ICategoryRepository, UowCategoryRepository>();    
    
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
    services.AddControllers();
}

七:SQL语句

CREATE SCHEMA `orange` ;

CREATE TABLE `orange`.`category` (
  `Id` INT NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(45) NOT NULL,
  PRIMARY KEY (`Id`));

CREATE TABLE `orange`.`product` (
  `Id` INT NOT NULL AUTO_INCREMENT,
  `Name` VARCHAR(45) NOT NULL,
  `Price` DECIMAL(19,2) NULL,
  `Quantity` INT NULL,
  `CategoryId` INT NOT NULL,
  PRIMARY KEY (`Id`),
  INDEX `fk_product_category_idx` (`CategoryId` ASC),
  CONSTRAINT `fk_product_category`
    FOREIGN KEY (`CategoryId`)
    REFERENCES `orange`.`category` (`Id`)
    ON DELETE CASCADE
    ON UPDATE NO ACTION);  

INSERT INTO `orange`.`category` (`Name`) VALUES("Phones");
INSERT INTO `orange`.`category` (`Name`) VALUES("Computers");

INSERT INTO `orange`.`product` (`Name`,`Price`,`Quantity`,`CategoryId`) VALUES("iPhone8",4999.99,10,1);
INSERT INTO `orange`.`product` (`Name`,`Price`,`Quantity`,`CategoryId`) VALUES("iPhone7",2999.99,10,1);
INSERT INTO `orange`.`product` (`Name`,`Price`,`Quantity`,`CategoryId`) VALUES("HP750",6000.00,5,2);
INSERT INTO `orange`.`product` (`Name`,`Price`,`Quantity`,`CategoryId`) VALUES("HP5000",12000.00,10,2);

八、调试


AspNet Core 3.1.0 + 领域驱动设计(DDD) + Dapper + MySql


AspNet Core 3.1.0 + 领域驱动设计(DDD) + Dapper + MySql


简单的描述asp.net core 3.0 中关于领域驱动设计DDD相关概念结构的实现过程,同时结合Dapper简单对象映射器。完全撇开EFCore的复杂映射。可能有部分人更愿意使用Dapper.


后续将会集成Swagger + AutoMapper 等实战用用到的内容。

下面有相关参考:

使用ASP.NET Core构建RESTful API的技术指南

https://mp.weixin.qq.com/s/s_3iI_8apahmr-DtJ7i9iQ