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