.net 如何在存储库模式上从实体框架核心设置多个服务?

.net 如何在存储库模式上从实体框架核心设置多个服务?,.net,entity-framework,asp.net-core,.net-core,entity-framework-core,.net,Entity Framework,Asp.net Core,.net Core,Entity Framework Core,我正在尝试在实体框架核心中使用存储库模式构建一个结构 我有一个通用接口和一个用于一般操作的服务。他们只是做积垢交易 我的界面: public interface IGeneralService<TEntity> where TEntity : class { void Delete(TEntity entityToDelete); void Delete(object id); IEnumerable<TEntity> Get(

我正在尝试在实体框架核心中使用存储库模式构建一个结构

我有一个通用接口和一个用于一般操作的服务。他们只是做积垢交易

我的界面:

  public interface IGeneralService<TEntity> where TEntity : class
{
    void Delete(TEntity entityToDelete);

    void Delete(object id);

    IEnumerable<TEntity> Get(
        Expression<Func<TEntity, bool>> filter = null,
        Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
        string includeProperties = "");

    TEntity GetById(object id);

    void Insert(TEntity entity);

    void Update(TEntity entityToUpdate);

}
我想在控制器前面放置另一个服务层

    [HttpGet]
    public Student GetStudent(int id)
    {
        var student = _generalService.Get(filter: x => x.id == id).FirstOrDefault();
        return student;
    }
当我想进行插入时,它必须首先符合我的业务规则(如电话号码、空检查等),但我找不到如何在“存储库模式”中调用多个服务(逻辑)(不创建新实例)

如果我创建一个新实例,如下所示:

  public static DrivingSchoolContext DrivingSchoolContext;
    private readonly IGeneralService<Student> _generalService;
    private readonly StudentService _studentService;
    public StudentController(DrivingSchoolContext drivingSchoolContext, IGeneralService<Student> generalService)
    {
        DrivingSchoolContext = drivingSchoolContext;
        _generalService = generalService;
        this._studentService = new StudentService(drivingSchoolContext);

    }


    [HttpGet]
    public Student GetStudent(int id)
    {
        var studentFromNewInstance = _studentService.GetStudent(id);
        var student = _generalService.Get(filter: x => x.id == id).FirstOrDefault();
        return student;
    }
 [HttpGet]
    public IActionResult CreateStudent(Student student)
    {

        student.FullName = StudentService.GetFullName(student.firstName + student.lastName);
        _generalService.Insert(student);

        return Ok("Successfully Created Student");
    }

一个建议是避免对存储库/服务使用通用模式,不要过度关注抽象。我知道这很有诱惑力,看起来也很有效率,但在编写高性能数据访问时,它会让你陷入困境。当涉及抽象时,我的建议是遵循K.I.S.S.并在实现最简单的解决方案后在何时何地整合它是可行的。当涉及到泛型和继承时,尝试为此“预先”进行设计是一种过早优化的形式。泛型和继承都应该应用于服务相同的行为。这绝对是关键。我经常看到开发人员在项目的早期就沉迷于这些行为,而不是仅仅是类似的行为。它们对于100%相同而不是95%相同的行为非常有用。这可能会将您置于一个不太理想的解决方案中,通常会限制性能或您适应不断变化的需求的能力,或者会导致妥协或偏差(即条件代码),从而为bug打开大门,或者,重新考虑不符合基于过去假设/知识的设计决策的新需求会增加相当大的成本

我推荐的方法是像对待控制器一样对待存储库。如果你有一个ManageStudentController,那么我就有一个ManageStudentRepository。可以根据数据存储的需求在存储库中定位低级业务逻辑。如果您真的想深入研究关注点分离,您可以创建一个StudentFactory类,但我发现存储库非常适合承担这一责任。最终由视图逻辑来包含业务规则以确保数据有效,存储库充当数据存储的最终看门人。它将持有一个CreateStudent方法,该方法接受所有所需详细信息的参数,以生成一个最小完整和有效的学生实体,将其与DbContext关联,并返回它。因此,如果需要姓名和电话号码等详细信息,CreateStudent方法将强制执行这些低级数据规则。对于不可为null的关系,它还可以接受FK值或相关实体。这就是为什么存储库是使用此方法的好地方,因为它可以验证值(例如检查唯一性)并在给定键时加载相关实体。然后,控制员等可以在工作单元提交更改之前,以受控的方式将任何可选细节填写到返回学生中(理想情况下使用DDD变异方法,而不是setter)

通用模式(如您所概述的)是一种非常常见的存储库实现,它的问题在于效率非常低

IEnumerable<TEntity> Get(
    Expression<Func<TEntity, bool>> filter = null,
    Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
    string includeProperties = "");
如果您使用的是软删除系统(使用IsActive而不是硬行删除),则:

IQueryable Get(bool includeInActivity=false);
存储库可以自动管理适用于查询的低级规则,如IsActive、身份验证/授权。然后,呼叫者可以完全自由地进一步过滤、排序、分页和投影结果,不管结果如何适合他们,再加上利用
async
/
wait
。这为您提供了对数据消耗的最终控制,以获得最大性能,并为单元测试提供了一个非常简单的抽象点。典型的问题是“如果dbset上的包装如此之薄,为什么还要麻烦使用存储库呢?”。答案是两件事:A)模拟存储库和
IQueryable
要比模拟
DbContext
/
DbSet
容易得多;B)存储库仍然是执行低级数据规则的好地方,以确保实体“足够完整”以及授权检查等

反对使用
IQueryable
的一个常见论点是,人们觉得它“泄露”了isms/knowledge。然而,使用表达式等尝试利用过滤、排序等功能的解决方案是100%泄漏的。过滤和排序表达式仍然需要考虑EF会理解什么,例如不使用本地函数或未映射属性等,以灵活地与<代码> IQueEnaby >,你最终改写了<>代码> IQueEngy//Cord>,并且仍然左短。(如投影/w
选择
或Automapper的
投影到

通过像控制器这样组织存储库,一个参数可能会违反DRY(不要重复您自己),因为管理学生的过程可能包括获取其他相关数据的存储库方法,因此类似于
GetClasses()
要获取可与学生关联的可用类的列表,应用程序的其他区域也可能存在,因此其他存储库也可能存在。这当然是真的,但反论点是干必须尊重单一责任原则。代码应该有一个原因,而且只有一个原因需要更改。无论您是编写一个
存储库
还是
类存储库
来坚持DRY,您都违反了SRP。似乎ClassRepository存在的唯一原因是为类服务,但事实上,引用ClassRepository的每个控制器/服务都会对存储库施加“更改原因”。每个控制器都不可避免地需要一些不同的东西
 [HttpGet]
    public IActionResult CreateStudent(Student student)
    {

        student.FullName = StudentService.GetFullName(student.firstName + student.lastName);
        _generalService.Insert(student);

        return Ok("Successfully Created Student");
    }
IEnumerable<TEntity> Get(
    Expression<Func<TEntity, bool>> filter = null,
    Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
    string includeProperties = "");
 IQueryable<TEntity> Get();
IQueryable<TEntity> Get(bool includeInactive = false);