前言
簡單整理一下倉儲層。
正文
在共享層的基礎建設類庫中:
/// <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,這個是領域設計的驅動。