.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
    {
        // 具體實現需要依賴 DbContext
        protected 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());
            #endregion
            base.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,不帶 s
            builder.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 這個目錄下面,為每一個模型定義一個映射類,當領域模型越來越復雜,數據庫的結構越來越復雜的時候,這樣的組織結構會非常的清晰

GitHub源碼鏈接:

https://github.com/witskeeper/geektime

知識共享許可協議

本作品采用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可。

歡迎轉載、使用、重新發布,但務必保留文章署名 鄭子銘 (包含鏈接: http://www.cnblogs.com/MingsonZheng/ ),不得用於商業目的,基於本文修改后的作品務必以相同的許可發布。

如有任何疑問,請與我聯系 (MingsonZheng@outlook.com) 。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM