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、搭建解决方案
一、解决方案依赖的包
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
二、领域层
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);
八、调试
简单的描述asp.net core 3.0 中关于领域驱动设计DDD相关概念结构的实现过程,同时结合Dapper简单对象映射器。完全撇开EFCore的复杂映射。可能有部分人更愿意使用Dapper.
后续将会集成Swagger + AutoMapper 等实战用用到的内容。