倉儲模式究竟是不是反模式?

前言

倉儲模式咱們已耳熟能詳,但當咱們將其進行應用時,真的是那麼駕輕就熟嗎?肯定是解放了生產力嗎?這究竟是怎樣的一個存在,肯定不是反模式?,一篇詳文咱們探討倉儲模式,這裏僅我我的的思考,如有更深入的理解,請在評論中給出git

倉儲反模式

5年前我在Web APi中使用EntityFramework中寫了一個倉儲模式,並將其放在我我的github上,此種模式也徹底是參考所流行的網傳模式,現現在在我看來那是極其錯誤的倉儲模式形式,當時在EntityFramework中有IDbSet接口,而後咱們又定義一個IDbContext接口等等,大同小異,接下來咱們看看在.NET Core中大可能是如何使用的呢?github

定義通用IRepository接口

public interface IRepository<TEntity> where TEntity : class
{
    /// <summary>
    /// 經過id得到實體
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    TEntity GetById(object id);
    
    //其餘諸如修改、刪除、查詢接口
}

固然還有泛型類可能須要基礎子基礎類等等,這裏咱們一併忽略架構

定義EntityRepository實現IRepository接口

public abstract class EntityRepository<TEntity> : IRepository<TEntity> where TEntity : class
{
    private readonly DbContext _context;

    public EntityRepository(DbContext context)
    {
        _context = context;
    }

    /// <summary>
    /// 經過id獲取實體
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    public TEntity GetById(object id)
    {
        return _context.Set<TEntity>().Find(id);
    }
}

定義業務倉儲接口IUserRepository接口

public interface IUserRepository : IRepository<User>
{
    /// <summary>
    /// 其餘非通用接口
    /// </summary>
    /// <returns></returns>
    List<User> Other();
}

定義業務倉儲接口具體實現UserRepository

public class UserRepository : EntityRepository<User>, IUserRepository
{
    public List<User> Other()
    {
        throw new NotImplementedException();
    }
}

咱們定義基礎通用接口和實現,而後每個業務都定義一個倉儲接口和實現,最後將其進行注入,以下:app

 services.AddDbContext<EFCoreDbContext>(options =>
  {
      options.UseSqlServer(@"Server=.;Database=EFCore;Trusted_Connection=True;");
  });

  services.AddScoped(typeof(IRepository<>), typeof(EntityRepository<>));
  
  services.AddScoped<IUserRepository, UserRepository>();
  

有一部分童鞋在項目中可能就是使用如上方式,每個具體倉儲實現咱們將其當作傳統的數據訪問層,緊接着咱們還定義一套業務層即服務層,如此第一眼看來和傳統三層架構無任何區別,只是分層名稱有所不一樣而已。每個具體倉儲接口都繼承基礎倉儲接口,而後每一個具體倉儲實現繼承基礎倉儲實現,對於服務層同理,反觀上述一系列操做本質,其實咱們回到了原點,那還不如直接經過上下文操做一步到位來的爽快。上述倉儲模式並無帶來任何益處,分層明確性從而加大了複雜性和重複性,根本沒有解放生產率,咱們將專一力所有放在了定義多層接口和實現上而不是業務邏輯,如此使用,這就是倉儲模式的反模式實現倉儲模式思考ide

倉儲模式思考

全部脫離實際項目和業務的思考都是耍流氓,若只是小型項目,直接經過上下文操做何嘗不可,既然用到了倉儲模式說明是想從必定程度上解決項目中所遇到的痛點所在,要否則只是隨波逐流,終將是自我打臉微服務

 

根據以下官方在微服務所使用倉儲連接,官方推崇倉儲模式,但在其連接中是直接在具體倉儲實現中所使用上下文進行操做,毫無覺得這沒半點毛病學習

EntityFramework Core基礎設施持久化層ui

 

https://docs.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/infrastructure-persistence-layer-implementation-entity-framework-core

但我們想在上下文的基礎上進一步將基本增、刪、改、查詢進行封裝,那麼咱們如何封裝基礎倉儲而避免出現反模式呢?this

我思倉儲模式

在進行改造以前,咱們思考兩個潛在須要解決的重點問題spa

 

其一,每個具體業務倉儲實現,定義倉儲接口是必定必要的嗎?我認爲徹底不必,有的童鞋就疑惑了,若咱們有非封裝基礎通用接口,需額外定義,那怎麼搞,咱們能夠基於基礎倉儲接口定義擴展方法

 

其二,若與其餘倉儲進行互操做,此時基礎倉儲不知足需求,那怎麼搞,咱們能夠在基礎倉儲接口中定義暴露獲取上下文Set屬性

 

其三,若很是複雜的查詢,可經過底層鏈接實現或引入Dapper

 

首先,咱們保持上述封裝基礎倉儲接口前提下添加暴露上下文Set屬性,以下:

  /// <summary>
  /// 基礎通用接口
  /// </summary>
  /// <typeparam name="TEntity"></typeparam>
  public interface IRepository<T> where T : class
  {
      IQueryable<T> Queryable { get; }
      T GetById(object id);
  }

上述咱們將基礎倉儲接口具體實現類,將其定義爲抽象,既然咱們封裝了針對基礎倉儲接口的實現,外部只需調用便可,那麼該類理論上就不該該被繼承,因此接下來咱們將其修飾爲密封類,以下:

public sealed class EntityRepository<T> : IRepository<T> where T : class
{
    private readonly DbContext _context;

    public EntityRepository(DbContext context)
    {
        _context = context;
    }

    public T GetById(object id)
    {
        return _context.Set<T>().Find(id);
    }
}

咱們從容器中獲取上下文並進一步暴露上下文Set屬性

public sealed class EntityRepository<T> : IRepository<T> where T : class
{
    private readonly IServiceProvider _serviceProvider;

    private EFCoreDbContext _context => (EFCoreDbContext)
        _serviceProvider.GetService(typeof(EFCoreDbContext));

    private DbSet<T> Set => _context.Set<T>();

    public IQueryable<T> Queryable => Set;

    public EntityRepository(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public T GetById(object id)
    {
        return Set.Find(id);
    }
}

若爲基礎倉儲接口不知足實現,則使用具體倉儲的擴展方法

public static class UserRepository
{
    public static List<User> Other(this IRepository<User> repository)
    {
        // 自定義其餘實現
    }
}

最後到了服務層,則是咱們的業務層,咱們只須要使用上述基礎倉儲接口或擴展方法便可

public class UserService
{
    private readonly IRepository<User> _repository;
    public UserService(IRepository<User>  repository)
    {
        _repository = repository;
    }
}

最後在注入時,咱們將省去註冊每個具體倉儲實現,以下:

  services.AddDbContext<EFCoreDbContext>(options =>
  {
      options.UseSqlServer(@"Server=.;Database=EFCore;Trusted_Connection=True;");
  });

  services.AddScoped(typeof(IRepository<>), typeof(EntityRepository<>));

  services.AddScoped<UserService>();

以上只是針對第一種反模式的基本改造,對於UnitOfWork同理,其本質不過是管理操做事務,並需咱們手動管理上下文釋放時機就好,這裏就再也不多講

 

咱們還能夠根據項目狀況可進一步實現其對應規則,好比在是否須要在進行指定操做以前實現自定義擴展,好比再抽取一個上下文接口等等,ABP vNext中則是如此,ABP vNext對EF Core擴展是我看過最完美的實現方案,接下來咱們來看看

ABP vNext倉儲模式

其核心在Volo.Abp.EntityFrameworkCore包中,將其單獨剝離出來除了抽象通用封裝外,還有一個則是調用了EF Core底層APi,一旦EF Core版本變更,此包也需同步更新

 

ABP vNext針對EF Core作了擴展,經過查看總體實現,主要經過擴展中特性實現指定屬性更新,EF Core中當模型被跟蹤時,直接提交則更新變化屬性,若未跟蹤,咱們直接Update但想要更新指定屬性,這種方式不可行,在ABP vNext則獲得了良好的解決

 

在其EF Core包中的AbpDbContext上下文中,針對屬性跟蹤更改作了良好的實現,以下:

  protected virtual void ChangeTracker_Tracked(object sender, EntityTrackedEventArgs e)
  {
      FillExtraPropertiesForTrackedEntities(e);
  }

  protected virtual void FillExtraPropertiesForTrackedEntities(EntityTrackedEventArgs e)
  {
      var entityType = e.Entry.Metadata.ClrType;
      if (entityType == null)
      {
          return;
      }

      if (!(e.Entry.Entity is IHasExtraProperties entity))
      {
          return;
      }
      
      .....
  }

除此以外的第二大亮點則是對UnitOfWork(工做單元)的完美方案,將其封裝在Volo.Abp.Uow包中,經過UnitOfWorkManager管理UnitOfWork,其事務提交不簡單是像以下形式

private IDbContextTransaction _transaction; 
public void BeginTransaction()
{ 
    _transaction = Database.BeginTransaction();
}

public void Commit()
{
    try
    {
        SaveChanges();
        _transaction.Commit();
    }
    finally
    {
        _transaction.Dispose();
    }        
}

public void Rollback()
{ 
    _transaction.Rollback();
    _transaction.Dispose();
}

額外的還實現了基於環境流動的事務(AmbientUnitOfWork),反正ABP vNext在EF Core這塊擴展實現使人歎服,我也在持續學習中,其餘就很少講了,博客園中講解原理的文章比比皆是

 

好了,本文到此結束,倒沒什麼可總結的,在文中已有歸納,咱們下次再會

相關文章
相關標籤/搜索