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、示例:产品分类实体和产品实体
[ | ]|
public class Category:IAggregateRoot | |
{ | |
public int Id { get; set; } | |
public string Name { get; set; } | |
} | |
[ | ]|
public class Product:IAggregateRoot | |
{ | |
[ | ]|
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) | |
{ | |
Debug.Print(">>> UnitOfWorkWithDapper - Thread {0}: {1}", Thread.CurrentThread.ManagedThreadId, message); | |
} | |
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."); | |
} | |
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); | |
} | |
} |
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层代表的服务调用层
[ | ]|
[ | ]|
public class ProductController : ControllerBase | |
{ | |
private IProductService _productservice; | |
public ProductController(IProductService productservice) | |
{ | |
_productservice = productservice; | |
} | |
[ | ]|
public async Task<IActionResult> Get(int id) | |
{ | |
var data = await _productservice.GetByIDAsync(id); | |
return Ok(data); | |
} | |
[ | ]|
public async Task<IActionResult> Post([FromBody] Product prod) | |
{ | |
if (!ModelState.IsValid) | |
{ | |
return BadRequest(ModelState); | |
} | |
await _productservice.AddAsync(prod); | |
return NoContent(); | |
} | |
[ | ]|
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(); | |
} | |
[ | ]|
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 等实战用用到的内容。