专业公司网站建设服务公司,开发手机软件,蒙文网站建设的意义,新乡微网站建设【导读】仓储模式我们已耳熟能详#xff0c;但当我们将其进行应用时#xff0c;真的是那么得心应手吗#xff1f;确定是解放了生产力吗#xff1f;这到底是怎样的一个存在#xff0c;确定不是反模式#xff1f;一篇详文我们探讨仓储模式#xff0c;这里仅我个人的思考但当我们将其进行应用时真的是那么得心应手吗确定是解放了生产力吗这到底是怎样的一个存在确定不是反模式一篇详文我们探讨仓储模式这里仅我个人的思考若有更深刻的理解请在留言中给出仓储反模式5年前我在Web APi中使用EntityFramework中写了一个仓储模式并将其放在我个人github上此种模式也完全是参考所流行的网传模式现如今在我看来那是极其错误的仓储模式形式当时在EntityFramework中有IDbSet接口然后我们又定义一个IDbContext接口等等大同小异接下来我们看看在.NET Core中大多是如何使用的呢???? 定义通用IRepository接口public interface IRepositoryTEntity where TEntity : class
{/// summary/// 通过id获得实体/// /summary/// param nameid/param/// returns/returnsTEntity GetById(object id);//其他诸如修改、删除、查询接口
}
当然还有泛型类可能需要基础子基础类等等这里我们一并忽略???? 定义EntityRepository实现IRepository接口public abstract class EntityRepositoryTEntity : IRepositoryTEntity where TEntity : class
{private readonly DbContext _context;public EntityRepository(DbContext context){_context context;}/// summary/// 通过id获取实体/// /summary/// param nameid/param/// returns/returnspublic TEntity GetById(object id){return _context.SetTEntity().Find(id);}
}???? 定义业务仓储接口IUserRepository接口public interface IUserRepository : IRepositoryUser
{/// summary/// 其他非通用接口/// /summary/// returns/returnsListUser Other();
}
???? 定义业务仓储接口具体实现UserRepositorypublic class UserRepository : EntityRepositoryUser, IUserRepository
{public ListUser Other(){throw new NotImplementedException();}
}
我们定义基础通用接口和实现然后每一个业务都定义一个仓储接口和实现最后将其进行注入如下 services.AddDbContextEFCoreDbContext(options {options.UseSqlServer(Server.;DatabaseEFCore;Trusted_ConnectionTrue;);});services.AddScoped(typeof(IRepository), typeof(EntityRepository));services.AddScopedIUserRepository, UserRepository();services.AddScopedIUserService, 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 nameTEntity/typeparampublic interface IRepositoryT where T : class{IQueryableT Queryable { get; }T GetById(object id);}上述我们将基础仓储接口具体实现类将其定义为抽象既然我们封装了针对基础仓储接口的实现外部只需调用即可那么该类理论上就不应该被继承所以接下来我们将其修饰为密封类如下public sealed class EntityRepositoryT : IRepositoryT where T : class
{private readonly DbContext _context;public EntityRepository(DbContext context){_context context;}public T GetById(object id){return _context.SetT().Find(id);}
}
我们从容器中获取上下文并进一步暴露上下文Set属性public sealed class EntityRepositoryT : IRepositoryT where T : class
{private readonly IServiceProvider _serviceProvider;private EFCoreDbContext _context (EFCoreDbContext)_serviceProvider.GetService(typeof(EFCoreDbContext));private DbSetT Set _context.SetT();public IQueryableT Queryable Set;public EntityRepository(IServiceProvider serviceProvider){_serviceProvider serviceProvider;}public T GetById(object id){return Set.Find(id);}
}
若为基础仓储接口不满足实现则使用具体仓储的扩展方法public static class UserRepository
{public static ListUser Other(this IRepositoryUser repository){// 自定义其他实现}
}
最后到了服务层则是我们的业务层我们只需要使用上述基础仓储接口或扩展方法即可public class UserService
{private readonly IRepositoryUser _repository;public UserService(IRepositoryUser repository){_repository repository;}
}
最后在注入时我们将省去注册每一个具体仓储实现如下 services.AddDbContextEFCoreDbContext(options {options.UseSqlServer(Server.;DatabaseEFCore;Trusted_ConnectionTrue;);});services.AddScoped(typeof(IRepository), typeof(EntityRepository));services.AddScopedUserService();
以上只是针对第一种反模式的基本改造对于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这块扩展实现令人叹服我也在持续学习中其他就不多讲了博客园中讲解原理的文章比比皆是好了本文到此结束倒没什么可总结的在文中已有概括我们下次再会