首页 > 解决方案 > 使用 DTOS 从 Repository 获取数据到服务层

问题描述

我正在学习 ASP.NET 存储库模式。我做了一些研究,找不到正确的答案。

我的 DAL 上有一个数据库实体 Employee,它有许多列。我有负责使用 DBContext 查询数据的 EmployeeRepository。

现在我的服务层上有 EmployeeService,它需要为员工返回一个 DTO,也就是说,它只有几个字段。(名字,姓氏,出生日期)。

现在,如果我从 EmployeeRepository 中获取作为 IQueryable 的员工列表,并在我的服务层上选择它们作为 EmployeeDTO,我会通过启用服务层访问数据库来违反规则。

如果没有,那么 EmployeeRepository 会将每个员工的所有数据以列表的形式返回给服务层,以便服务层只选择几个字段来创建 EmployeeDTO。但在这种方法中,我将加载所有我不需要的数据。

我想知道是否有人有建议。

亲切的问候,

标签: c#asp.netasp.net-web-apimodel-view-controllerrepository-pattern

解决方案


重要的提示

我将在下面解释的所有内容都基于您想要学习一些在构建大型企业级应用程序时有用的技能的假​​设。在这些场景中,可维护性和可扩展性是主要优先事项。为什么?因为如果你不注意它们,意外的复杂性很容易被打破,把你的代码库变成一大碗意大利面条代码。

回到定义

首先,让我们重新审视存储库模式的定义。如果您仔细阅读它,它会声明其目的是包含查询逻辑,在您的场景中是使用 LINQ-to-SQL 完成的(如果您使用 ADO.NET,您的查询逻辑将是您要执行的实际 SQL执行)。
这种模式背后的想法是,您不希望存储库的消费者知道或关心您正在使用什么类型的存储机制和查询/操作方式。
他们想要并且应该知道的是他们有一个接口,例如:

public interface IEmployeeRepository
{
    IEnumerable<Employee> GetAllEmployees();
    Employee GetEmployee(int id);
}

这意味着,如果您在公共接口的任何位置引入 IQueryable,这会自动告诉他们“嘿,虽然您正在与接口交谈,但我只会让您知道它实际上是您正在与之交谈的数据库”.. . 这有点超出了接口和抽象的目的。在这方面,您说服务层不应该访问数据库是对的。

其次,这是一篇很好的文章来支持我的观点,即IQueryable 是一种泄漏抽象——它在您的应用程序和存储机制之间创建了紧密耦合。

回到我上面定义的接口,它有一个很大的问题——它非常粗糙。根据您的示例,在大多数情况下,您不想提取与员工相关的所有数据,也不想提取所有员工的所有数据,而是每次都提取不同的行和列子集。但是我们刚刚放弃了 IQueryable 提供的灵活性,还剩下什么?

只带你需要的东西

现在,由于我们已经确定 IQueryable 不应成为您接口的公共 API 的一部分,因此只剩下一个(我知道的)替代方案来获得您需要的东西:为您需要获取的每个不同的员工数据子集使用不同的方法.

例如,您在问题中要求的场景看起来是谎言

public interface IEmployeeRepository
{
    IEnumerable<BasicEmployeeData> GetBasicEmployeesData(IEnumerable<int> ids);
}

public class Employee
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime DateOfBirth { get; set; }
}

public class EntityFrameworkEmployeeRepository : IEmployeeRepository
{
    public IEnumerable<BasicEmployeeData> GetBasicEmployeesData(IEnumerable<int> ids)
    {
        using (var context = new EmployeeContext())
        {
            return context.Employees.Where(e => ids.Any(id => id == e)).ToList();
            // I haven't tested if .Any works or whether you should instead do .Join
            // but this is orthogonal to the main question
        }
    }
}

如果将来您只需要提取几个员工的 id、名字、姓氏、月薪和绩效评级,那么您所要做的就是创建一个新的 DTO(例如 EmployeeEvaluation) ,添加一个返回给定 DTO 的 IEnumerable 的接口方法,然后将此新方法实现到您的 EntityFrameworkRepository 中。

此外,如果在未来的某个地方,您想要提取您为某些员工获得的所有数据,您的界面可能如下所示:

public interface IEmployeeRepository
{
    IEnumerable<BasicEmployeeData> GetBasicEmployeesData(IEnumerable<int> ids);
    IEnumerable<EmployeeEvaluation> GetEmployeeEvaluations(IEnumerable<int> ids);
    IEnumerable<Employee> GetEmployees(IEnumerable<int> ids);
    IEnumerable<Employee> GetEmployees();
}


在回复包含“在我的情况下,我还将构建服务层。所以,我将成为我自己的存储库的客户端。所以,如果是这种情况,我不确定我是否应该在全部。”

当使用“客户端”这个词时,我指的是任何调用您的存储库的代码,无论是您自己的还是其他人的。所以它可以是你的服务、你的控制器、你的测试,或者如果你决定编译和分发你的代码作为一个 DLL,任何其他决定使用它的人。

但是,无论谁调用该代码,他们都应该不知道幕后发生的事情。如果您拥有整个代码库,这也适用,就像您的情况一样。考虑以下场景:
在未来的某个时候,您的公司决定购买一些精美的软件来帮助他们进行会计处理并放弃关系数据库。为了保持您的应用程序运行,您还必须放弃对实体框架的任何使用,而是对该软件进行 Web 服务调用。现在,如果按照您的建议,您已经在所有服务中直接使用了 EF(因为您还没有实现存储库,这是唯一剩下的选择),您的 Servuce 层中会有很多地方您必须去吧,删除对 EF 的所有引用并将其转换为使用新 Web 服务的代码。这被称为霰弹枪手术,原因是你混合了两个应该分开的东西:数据检索和数据处理。
如果您采用存储库的方式,那么您唯一需要更改的是存储库(数据检索),而所有服务逻辑(处理)将保持不变。解耦代码之美 :)

考虑到这些,我强烈建议您实际实现存储库模式。它确实可以帮助您保持解耦并符合 SOLID 中的 S。


推荐阅读