天天看點

重新整理 .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;
}
           

當我們啟動的時候,如果資料庫裡面沒有這個資料庫,那麼就會生成。

重新整理 .net core 實踐篇—————倉儲層的具體實作[二十七]

下一節,簡單介紹一下Mediator,這個是領域設計的驅動。