重新整理 .net core 实践篇—————仓储层的具体实现[二十七]
前言
简单整理一下仓储层。
正文
在共享层的基础建设类库中:
/// <summary>
/// 泛型仓储接口
/// </summary>
/// <typeparam name="TEntity">实体类型</typeparam>
public interface IRepository<TEntity> where TEntity : Entity, IAggregateRoot
{
IUnitOfWork UnitOfWork { get; }
TEntity Add(TEntity entity);
Task<TEntity> AddAsync(TEntity entity, CancellationToken cancellationToken = default);
TEntity Update(TEntity entity);
Task<TEntity> UpdateAsync(TEntity entity, CancellationToken cancellationToken = default);
// 当前接口未指定主键类型,所以这里需要根据实体对象去删除
bool Remove(Entity entity);
Task<bool> RemoveAsync(Entity entity);
}
/// <summary>
/// 泛型仓储接口
/// </summary>
/// <typeparam name="TEntity">实体类型</typeparam>
/// <typeparam name="TKey">主键Id类型</typeparam>
public interface IRepository<TEntity, TKey> : IRepository<TEntity> where TEntity : Entity<TKey>, IAggregateRoot
{
bool Delete(TKey id);
Task<bool> DeleteAsync(TKey id, CancellationToken cancellationToken = default);
TEntity Get(TKey id);
Task<TEntity> GetAsync(TKey id, CancellationToken cancellationToken = default);
}
IRepository 是定义了一个接口,表示要实现增删改查方法。
同样在该类库下,创建了对应的实现。
之所以在相同类库中建立实现的原因,就是因为没有必要分为两个类库。
以前我们写三层的时候分为IDAL 类库和 DAL 类库。IDAl 是接口层,DAL 是具体的实现。他们就称为DataAccessLayer层,也就是数据访问层。
然后用的时候发现一个问题,那就是数据库非常的稳定,哪家公司没事会去换数据库呢?
然后就把DAl类库和IDAL类库合并到DAl类库,然后把接口写在DAl类库,新建一个文件夹,叫做IDAl文件夹,里面放置接口。
如果到时候部分迁移到另外的数据库,又可以把接口移出来,新建类库进行重写这部分。
同样的现在微服务,每个应用都比较小,那么DAl可能就那么几个类,同样类中实现的方法也就那么几个,然后可能就把接口和类写在同一个cs里面。
当然这种是因为是数据库不会换,会有这种演变。如果是扩展性比较强的,比如依赖注入,那么还是要把接口和实现分开。
上面这个只是个人理解,如有错误望请指点。
实现如下:
/// <summary>
/// 泛型仓储抽象基类
/// </summary>
/// <typeparam name="TEntity">实体类型</typeparam>
/// <typeparam name="TDbContext">EFContext实例</typeparam>
public abstract class Repository<TEntity, TDbContext> : IRepository<TEntity> where TEntity : Entity, IAggregateRoot where TDbContext : EFContext
{
protected virtual TDbContext DbContext { get; set; }
public Repository(TDbContext dbContext)
{
DbContext = dbContext;
}
/// <summary>
/// 工作单元
/// 因为 EFContext 实现了 IUnitOfWork,所以这里直接返回 EFContext 的实例即可
/// </summary>
public IUnitOfWork UnitOfWork => DbContext;
public virtual TEntity Add(TEntity entity)
{
return DbContext.Add(entity).Entity;
}
public virtual Task<TEntity> AddAsync(TEntity entity, CancellationToken cancellationToken = default)
{
return Task.FromResult(Add(entity));
}
public virtual TEntity Update(TEntity entity)
{
return DbContext.Update(entity).Entity;
}
public virtual Task<TEntity> UpdateAsync(TEntity entity, CancellationToken cancellationToken = default)
{
return Task.FromResult(Update(entity));
}
public bool Remove(Entity entity)
{
DbContext.Remove(entity);
return true;
}
public Task<bool> RemoveAsync(Entity entity)
{
return Task.FromResult(Remove(entity));
}
}
/// <summary>
/// 泛型仓储抽象基类
/// </summary>
/// <typeparam name="TEntity">实体类型</typeparam>
/// <typeparam name="TKey">主键Id类型</typeparam>
/// <typeparam name="TDbContext">EFContext实例</typeparam>
public abstract class Repository<TEntity, TKey, TDbContext> : Repository<TEntity, TDbContext>, IRepository<TEntity, TKey>
where TEntity : Entity<TKey>, IAggregateRoot
where TDbContext : EFContext
{
public Repository(TDbContext dbContext)
: base(dbContext)
{
}
public virtual bool Delete(TKey id)
{
var entity = DbContext.Find<TEntity>(id);
if (entity == null)
{
return false;
}
DbContext.Remove(entity);
return true;
}
public virtual async Task<bool> DeleteAsync(TKey id, CancellationToken cancellationToken = default)
{
var entity = await DbContext.FindAsync<TEntity>(id, cancellationToken);
if (entity == null)
{
return false;
}
DbContext.Remove(entity);
return true;
}
public virtual TEntity Get(TKey id)
{
return DbContext.Find<TEntity>(id);
}
public virtual async Task<TEntity> GetAsync(TKey id, CancellationToken cancellationToken = default)
{
return await DbContext.FindAsync<TEntity>(id, cancellationToken);
}
}
然后到了基础建设层,也就是具体实现层,我们需要注入模型与数据库的映射关系:
/// <summary>
/// EFContext具体实现
/// </summary>
public class DomainContext : EFContext
{
public DomainContext( DbContextOptions options,IMediator mediator,ICapPublisher capBus)
:base(options,mediator,capBus)
{
}
public DbSet<Order> Orders { get; set; }
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
#region 注册领域模型与数据库的映射关系
modelBuilder.ApplyConfiguration(new OrderEntityTypeConfiguration());
modelBuilder.ApplyConfiguration(new UserEntityTypeConfiguration());
#endregion
base.OnModelCreating(modelBuilder);
}
}
这里我随便找一个模型的应用配置看下,看下order的。
/// <summary>
/// 领域模型 Order 数据库映射配置
/// </summary>
class OrderEntityTypeConfiguration : IEntityTypeConfiguration<Order>
{
public void Configure(EntityTypeBuilder<Order> builder)
{
// 定义主键
builder.HasKey(p => p.Id);
// 指定表名
builder.ToTable("Order");
// 设置字段长度限制
builder.Property(p => p.UserId).HasMaxLength(20);
builder.Property(p => p.UserName).HasMaxLength(30);
// 导航属性
builder.OwnsOne(c => c.Address, a =>
{
a.WithOwner();
a.Property(p => p.City).HasMaxLength(20);
a.Property(p => p.Street).HasMaxLength(50);
a.Property(p => p.ZipCode).HasMaxLength(10);
});
}
}
定义了一些主键、表名、设置字段长度限制、导航属性。
对了,如果你们的数据库很稳定,且多个应用都用到了这些表,那么也可以将这些剥离到一个类库中共享。
因为我们的事务是工作单元模式,那么事务的处理是独立开来的,那么看下在基础建设层,事务的处理如下(这个在后面的使用中会具体介绍):
/// <summary>
/// 数据库上下文事务处理
/// </summary>
/// <typeparam name="TRequest"></typeparam>
/// <typeparam name="TResponse"></typeparam>
public class DomainContextTransactionBehavior<TRequest, TResponse> : TransactionBehavior<DomainContext, TRequest, TResponse>
{
public DomainContextTransactionBehavior(DomainContext dbContext, ICapPublisher capBus, ILogger<DomainContextTransactionBehavior<TRequest, TResponse>> logger)
: base(dbContext, capBus, logger)
{
}
}
具体的仓储实现类:
/// <summary>
/// Order 仓储实现类
/// </summary>
public class OrderRepository : Repository<Order, long, DomainContext>, IOrderRepository
{
public OrderRepository(DomainContext context)
: base(context)
{
}
}
然后我们就需要注册仓储服务和数据库服务:
// 注册 MySql 数据库上下文
services.AddMySqlDomainContext(Configuration.GetValue<string>("MySql"));
// 注册 仓储服务
services.AddRepositories();
显然这两个是扩展服务:
/// <summary>
/// 注册MySql服务
/// </summary>
/// <param name="services"></param>
/// <param name="connectionString"></param>
/// <returns></returns>
public static IServiceCollection AddMySqlDomainContext(this IServiceCollection services, string connectionString)
{
return services.AddDomainContext(builder =>
{
// package: Pomelo.EntityFrameworkCore.MySql
builder.UseMySql(connectionString);
});
}
/// <summary>
/// 注册仓储服务
/// </summary>
/// <param name="services"></param>
/// <returns></returns>
public static IServiceCollection AddRepositories(this IServiceCollection services)
{
services.AddScoped<IOrderRepository, OrderRepository>();
return services;
}
当我们启动的时候,如果数据库里面没有这个数据库,那么就会生成。
结
下一节,简单介绍一下Mediator,这个是领域设计的驱动。