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

不問前世發表於2021-06-24

前言

簡單整理一下倉儲層。

正文

在共享層的基礎建設類庫中:

/// <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,這個是領域設計的驅動。

相關文章