WCF:在哪个层执行POCO实体到DTO的转换?

时间:2012-06-20 12:13:40

标签: wcf converter dto n-tier-architecture

这是here的问题的后续跟踪。

首先,我在项目中没有使用 DDD。

我有一个包含3层的WCF服务:

  • 服务层(仅包含操作并调用BL方法)
  • 包含所有业务逻辑类和方法的业务逻辑层
  • 包含DbContext(LINQ-TO-EF)和POCO实体的数据访问层

WCF服务需要返回DTO对象,而我无法确定将“POCO”实体转换为DTO的“Translator”类的最佳位置。

我有两种选择:

方法A

让业务逻辑方法将实体返回到服务层,服务层中有一个转换器类,用于将实体转换为DTO。

优点:

  • 业务逻辑层完成它必须做的事情 - 验证和CRUD操作
  • 业务逻辑层根本不需要了解DTO

CONS:

  • 服务层现在必须包含对“数据访问层”程序集的引用,因为它从业务逻辑层接收实体。这似乎打破了3层概念,即服务层只需要引用BL层,而BL层只需要引用DAL。
  • 这是最糟糕的问题:翻译类需要从实体对象创建DTO。因为它在处理DbContext后从BL接收实体对象,所以它无法访问未加载“Include”扩展名的任何内容。这意味着BL方法需要将实体返回到服务层,其中所有是翻译者为了创建DTO而需要的。这是一个问题,因为它需要BL知道翻译者需要什么,其次 - 它将从数据库中获取很多不必要的数据! (也许翻译者需要返回一个'UserDto'对象,其中一个字段是'订单总数' - 为什么我要从数据库中获取所有订单只是为了在翻译器中进行'Count()'调用?

方法B

让“Translator”类从实体对象转换为放置在“业务逻辑层”中的DTO。在这种机制中 - BL方法已经返回DTO。

优点:

  • BL方法执行BL代码,然后调用'translate_to_dto'相应的消息将结果转换为返回的DTO。这一切都在'DbContext'中完成,这意味着当调用翻译类来翻译实体时,它仍然可以访问子对象,并且不需要调用'Include'。这意味着只从数据库中获取创建DTO所需的数据。

CONS:

  • 现在,“业务逻辑层”中的“翻译者”类是需要了解DTO的人,即使服务层有责任只知道它们!
  • BL中的每个方法现在执行纯BL(有效性检查,CRUD操作等),另外调用转换器方法以返回DTO。这打破了“单一责任规则”,该规则指出一种方法(在BL中)应该只做一件具体的事情。

任何人都可以告诉我在哪里正确的地方执行'实体==> DTO的转换?

[更新 - 已添加示例]

业务逻辑层有一个名为 UserManager 的管理器类,它有一个这样的BL方法:

public UserTasksDto     GetUserInfoWithTasks(Guid userId)
{
    if (userId == Guid.Empty)
        throw new ArgumentException("User ID cannot be empty");

    using (IMyDBEntities entities = _contextFactory.GetContext())
    {
        // Get POCO Object from DbContext
        User user = entities.Users.Find(userId);
        if (user == null)
            throw new EntityNotFoundException("User was not found in the database");

        if (user.Tasks.Count() == 0)
            throw new Exception("User does not have any tasks !");

        // Call 'Translator' static method to translate POCO to DTO
        Translator.TranslateUserToUserTasksDto(user);
    }
}

如上所示 - BL方法调用'translator'方法将POCO转换为DTO。 这是在内部“实体”上下文中完成的,这样翻译人员仍然可以访问用户的“任务”子项。

以下是'翻译'方法:

class Translator
{
    public static UserTasksDto   TranslateUserToUserTasksDto ( User userPoco )
    {
        UserTasksDto dto = new UserTasksDto
        {
            UserId        = userPoco.Id,
            Username      = userPoco.Username,
            CreationDate  = userPoco.CreationDate,

            // Accessing a related entity, this is why this 'translate' method 
            // needs to be called inside the DbContext, otherwise it will except
            // (or we load all related entities using 'Include' just for the 'Count' purpose)
            Supervisor    = userPoco.Supervisor.Username,    
            NumOfTasks    = userPoco.Tasks.Count(),
            FirstTaskDate = userPoco.Tasks.OrderBy(task => task.Date).Take(1),
        }

        return dto;
    }
}

正如您在上面所看到的 - “翻译”方法从“用户”POCO对象“构建”'UserTasksDto'。 这是通过将“用户”对象及其相关实体中的某些字段映射到DTO来完成的。 如果这个方法不是里面 BL方法的ObjectContext - 我会得到一个例外,说我试图访问没有上下文的实体。

我希望我现在的问题更清楚......

2 个答案:

答案 0 :(得分:2)

虽然有问题,

如果您需要服务层独立于BL / DAL数据实体,那么, 在我看来,你必须为独立模型设置一个新的抽象层(dll程序集)。

您的BL和/或DAL现在不会返回实体,而是返回此新程序集中的模型对象。

您的服务图层不需要引用实体的DAL,而是引用新的模型程序集。

这是与视图模型类似的模式。

现在,服务层的工作是将模型转换为DTO,如果它选择这样做。

PROS:会释放您的BL / DAL依赖

缺点:模型抽象层看起来好像是redundent


修改

哦,我不是故意要从业务层返回DTO。 我的意思是从业务逻辑中返回独立模型,以便它们真正独立于所有层,并且随后使用它们的任何层都可以使用它们或将它们转换为所需的任何形式。

例如,服务层可以包括DTO转换器的模型,表示层可以将它们转换为视图模型,存储库可以选择将它们转换为XML ....等等。现在每个层都有自己的Model to X转换器,BL只有单一责任,所有层都独立于DAL实体。

N.B。有些图层可能会选择直接使用它们,我相信这是你关注的问题,是的,如果他们这样做,他们会表现得好像BL返回了DTO / VM,但这不是我的意图。

希望现在能够解决问题......


<强>更新

请注意,您是一名开发人员,除了BL之外不会写任何图层。

您会返回DAL的实体吗?没有。

您是否有任何关于如何使用库以及将使用哪些层的概念。 NO。

您将在DAL实体之上创建一个新的抽象,并将这些抽象返回到将使用您的库的WHICHEVER层。

因此,如果开发人员X出现并使用您的库创建ACME WCF服务,我很确定X不会将您的模型对象用作他的DTO,而是X将使用您的模型作为开始创建DTO。

Developer Y出现并使用您的库创建一个ACME ASP.NET MVC 3应用程序,我很确定Y不会将您的模型对象用作他的VM(View Models),而是Y将使用您的模型创建VM一个开始。

答案 1 :(得分:0)

可能没有正确的位置,但最合乎逻辑的地方是您的业务逻辑。