仓储模式到底是不是反模式?

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

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

仓储反模式

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这块扩展实现令人叹服,我也在持续学习中,其他就不多讲了,博客园中讲解原理的文章比比皆是

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/305796.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网络工程师必须懂的十五大专业术语!

1、什么时候使用多路由协议&#xff1f; 当两种不同的路由协议要交换路由信息时&#xff0c;就要用到多路由协议。当然&#xff0c;路由再分配也可以交换路由信息。下列情况不必使用多路由协议&#xff1a; 从老版本的内部网关协议&#xff08; Interior Gateway Protocol&…

dnSpy反编译、部署调试神器

一、概要在工作当中&#xff0c;当程序部署了之后就算打了日志遇到极个别的特殊异常没有在程序日志中体现出来或者没有详细的报错原因会让开发者非常头疼&#xff0c;不得不盲猜bug到底出在哪里。这里分享一下工作上经常会用到的工具&#xff0c;这款工具可以反编译并运行调试已…

java中内边距跟外边距,padding和margin——内边距和外边距

一、padding——内边距(内填充)1.1、padding 简写属性在一个声明中设置所有填充属性。该属性可以有1到4个值。div.outer{width: 400px;height: 400px;border: 2px solid #000;}div.inner{width: 200px;height: 200px;background-color:red ;padding: 50px;}运行效果图&#xff…

AJAX将成为移动Web2.0时代首选开发平台

一、 引言  最近,Opera宣布通过他们的浏览器把AJAX技术应用于移动设备开发中。考虑到Opera浏览器在目前浏览器市场(特别是在移动浏览器市场)的流行性,我们可以预计这一宣布对于整个浏览器市场必然会产生重要影响。从加入到移动服务开发市场几年的经验来看&#xff0c;我相信现…

matlab仿真习题,(MATlab仿真部分习题答案.doc

(MATlab仿真部分习题答案[4.1]控制系统结构如图4.1所示利用MATLAB对以上单位负反馈控制系统建立传递函数&#xff1b;将第一问中求得的传递函数模型转化为零极点增益形式和状态空间形式。解:(1)num[2 2];den[1 2 1];[num1,den1]cloop(num,den);systf(num1,den1)程序运行结果如下…

使用 ML.NET 实现峰值检测来排查异常

机器学习中一类问题称为峰值检测&#xff0c;它旨在识别与大部分时序中明显不同但临时突发的数据值。及时检测到这些可疑的个体、事件或观察值很重要&#xff0c;这样才能尽量减少其产生。异常情况检测是检测时序数据离群值的过程&#xff0c;在给定的输入时序上指向“怪异”或…

如何使用Tasklist命令

Tasklist命令用来显示运行在本地或远程计算机上的所有进程&#xff0c;带有多个执行参数。使用格式Tasklist [/S system [/U username [/P [password]]]] [/M [module] | /SVC | /V] [/FI filter] [/FO format] [/NH]参数含义/S system 指定连接到的远程系统。/U [domain]user…

PHP防QQ列表右划,react native 实现类似QQ的侧滑列表效果

如果列表行数据需要更多的操作&#xff0c;使用侧滑菜单是移动端比较常见的方式&#xff0c;也符合用户的操作习惯&#xff0c;对app的接受度自然会相对提高点。最近得空就把原来的react-native项目升级了侧滑操作&#xff0c;轻轻松松支持android和ios双平台&#xff0c;效果如…

OpenTelemetry - 云原生下可观测性的新标准

CNCF 简介CNCF&#xff08;Cloud Native Computing Foundation&#xff09;&#xff0c;中文为“云原生计算基金会”&#xff0c;CNCF是Linux基金会旗下的基金会&#xff0c;可以理解为一个非盈利组织。当年谷歌内部一直用于编排容器的Borg项目开源了&#xff0c;为了该项目更好…

毕业设计——第三章 开发方法及系统实现(5)

国内私募机构九鼎控股打造APP&#xff0c;来就送 20元现金领取地址&#xff1a;http://jdb.jiudingcapital.com/phone.html内部邀请码&#xff1a;C8E245J &#xff08;不写邀请码&#xff0c;没有现金送&#xff09;国内私募机构九鼎控股打造&#xff0c;九鼎投资是在全国股份…

说说 RabbiMQ 的应答模式

RabbiMQ 我们都很熟悉了&#xff0c;是很常用的一个开源消息队列。搞懂 RabbiMQ 的应答模式对我们排查错误很有帮助&#xff0c;也能避免一些坑。本文说说 RabbiMQ 的应答模式。生产者发出一条消息给 RabbiMQ &#xff0c;RabbiMQ 将消息推送给消费者&#xff0c;消费者处理完消…

php 输出json utf8,php json_encode utf-8中文问题

以前碰到最多的是json_encode是gbk 编码时出现乱码&#xff0c;今天发现uft8也会出现中文乱码了&#xff0c;下面我们一起看问题如何解决吧。utf-8字符json_encode为变成转成utf16编码&#xff0c;也就是介个样子&#xff1a; 代码如下复制代码$ ./php/bin/php -r echo json_en…

2005-5-29+ 认识httphandler

httphandler是做什么的&#xff1f;用任何一个搜索引擎都可以找到一大堆资料&#xff0c;我就不多说了&#xff0c;把我今天学习的一些认识写在这里&#xff0c;希望可以从另一个侧面让大家认识httphandler。所谓httphandler&#xff0c;其实是.net用来处理页面请求的。大家可以…

php oracle 锁表,ORACLE 用户锁定问题

在开发环境下&#xff0c;由于直接修改了数据库用户的密码&#xff0c;之后一直不能连接&#xff0c;及时执行alter user username account unlock 还是提示用户锁定。刚开始只是怀疑是数据库的问题&#xff0c;就一直在数据库上找问题&#xff0c;其实最终的问题是更改密码后&…

微软2020开源回顾:止不住的挨骂,停不下的贡献

喜欢就关注我们吧&#xff01;2020年&#xff0c;Linus Torvalds 开启“飚骚话”模式&#xff0c;言语不再激烈。看到“大喷子” Linus 都有机会变慈祥&#xff0c;料想微软近年来主动拥抱开源并示好的行为应该能改变他人的看法。然而事实并非如此&#xff0c;虽然微软积极投身…

下午回家啦~

决定回家&#xff0c;今天买火车票也很顺利&#xff0c;晚上七点多就到家了。老爸老妈&#xff0c;还有很多亲人朋友&#xff0c;俺就要见到你们了&#xff0c;激动、开心&#xff01;

用matlab实现机械臂的仿真,基于MATLAB的SCARA机械臂仿真与性能评估

工业机器人以其代替人类单调繁重的体力劳动,便于实现自动化提高生产效率等优点,而被广泛应用于工程机械、汽车零部件、轨道交通、轻工造纸等行业,具有可观的经济效益。到2015年,中国机器人市场将成世界最大规模的市场。与此同时,它又是融合了多个学科的复杂机电一体化产品。近年…

每日一题——LeetCode859

方法一 个人方法&#xff1a; 首先s和goal要是长度不一样或者就只有一个字符这两种情况可以直接排除剩下的情况s和goal的长度都是一样的&#xff0c;s的长度为2也是特殊情况&#xff0c;只有s的第一位等于goal的第二位&#xff0c;s的第二位等于goal的第一位才能满足剩下的我们…

4倍速!ML.NET Model Builder GPU 与 CPU 对比测试

当我们使用 Visual Studio 进行机器学习开发时&#xff0c;一般都会推荐安装 ML.NET Model Builder &#xff0c;这让我们的开发更加可视化&#xff0c;并且按照步骤载入相关的训练集&#xff0c;选择好模型就够了&#xff0c;一切就是如此朴实无华。说到 ML.NET Model Builder…

疯狂的爬网

今天下午13:00~16:00&#xff0c;来自61.152.236.158的访客对博客园进行了疯狂的爬网。访问次数 &#xff1a;45506 频率&#xff1a;13次/秒访问目标地址&#xff1a;主要是个人Blog各个分类的RSS。看来需要对各个分类的RSS进行进一步的性能优化。