使用DTOS将数据从存储库获取到服务层

时间:2019-01-21 19:08:27

标签: c# asp.net asp.net-web-api model-view-controller repository-pattern

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

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

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

现在,如果我从EmployeeRepository中获得雇员列表作为IQueryable,并在我的服务层上将它们选择为EmployeeDTO,我将通过允许服务层访问数据库来违反该规则。

如果不是,则EmployeeRepository将每个雇员的所有数据作为列表返回到服务层,以便服务层仅选择几个字段来创建EmployeeDTO。但是,通过这种方法,我将加载不需要的所有数据。

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

问候,

3 个答案:

答案 0 :(得分:1)

当我开始学习时,我也有类似的困惑。

  

现在,如果我从EmployeeRepository获得的雇员列表为   IQueryable,然后在我的服务层上将它们选择为EmployeeDTO   通过启用服务层访问   数据库。

您的理解是正确的。该存储库不应公开IQueryable,而应仅返回数据。

  

如果不是,那么EmployeeRepository将返回每个的所有数据   员工作为列表添加到服务层,以便该服务层   仅选择几个字段来创建EmployeeDTO。但是用这种方法   我将加载所有不需要的数据。

这实际上是基于您的需求和您的应用程序需求。方法声明及其应如何返回没有严格的规则。 从下面的内容中,我可以理解两件事:“ 将每个员工的所有数据返回到服务层 ”。

  • 无论员工是什么,都将所有数据返回给服务层。产品,然后您将获得员工所需的产品。 如果是这种情况,则可以限制使用参数
  • 或者您是指要返回的对象属性。这是基于从存储库到服务层的DTO。您可以根据需要定义自己的DTO。为此,我的建议是,如果您可以为Db和DTO保留单独的对象,那么它将使您的系统变得复杂并且难以维护。将数据公开给用户时,请保留必要的单独对象。

示例实现 Dotnet-Repository-Pattern-Template-With-Unit-Of-Work

在此示例中,使用了三层。

  1. Api-向用户公开数据
  2. BL-服务层-处理所有业务逻辑并处理CRUD的存储库
  3. 持久性-处理持久性-封装大型/复杂查询。

答案 1 :(得分:0)

我认为您应该只从存储库中返回所需的列。之后,将您的服务映射到正确的Dto。我认为这个主题有很多不同的观点并且有点难。在下面的示例中,没有服务,只有仓库和api控制器。

void strassen(int A[][N], int B[][N], int C[][N]) {
  if (N == 1) {
    C[0][0] = A[0][0] * B[0][0];
  }

  if (N > 1 && N % 2 != 0) {
    int A_new[N + 1][N + 1];
    int B_new[N + 1][N + 1];
    int i, j;
    for (i = 0; i < N + 1; i++) {
      if (i == N) {
        for (j = 0; j < N + 1; j++) {
          A_new[i][j] = 0;
          B_new[i][j] = 0;
        }

      } else {
        for (j = 0; j < N + 1; j++) {
          if (j == N) {
            A_new[i][j] = 0;
            B_new[i][j] = 0;
          } else {
            A_new[i][j] = A[i][j];
            B_new[i][j] = B[i][j];

          }

        }
      }
    }

    for (i = 0; i < N + 1; i++) {
      for (j = 0; j < N + 1; j++) {
        C[i][j] = 0;
      }
    }

  } else {
    int i, j;
    int A_new[N][N];
    int B_new[N][N];
    for (i = 0; i < N; i++) {
      for (j = 0; j < N; j++) {
        A_new[i][j] = A[i][j];
      }
    }

    for (i = 0; i < N; i++) {
      for (j = 0; j < N; j++) {
        B_new[i][j] = B[i][j];
      }
    }

    for (i = 0; i < N; i++) {
      for (j = 0; j < N; j++) {
        C[i][j] = 0;
      }
    }
  }

答案 2 :(得分:0)

重要提示

我在下面将要解释的所有内容都是基于以下假设:您想学习一些在构建大型企业级应用程序时有用的技能。在这些情况下,可维护性和可扩展性是首要任务。为什么?因为如果您不注意它们,意外的复杂性很容易使比例损失并将您的代码库变成一大碗意大利面条代码。

返回定义

首先,让我们重新访问definition of the repository pattern。如果仔细阅读,它会指出其目的是包含查询逻辑,在您的方案中,这是使用LINQ-to-SQL完成的(如果您使用的是ADO.NET,则查询逻辑将是您要使用的实际SQL执行)。
这种模式背后的想法是,您不希望存储库的使用者知道或关心您正在使用哪种类型的存储机制和查询/操纵方式。
他们想要并且应该知道的是他们有一个接口,例如:

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

这意味着,如果您在公共接口的任何地方引入IQueryable,它会自动告诉他们“嘿,尽管您正在使用接口,但我只会让您知道它实际上是您正在与之通信的数据库“ ...总体上来说,这超出了接口和抽象的目的。在这方面,您说服务层不应该访问数据库是正确的。

其次,这是一篇很好的文章来支持我的观点,即IQueryable is a leaky abstraction-它在您的应用程序和存储机制之间建立了紧密的联系。

回到我上面定义的接口,它有一个大问题-它非常粗糙。按照您的示例,在大多数情况下,您既不想提取与雇员相关的所有数据,也不想提取与所有雇员有关的所有数据,而是每次都提取不同的行和列子集。但是我们只是放弃了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,则决定使用它的其他任何人。

但是,无论是谁调用该代码,都应该使他们不知道幕后发生的事情。如果您拥有整个代码库,则这同样适用。考虑以下情形:
将来的某个时候,您的公司决定购买一些精美的软件,以帮助他们进行会计核算并放弃关系数据库。为了保持您的应用程序运行,您还必须放弃对Entity Framework的任何使用,而应对此软件进行Web服务调用。现在,如果按照您的建议,您已经在所有服务中直接使用了EF(由于尚未实现存储库,这是唯一剩下的选择),因此在Servuce层中会有很多地方需要请删除所有对EF的引用,并将其转换为使用新Web服务的代码。这称为shotgun surgery,其原因是您正在混合应该分开的两件事:数据检索和数据处理。
如果您采用存储库方式,则唯一需要更改的就是存储库(数据检索),而所有服务逻辑(处理)都将保持不变。解耦代码的美丽:)

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