【导读】仓储模式我们已耳熟能详,但当我们将其进行应用时,真的是那么得心应手吗?确定是解放了生产力吗?这到底是怎样的一个存在,确定不是反模式?

一篇详文我们探讨仓储模式,这里仅我个人的思考,若有更深刻的理解,请在留言中给出

仓储反模式

5年前我在Web APi中使用EntityFramework中写了一个仓储模式,并将其放在我个人github上,此种模式也完全是参考所流行的网传模式,现如今在我看来那是极其错误的仓储模式形式,当时在EntityFramework中有IDbSet接口,然后我们又定义一个IDbContext接口等等,大同小异,接下来我们看看在.NET Core中大多是如何使用的呢?

???? 定义通用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();}
}

我们定义基础通用接口和实现,然后每一个业务都定义一个仓储接口和实现,最后将其进行注入,如下:

  services.AddDbContext<EFCoreDbContext>(options =>{options.UseSqlServer(@"Server=.;Database=EFCore;Trusted_Connection=True;");});services.AddScoped(typeof(IRepository<>), typeof(EntityRepository<>));services.AddScoped<IUserRepository, UserRepository>();services.AddScoped<IUserService, UserService>());

有一部分童鞋在项目中可能就是使用如上方式,每一个具体仓储实现我们将其看成传统的数据访问层,紧接着我们还定义一套业务层即服务层,如此第一眼看来和传统三层架构无任何区别,只是分层名称有所不同而已

每一个具体仓储接口都继承基础仓储接口,然后每个具体仓储实现继承基础仓储实现,对于服务层同理,反观上述一系列操作本质,其实我们回到了原点,那还不如直接通过上下文操作一步到位来的爽快

上述仓储模式并没有带来任何益处,分层明确性从而加大了复杂性和重复性,根本没有解放生产率,我们将专注力全部放在了定义多层接口和实现上而不是业务逻辑,如此使用,这就是仓储模式的反模式实现

仓储模式思考

所有脱离实际项目和业务的思考都是耍流氓,若只是小型项目,直接通过上下文操作未尝不可,既然用到了仓储模式说明是想从一定程度上解决项目中所遇到的痛点所在,要不然只是随波逐流,终将是自我打脸

根据如下官方在微服务所使用仓储链接,官方推崇仓储模式,但在其链接中是直接在具体仓储实现中所使用上下文进行操作,毫无以为这没半点毛病

EntityFramework Core基础设施持久化层

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

但我们想在上下文的基础上进一步将基本增、删、改、查询进行封装,那么我们如何封装基础仓储而避免出现反模式呢?

我思仓储模式

在进行改造之前,我们思考两个潜在需要解决的重点问题

其一,每一个具体业务仓储实现,定义仓储接口是一定必要的吗?我认为完全没必要,有的童鞋就疑惑了,若我们有非封装基础通用接口,需额外定义,那怎么搞,我们可以基于基础仓储接口定义扩展方法

其二,若与其他仓储进行互操作,此时基础仓储不满足需求,那怎么搞,我们可以在基础仓储接口中定义暴露获取上下文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这块扩展实现令人叹服,我也在持续学习中,其他就不多讲了,博客园中讲解原理的文章比比皆是

好了,本文到此结束,倒没什么可总结的,在文中已有概括,我们下次再会

仓储模式到底是不是反模式?相关推荐

  1. 重构是提高可测试性的主要手段 《设计模式》《代码重构》《从重构到模式》 《反模式》 重构时机 编写测试时候 修改BUG时候

    l重构是提高可测试性的主要手段 <设计模式><代码重构><从重构到模式> <反模式> 重构时机 编写测试时候 修改BUG时候

  2. 多线程编程反模式_编程反模式

    多线程编程反模式 您是否曾经进行过代码审查,记录了非常高的WTF / m? 您是否想知道所有这些错误代码的原因是什么? 在大多数情况下,导致原因1的原因是使用设计和编码反模式. 如果您喜欢定义,请参见 ...

  3. 反模式:神仙大类和黄金大锤

    数学中有正数和负数 物理学有『物质』和『反物质』的存在 武侠小说中有九阳神功也有九阴真经 生活中有婚姻也有出轨 ...... 事物总是充满这种相互矛盾而统一的有趣现象. 对于GoF提出的23种设计模式 ...

  4. SOA系列文章(二):服务设计原理:服务模式和反模式

    服务设计系列的法则已经发展到最佳通信实践和取样相关编码的程度.本文提供了设计和实现网络服务的基本原理,并且对面向服务的体系结构(SOA)的相关概念做了一个简要的回顾,以及有关于几种模式和反模式的详细讨 ...

  5. 反模式设计_设计模式:模式或反模式,这就是问题

    反模式设计 我最近遇到了Wiki页面" Anti-pattern" ,其中包含详尽的反模式列表. 其中一些对我来说很明显. 他们中的一些让我想了一下,其他的让我想了更多. 然后,我 ...

  6. 设计模式:模式或反模式,这就是问题

    我最近遇到了Wiki页面" Anti-pattern" ,其中包含详尽的反模式列表. 其中一些对我来说很明显. 他们中的一些让我想了一下,其他的让我想了更多. 然后,我开始在页面上 ...

  7. 如何避免8种常见的敏捷反模式对你的团队造成伤害?

    作者:Søren Pedersen Agile是科技领域最流行的概念之一. 其理论于2001年首次被提出,它包含了几个框架,如eXtreme Programming.Crystal或Lean Soft ...

  8. 有效的ejb 和 反模式的那些事 热土豆 金锤子 等

    这是原文翻译过来的 原文出处: Effective EJB: Make EJBs Work For You Java开发正处于一个十字路口.开放的标准已经为Java平台和语言带来了很多益处,但它们也带 ...

  9. 微服务设计 10 大反模式和陷阱

    数据驱动迁移反模式(Data-Driven Migration) ​ 如上图所示,此种反模式的问题在于微服务的粒度没有最终确定之前就做了数据迁移,如此当不断的调整服务粒度时,那么数据库就免不了频繁迁移 ...

最新文章

  1. 关闭ubuntu启动时System Program Problem Detected提示
  2. groupadd - 建 立 新 群 组
  3. 消除软硬件鸿沟,芯客网完美支持智能硬件在移动互联时代的爆发
  4. php分割文本读入数组,PHP fgets按行读取字符串和explode分割字符串为数组
  5. cin cout加快
  6. QT自定义opengl的Widget绘制Mat
  7. 网络测试工具iperf使用教程
  8. C#中制作启动窗体的方法和问题
  9. 循序渐进ActiveMQ(6)----使用zookeeper实现activemq的主从环境搭建
  10. 三个学生开发的学术钓鱼软件,成功忽悠了整个学术圈
  11. 吴恩达机器学习 EX7 第二部分 主成分分析(PCA)
  12. USACO--Milking Cows (C语言)挤奶牛
  13. jzoj5331 【NOIP2017提高A组模拟8.23】壕游戏
  14. 拿破仑的滑铁卢与罗斯柴尔德的凯旋门
  15. 判断按键输入代码及 vk 键值对应表
  16. 安装IBM Rational Software Architect V9.0
  17. es如何修改es索引字段类型 reindex
  18. 机器学习之掌纹识别(掌纹分类)
  19. C#实现利用熵值法确定权重
  20. [js倒计时]指定对应时间自动倒计时

热门文章

  1. Javascript实现的左右滑动菜单
  2. Red hat6.4重新安装yum
  3. 日记2015.11.5
  4. 经验总结03-dwr
  5. Android 的基本组件之一 Gallery
  6. 鸿蒙系统发布会是什么时候,鸿蒙系统2.0发布时间是什么时候?或将与EMUI11一同发布!...
  7. Unity(创建脚本)
  8. 使用Network Recycle Bin启用映射网络驱动器上的回收站
  9. Scala具体解释---------Scala是什么?可伸展的语言!
  10. ELK 中的elasticsearch 集群的部署