.NET Core开发实战(第29课:定义仓储:使用EF Core实现仓储层)--学习笔记
29 | 定義倉儲:使用EF Core實現倉儲層
首先定義倉儲層的接口,以及倉儲層實現的基類,抽象類
倉儲層的接口
namespace GeekTime.Infrastructure.Core {/// <summary>/// 包含普通實體的倉儲/// 約束 TEntity 必須是繼承 Entity 的基類,必須實現聚合根 IAggregateRoot/// 也就是說倉儲里面存儲的對象必須是一個聚合根對象/// </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>/// 包含指定主鍵的類型的實體的倉儲/// 繼承了上面的接口 IRepository<TEntity>,也就是說擁有了上面定義的所有方法/// 另外一個,它實現了幾個跟 Id 相關的操作的方法/// </summary>/// <typeparam name="TEntity"></typeparam>/// <typeparam name="TKey"></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);} }具體抽象類的實現
namespace GeekTime.Infrastructure.Core {/// <summary>/// 定義普通實體的倉儲/// 定義約束 TDbContext 必須是 EFContext,也就是倉儲必須依賴于 EFContext 及其子類/// 將來就可以把自己定義的比如 DomainContext 作為泛型參數傳入 Repository,就可以很快捷地定義出來自己的倉儲/// </summary>/// <typeparam name="TEntity"></typeparam>/// <typeparam name="TDbContext"></typeparam>public abstract class Repository<TEntity, TDbContext> : IRepository<TEntity> where TEntity : Entity, IAggregateRoot where TDbContext : EFContext{// 具體實現需要依賴 DbContextprotected virtual TDbContext DbContext { get; set; }public Repository(TDbContext context){this.DbContext = context;}public virtual IUnitOfWork UnitOfWork => DbContext;// 因為 DbContext, EFContext 實際上實現了 IUnitOfWork,所以直接返回// 下面這些方法都是 EntityFramework 提供的能力,所以就能通過簡單的幾行代碼來實現基本的倉儲操作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 virtual bool Remove(Entity entity){DbContext.Remove(entity);return true;}public virtual Task<bool> RemoveAsync(Entity entity){return Task.FromResult(Remove(entity));}}/// <summary>/// 定義主鍵的實體的倉儲/// </summary>/// <typeparam name="TEntity"></typeparam>/// <typeparam name="TKey"></typeparam>/// <typeparam name="TDbContext"></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 context) : base(context){}/// <summary>/// 根據 Id 從 DbContext 獲取 Entity,然后再 Remove/// 這樣的好處是可以跟蹤對象的狀態/// 壞處是任意的刪除都需要先去數據庫里面做查詢/// </summary>/// <param name="id"></param>/// <returns></returns>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);}}}實現自己的 DbContext
DomainContext
namespace GeekTime.Infrastructure {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());#endregionbase.OnModelCreating(modelBuilder);}} }映射關系,針對每一個領域模型創建一個 EntityTypeConfiguration
OrderEntityTypeConfiguration
namespace GeekTime.Infrastructure.EntityConfigurations {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(o => o.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);});}} }UserEntityTypeConfiguration
namespace GeekTime.Infrastructure.EntityConfigurations {class UserEntityTypeConfiguration : IEntityTypeConfiguration<User>{public void Configure(EntityTypeBuilder<User> builder){builder.HasKey(p => p.Id);}} }事務處理
要實現對 DomainContext 的事務處理的話,僅僅需要創建一個類 DomainContextTransactionBehavior
namespace GeekTime.Infrastructure {public class DomainContextTransactionBehavior<TRequest, TResponse> : TransactionBehavior<DomainContext, TRequest, TResponse>{public DomainContextTransactionBehavior(DomainContext dbContext, ICapPublisher capBus, ILogger<DomainContextTransactionBehavior<TRequest, TResponse>> logger) : base(dbContext, capBus, logger){}} }為了演示效果,在應用程序啟動時,添加一行代碼
Startup
// 這一行代碼的作用是創建一個 Scope,在這個范圍內創建 DomainContext using (var scope = app.ApplicationServices.CreateScope()) {var dc = scope.ServiceProvider.GetService<DomainContext>();// 確定數據庫已經創建,如果數據庫沒有創建,這個時候會執行數據庫的自動創建過程,根據模型創建數據庫dc.Database.EnsureCreated(); }數據庫的注冊部分
ServiceCollectionExtensions
/// <summary> /// 這個定義就是將連接字符串配置到 dDomainContext /// </summary> /// <param name="services"></param> /// <param name="connectionString"></param> /// <returns></returns> public static IServiceCollection AddMySqlDomainContext(this IServiceCollection services, string connectionString) {return services.AddDomainContext(builder =>{builder.UseMySql(connectionString);}); }這一行代碼的調用位置是在 ConfigureServices 里面
// 從配置中獲取字符串 services.AddMySqlDomainContext(Configuration.GetValue<string>("Mysql"));啟動程序,運行過程中 EF 框架會根據定義的實體映射關系生成數據庫,可在 Mysql 數據庫中查看生成結果
接著豐富一下 Order 的映射關系
namespace GeekTime.Infrastructure.EntityConfigurations {class OrderEntityTypeConfiguration : IEntityTypeConfiguration<Order>{public void Configure(EntityTypeBuilder<Order> builder){// 定義主鍵builder.HasKey(p => p.Id);builder.ToTable("order");// 修改表名為 order,不帶 sbuilder.Property(p => p.UserId).HasMaxLength(20);// 修改字段長度builder.Property(p => p.UserName).HasMaxLength(30);// 定義導航屬性// OwnsOne 的方式可以將 Address 這個值類型作為同一個表的字段來設置builder.OwnsOne(o => o.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);});}} }啟動程序,可以看到數據庫修改結果
這說明可以在倉儲層定義領域模型與數據庫的映射關系,這個映射關系可以組織為一個目錄,為每一個領域模型設置一個類型來定義,并且這個過程是強類型的,這樣的結構,便于后期維護
另外倉儲層的話,定義了一個 IOrderRepository,僅僅實現了 IRepository 泛型接口,引進 Order,由于 Order 實際上有一個主鍵是 long,所以這里把主鍵類型也傳給 IRepository
namespace GeekTime.Infrastructure.Repositories {public interface IOrderRepository : IRepository<Order, long>{} }Order
public class Order : Entity<long>, IAggregateRoot這樣子,Order 的倉儲就定義完畢
那么 Order 倉儲的實現也非常簡單,僅僅需要繼承 Repository,把 Order,long,DomainContext 傳入泛型 Repository 即可,這里還實現了 IOrderRepository
namespace GeekTime.Infrastructure.Repositories {public class OrderRepository : Repository<Order, long, DomainContext>, IOrderRepository{public OrderRepository(DomainContext context) : base(context){}} }通過這樣簡單的繼承,可以復用之前定義的代碼,快速實現倉儲層的定義
可以通過代碼提升看到倉儲層是有 Add,Update,Remove,Delete 方法,還有 UnitOfWork 的屬性
這樣一來就完成了倉儲層的定義,可以看到倉儲層的代碼非常的薄,僅僅包含了一些接口的定義和類的繼承,需要自定義一些方法的時候,可以在倉儲層定義一些特殊方法,比如 AddABC 等特殊的邏輯都可以在這里去實現
namespace GeekTime.Infrastructure.Repositories {public class OrderRepository : Repository<Order, long, DomainContext>, IOrderRepository{public OrderRepository(DomainContext context) : base(context){}}public void AddABC(){} }另外一個在組織領域模型和數據庫的關系的時候,可以很清晰的看到,是在 EntityConfiguration 這個目錄下面,為每一個模型定義一個映射類,當領域模型越來越復雜,數據庫的結構越來越復雜的時候,這樣的組織結構會非常的清晰
總結
以上是生活随笔為你收集整理的.NET Core开发实战(第29课:定义仓储:使用EF Core实现仓储层)--学习笔记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: .NET Core 如何生成信用卡卡号
- 下一篇: Asp.Net Core 中Identi