Repository 模式 (Repository Pattern)

Repository 模式的好處

在軟體分層那篇文章中,資料存取層的實作便是 Repository 模式的體現,有效的將資料存取隔離於商業邏輯之外,當要抽換資料來源時,無須改動展示層與商業邏輯層(前提是 Repoistory 約定的介面沒有變動)。

專用型 IRepository 與 泛型 IRepository

我們先定義兩種應用的模式

  • 專用型 Repository:一個 interface 實作 一個 Repository

public interface IBlogRepository
{
    // ....
}

public class BlogRepository : IBlogRepository
{
    // ....
}

public interface IPostRepository
{
    // ....
}

public class PostRepository : IPostRepository
{
    // ....
}
  • 泛型 Repository:一個 interface 實作 一個 Repository,透過不同的 TEntity 來操作不同的資料表。

public interface IGenericRepository<TEntity>
{
    // ....
}

public class GenericRepository : GenericRepository<TEntity>
{
    // ....
}

當 Repository 有不同的介面方法的時候,專用型 Repository 能提供最大的彈性。

當 Repoitory 有固定的 CRUD 的介面方法,泛型 Repository 可以有效的減少重複的程式碼。

專用型 Repository

DBContext

EFCore 使用指定產生出 DBContext 如下

/// <summary>
///
/// </summary>
/// <seealso cref="Microsoft.EntityFrameworkCore.DbContext" />
public partial class BloggingContext : DbContext
{
    /// <summary>
    /// Initializes a new instance of the <see cref="BloggingContext"/> class.
    /// </summary>
    public BloggingContext()
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="BloggingContext"/> class.
    /// </summary>
    /// <param name="options">The options.</param>
    public BloggingContext(DbContextOptions<BloggingContext> options)
        : base(options)
    {
    }

    /// <summary>
    /// blog.
    /// </summary>
    public virtual DbSet<Blog> Blog { get; set; }

    /// <summary>
    /// post.
    /// </summary>
    public virtual DbSet<Post> Post { get; set; }

    /// <summary>
    /// <para>
    /// Override this method to configure the database (and other options) to be used for this context.
    /// This method is called for each instance of the context that is created.
    /// The base implementation does nothing.
    /// </para>
    /// <para>
    /// In situations where an instance of <see cref="T:Microsoft.EntityFrameworkCore.DbContextOptions" /> may or may not have been passed
    /// to the constructor, you can use <see cref="P:Microsoft.EntityFrameworkCore.DbContextOptionsBuilder.IsConfigured" /> to determine if
    /// the options have already been set, and skip some or all of the logic in
    /// <see cref="M:Microsoft.EntityFrameworkCore.DbContext.OnConfiguring(Microsoft.EntityFrameworkCore.DbContextOptionsBuilder)" />.
    /// </para>
    /// </summary>
    /// <param name="optionsBuilder">A builder used to create or modify options for this context. Databases (and other extensions)
    /// typically define extension methods on this object that allow you to configure the context.</param>
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
    }

    /// <summary>
    /// Override this method to further configure the model that was discovered by convention from the entity types
    /// exposed in <see cref="T:Microsoft.EntityFrameworkCore.DbSet`1" /> properties on your derived context. The resulting model may be cached
    /// and re-used for subsequent instances of your derived context.
    /// </summary>
    /// <param name="modelBuilder">The builder being used to construct the model for this context. Databases (and other extensions) typically
    /// define extension methods on this object that allow you to configure aspects of the model that are specific
    /// to a given database.</param>
    /// <remarks>
    /// If a model is explicitly set on the options for this context (via <see cref="M:Microsoft.EntityFrameworkCore.DbContextOptionsBuilder.UseModel(Microsoft.EntityFrameworkCore.Metadata.IModel)" />)
    /// then this method will not be run.
    /// </remarks>
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.HasAnnotation("ProductVersion", "2.2.6-servicing-10079");

        modelBuilder.Entity<Blog>(entity =>
        {
            entity.Property(e => e.Url).IsRequired();
        });

        modelBuilder.Entity<Post>(entity =>
        {
            entity.HasOne(d => d.Blog)
                .WithMany(p => p.Post)
                .HasForeignKey(d => d.BlogId);
        });
    }
}

Respository 介面

/// <summary>
/// Interface BlogRepository
/// </summary>
public interface IBlogRepository
{
    /// <summary>
    /// 新增 Blog
    /// </summary>
    /// <param name="blog">Blog 實體</param>
    Task<int> AddAsync(Blog blog);

    /// <summary>
    /// 取得所有 Blog
    /// </summary>
    /// <param name="blogQuery">查詢條件</param>
    Task<IEnumerable<Blog>> GetAsync(BlogQuery blogQuery);

    /// <summary>
    /// 刪除 Blog
    /// </summary>
    /// <param name="blog">Blog 實體</param>
    Task<int> RemoveAsync(Blog blog);

    /// <summary>
    /// 更新 Blog
    /// </summary>
    /// <param name="blog">Blog 實體</param>
    Task<int> UpdateAsync(Blog blog);
}

Repository 實作

Repository 介面與 Service 使用都不需修改,只需修改 Repository 實作的部分 (有沒有開始感受到分層抽離,依賴建介面的好處)

/// <summary>
/// Class BlogRepository.
/// Implements the <see cref="Sample.Repository.Interface.IBlogRepository" />
/// </summary>
/// <seealso cref="Sample.Repository.Interface.IBlogRepository" />
public class BlogRepository : IBlogRepository
{
    /// <summary>
    /// The database
    /// </summary>
    private readonly BloggingContext _context;

    /// <summary>
    /// Initializes a new instance of the <see cref="BlogRepository"/> class.
    /// </summary>
    /// <param name="context">The context.</param>
    public BlogRepository(BloggingContext context)
    {
        this._context = context;
    }

    /// <summary>
    /// 新增 Blog
    /// </summary>
    /// <param name="blog">實體</param>
    /// <returns></returns>
    public async Task<bool> AddAsync(Blog blog)
    {
        _context.Add(blog);
        var count = await _context.SaveChangesAsync();

        return count > 0;
    }

    /// <summary>
    /// 取得 Blog
    /// </summary>
    /// <param name="condition">查詢條件</param>
    /// <returns></returns>
    public async Task<IEnumerable<Blog>> GetAsync(BlogQuery condition)
    {
        var blogs = await _context.Blog.ToListAsync();
        return blogs;
    }

    /// <summary>
    /// 刪除 Blog
    /// </summary>
    /// <param name="id">blog id</param>
    /// <returns></returns>
    public async Task<bool> RemoveAsync(int id)
    {
        var blog = await _context.Blog.FindAsync(id);
        _context.Blog.Remove(blog);
        var count = await _context.SaveChangesAsync();

        return count > 0;
    }

    /// <summary>
    /// 更新 Blog
    /// </summary>
    /// <param name="blog">實體</param>
    /// <returns></returns>
    public async Task<bool> UpdateAsync(Blog blog)
    {
        _context.Update(blog);
        var count = await _context.SaveChangesAsync();

        return count > 0;
    }
}

Service 的應用

注入 IBlogRepository 就可以使用。

注入設定

/// <summary>
/// This method gets called by the runtime. Use this method to add services to the container.
/// </summary>
/// <param name="services"></param>
public void ConfigureServices(IServiceCollection services)
{
    var connection = this.Configuration.GetConnectionString("DefaultConnection");

    // DI Register
    services.AddScoped<IBlogService, BlogService>();
    services.AddScoped<IBlogRepository, BlogRepository>();

    services.AddDbContext<BloggingContext>(
        options => options.UseSqlServer(connection));
    services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());

    ///...............
}

泛型 Repository 模式

泛型 IRepository

/// <summary>
/// Interface Repository
/// </summary>
public interface IGenericRepository<TEntity> where TEntity : class
{
    /// <summary>
    /// 新增
    /// </summary>
    /// <param name="entity">實體</param>
    void Add(TEntity entity);

    /// <summary>
    /// 取得全部
    /// </summary>
    /// <returns></returns>
    Task<ICollection<TEntity>> GetAllAsync();

    /// <summary>
    /// 取得單筆
    /// </summary>
    /// <param name="predicate">查詢條件</param>
    /// <returns></returns>
    Task<TEntity> GetAsync(Expression<Func<TEntity, bool>> predicate);

    /// <summary>
    /// 刪除
    /// </summary>
    /// <param name="entity">實體</param>
    void Remove(TEntity entity);

    /// <summary>
    /// 更新
    /// </summary>
    /// <param name="entity">實體</param>
    void Update(TEntity entity);
}

泛型 IRepository 實作

/// <summary>
///
/// </summary>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
/// <seealso cref="Sample.Repository.Interface.IGenericRepository{TEntity}" />
public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class
{
    /// <summary>
    /// UnitOfWork 實體
    /// </summary>
    private readonly IUnitOfWork _unitOfWork;

    /// <summary>
    /// Initializes a new instance of the <see cref="GenericRepository{TEntity}"/> class.
    /// </summary>
    /// <param name="unitofwork">The unitofwork.</param>
    public GenericRepository(IUnitOfWork unitofwork)
    {
        this._unitOfWork = unitofwork;
    }

    /// <summary>
    /// 新增
    /// </summary>
    /// <param name="entity">實體</param>
    public void Add(TEntity entity)
    {
        if (entity == null)
        {
            throw new ArgumentNullException("entity");
        }

        _unitOfWork.Context.Set<TEntity>().Add(entity);
    }

    /// <summary>
    /// 取得全部
    /// </summary>
    /// <returns></returns>
    public async Task<ICollection<TEntity>> GetAllAsync()
    {
        return await this._unitOfWork.Context.Set<TEntity>().ToListAsync();
    }

    /// <summary>
    /// 取得單筆
    /// </summary>
    /// <param name="predicate">查詢條件</param>
    /// <returns></returns>
    public async Task<TEntity> GetAsync(Expression<Func<TEntity, bool>> predicate)
    {
        return await this._unitOfWork.Context.Set<TEntity>().FirstOrDefaultAsync(predicate);
    }

    /// <summary>
    /// 刪除
    /// </summary>
    /// <param name="entity">實體</param>
    public void Remove(TEntity entity)
    {
        if (entity == null)
        {
            throw new ArgumentNullException("entity");
        }

        this._unitOfWork.Context.Entry(entity).State = EntityState.Deleted;
    }

    /// <summary>
    /// 更新
    /// </summary>
    /// <param name="entity">實體</param>
    public void Update(TEntity entity)
    {
        if (entity == null)
        {
            throw new ArgumentNullException("entity");
        }

        this._unitOfWork.Context.Entry(entity).State = EntityState.Modified;
    }
}

注入設定

/// <summary>
/// This method gets called by the runtime. Use this method to add services to the container.
/// </summary>
/// <param name="services"></param>
public void ConfigureServices(IServiceCollection services)
{
    var connection = this.Configuration.GetConnectionString("DefaultConnection");

    // DI Register
    services.AddScoped<IBlogService, BlogService>();
    services.AddScoped<IGenericRepository<Blog>, GenericRepository<Blog>>();
    services.AddScoped<DbContext, BloggingContext>();
    services.AddDbContext<BloggingContext>(
         options => options.UseSqlServer(connection));

Repository Pattern & Unit Of Work

職責分離 首先先調整 UnitOfWork 的介面與實作

    /// <summary>
    /// UnitOfWork 介面
    /// </summary>
    /// <seealso cref="System.IDisposable" />
    public interface IUnitOfWork : IDisposable
    {
        /// <summary>
        ///
        /// </summary>
        IGenericRepository<Blog> BlogRepository { get; }

        /// <summary>
        /// DB Context
        /// </summary>
        DbContext Context { get; }

        /// <summary>
        /// Saves the change.
        /// </summary>
        /// <returns></returns>
        Task<int> SaveChangeAsync();
    }
}

    /// <summary>
    /// UnitOfWork
    /// </summary>
    public class UnitOfWork : IUnitOfWork
    {
        /// <summary>
        ///
        /// </summary>
        private bool disposed = false;

        /// <summary>
        /// Initializes a new instance of the <see cref="UnitOfWork"/> class.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="blogRepository">The blog repository.</param>
        public UnitOfWork(
            DbContext context,
            IGenericRepository<Blog> blogRepository)
        {
            this.Context = context;
            this.BlogRepository = blogRepository;
        }

        /// <summary>
        /// </summary>
        public IGenericRepository<Blog> BlogRepository { get; private set; }

        /// <summary>
        /// Context
        /// </summary>
        public DbContext Context { get; private set; }

        /// <summary>
        /// Dispose
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// SaveChange
        /// </summary>
        /// <returns></returns>
        public async Task<int> SaveChangeAsync()
        {
            return await this.Context.SaveChangesAsync();
        }

        /// <summary>
        /// Dispose
        /// </summary>
        /// <param name="disposing"></param>
        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    this.Context.Dispose();
                    this.Context = null;
                }
            }
            this.disposed = true;
        }
    }

再來修改 Repository 的實作

/// <summary>
 ///
 /// </summary>
 /// <typeparam name="TEntity">The type of the entity.</typeparam>
 /// <seealso cref="Sample.Repository.Implement.GenericRepository{TEntity}" />
 public class BlogRepository<TEntity> : GenericRepository<TEntity> where TEntity : class
 {
     /// <summary>
     /// Initializes a new instance of the <see cref="BlogRepository{TEntity}"/> class.
     /// </summary>
     /// <param name="context">db context.</param>
     public BlogRepository(DbContext context) : base(context)
     {
     }
 }

再來是 Service 的修改

/// <summary>
/// BlogService
/// </summary>
public class BlogService : IBlogService
{
    private IMapper _mapper;
    private IUnitOfWork _unitofwork;

    /// <summary>
    /// Initializes a new instance of the <see cref="BlogService"/> class.
    /// </summary>
    public BlogService(
        IUnitOfWork unitofwork,
        IMapper mapper)
    {
        this._unitofwork = unitofwork;
        this._mapper = mapper;
    }

    /// <summary>
    /// 新增 Blog
    /// </summary>
    /// <param name="blogDto">Blog Dto</param>
    /// <returns></returns>
    public async Task<int> AddAsync(BlogDto blogDto)
    {
        // Convert BlogDto to Blog
        var blog = this._mapper.Map<Blog>(blogDto);

        this._unitofwork.BlogRepository.Add(blog);
        return await this._unitofwork.SaveChangeAsync();
    }

    /// <summary>
    /// 取得 Blog
    /// </summary>
    /// <param name="blogQueryDto">查詢條件</param>
    /// <returns></returns>
    public async Task<IEnumerable<BlogDto>> GetAsync(BlogQueryDto blogQueryDto)
    {
        var blogs = await this._unitofwork.BlogRepository.GetAllAsync();

        // Convert Blog to BlogDto
        var blogDtos = this._mapper.Map<IEnumerable<BlogDto>>(blogs);

        return blogDtos;
    }

    /// <summary>
    /// 刪除 Blog
    /// </summary>
    /// <param name="blogId">Blog Id</param>
    /// <returns></returns>
    public async Task<int> RemoveAsync(int blogId)
    {
        var blog = await this._unitofwork.BlogRepository.GetAsync(x => x.BlogId == blogId);

        this._unitofwork.BlogRepository.Remove(blog);
        return await this._unitofwork.SaveChangeAsync();
    }

    /// <summary>
    /// 修改 Blog
    /// </summary>
    /// <param name="blogDto">Blog Dto</param>
    /// <returns></returns>
    public async Task<int> UpdateAsync(BlogDto blogDto)
    {
        // Convert BlogDto to Blog
        var blog = this._mapper.Map<Blog>(blogDto);

        this._unitofwork.BlogRepository.Update(blog);
        return await this._unitofwork.SaveChangeAsync();
    }
}

Last updated