前言

简单整理一下仓储层。

正文

在共享层的基础建设类库中:

/// <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 实践篇—————仓储层的具体实现[二十七]-LMLPHP

下一节,简单介绍一下Mediator,这个是领域设计的驱动。

06-24 12:38