将代码移动到服务层的最佳实践

3

我之前提出了一个关于如何将ViewModels映射到Entity Framework models的最佳实践问题,并被建议使用LINQ投影(Projection)来编写代码,虽然AutoMapper也可以用作补充。

现在我感觉需要/想要将控制器方法中大部分操作移到新的服务层(Service layer),这样我就可以在需要时在该层添加业务逻辑,然后只需在我的控制器中调用方法即可。但是我不确定该怎么做。我的ViewModels当然仍然在Web项目中,那么服务层中的方法应该长什么样子,我应该在哪里/如何映射这些ViewModels?

以下是当前GET和POST控制器方法示例:

    public ActionResult Laboratories()
    {
        var context = new PASSEntities();
        var model = (from a in context.Laboratories
                     select new LaboratoryViewModel()
                     {
                         ID = a.ID,
                         Description = a.Description,
                         LabAdmins = (from b in context.Users_Roles
                                      join c in context.Users on b.User_ID equals c.ID
                                      where b.Laboratory_ID == a.ID
                                      select new LabAdminViewModel()
                                      {
                                          ID = b.ID,
                                          User_ID = b.User_ID,
                                          Role_ID = b.Role_ID,
                                          Laboratory_ID = b.Laboratory_ID,
                                          BNL_ID = c.BNL_ID,
                                          First_Name = c.Pool.First_Name,
                                          Last_Name = c.Pool.Last_Name,
                                          Account = c.Account
                                      })
                     });

        return View(model);
    }

    [HttpPost]
    public ActionResult AddLaboratory(LaboratoryViewModel model)
    {
        try
        {
            using (PASSEntities context = new PASSEntities())
            {
                var laboratory = new Laboratory()
                {
                    ID = model.ID,
                    Description = model.Description
                };

                context.Laboratories.Add(laboratory);
                context.SaveChanges();
            }
            return RedirectToAction("Laboratories");
        }
        catch
        {
            return View();   
        }
    }

仓储模式怎么样? https://dev59.com/BWct5IYBdhLWcg3wk-Vq - Evgeniy Labunskiy
1
你是否有一个“领域层”,用于保存你的“领域对象”,或者这些对象是在表示层中的“模型”? - Karl Anderson
PASSEntities上下文引用了Entity Framework中包含在单独项目中的我的模型。我相信这是我的领域模型...? - Steve Giordano
2个回答

5

你的服务层应该返回你的领域模型。控制器负责将它们映射到视图模型并将其返回给视图。下面是一个简单的例子:

public ActionResult Laboratories()
{
    // Get the laboratories domain models from the service layer.
    var laboratories = _laboratoryService.GetLaboratories();

    // Map the domain models to view models using AutoMapper.
    var laboratoriesModel = Mapper.Map<List<LaboratoryViewModel>>(laboratories);

    // Return view model to the view.
    return View(laboratoriesModel);
}

采用这种方法,您需要一个核心/领域层,其中包含您的领域实体。服务层包含业务逻辑,并与领域模型交互(例如,通过仓储),并将材料化的对象返回给控制器。正如您所建议的那样,您的视图模型应该在网站项目中。
请查看此问题,我在其中提供了类似解决方案的示例。 更新 服务层中的GetLaborarties方法返回一个(集合)领域模型。
public List<Laboratory> GetLaboratories()
{
    return _db.Laboratories.ToList();
}

现在在您的控制器中调用此方法并将其映射到视图模型。您可以使用Linq Select方法来实现这一点:

public ActionResult Laboratories()
{
    // Get the laboratories domain models from the service layer.
    var laboratories = _laboratoryService.GetLaboratories();

    var laboratoriesModel = laboratories.Select(new LaboratoryViewModel
                                                    {
                                                        // Map here..
                                                    }).ToList();

    return View(laboratoriesModel);
}

或者使用我上面提到的AutoMapper


更新2

涉及相关对象的导航属性的简单示例:

假设我们有这个领域模型:

public class Category
{
    public string Name { get; set; }

    public string UrlName { get; set; }

    // Other properties..

    public virtual ICollection<Product> Products { get; set; }
}

我们可以在服务层创建一个方法:
public CategoryService : ICategoryService
{
    public Category GetByName(string name)
    {
        return _categoryRepository.Table
                                  .Include(c => c.Products) // Include related products
                                  .FirstOrDefault(c => c.UrlName = name);
    }
}

我配置了实体框架,使一个类别包含零个或更多产品。使用Include方法,我要求实体框架在sql查询中包含相关的产品。现在,Products将包含类别所有相关的产品。


@SteveGiordano 服务层将领域模型返回控制器,控制器将其映射到视图模型。 - Henk Mollema
@HankMillema 好的,这正是我想的,但如果 GetLaboratories 方法需要查询多个数据库表怎么办?就像我原来为 LabAdmins 编写的示例一样。它在多个表上进行了联接。这能在服务层中实现吗?我猜这就是上面其他答案所说的,当您需要在从服务层返回域模型之前对其进行更多操作时。如果我需要作为单个模型返回的内容比我的域模型具有更多属性呢? - Steve Giordano
如果您有时间,能否向我展示一个引用导航属性的服务层方法的示例?非常感谢您的帮助。在深入进行此项目并需要进行大量重构之前,只是想弄清楚最好的做法是什么。 - Steve Giordano
@SteveGiordano 我添加了一个示例。此外,如果您的领域模型仍不适合,您可以在服务层和控制器之间创建一个类,这个类(一个数据传输对象?)只是您在控制器中需要的属性的简单组合,您可以将它们映射到视图模型。 - Henk Mollema
@SteveGiordano DTO类可以存在于服务层或更高层,例如与领域模型并列。顺便问一下,HenkM_i_llema来自哪里?应该是M_o_llema呵呵。 :-) - Henk Mollema
显示剩余8条评论

4

我认为对于如此简单的代码来说,重构到另一个层级是没有意义的。然而,在其他上下文中这个问题是有意义的,比如在更复杂的处理中,例如创建一个帐户,为其设置默认密码并分配角色,这可能需要在单个业务交易中进行少量插入和可能的少量选择。

服务层由服务组成。您的服务在域实体级别操作。

public class AccountService
{
    private PASSEntities _context { get; set; }

    public AccountService( PASSEntities context )
    {
       this._context = context;
    }

    public User CreateAccount( string username, string password )
    {
       // implementation here
    }

您从控制器访问服务,这是视图模型和模型之间进行翻译的地方,以使服务不知道视图模型层:
[HttpPost]
public ActionResult CreateUser( UserViewModel model )
{
   using ( PASSEntities context = new PASSEntities() )
   {
      AccountService service = new AccountService( context );
      service.CreateUser( model.Username, model.Password );

      // return something appropriate here     
   }
}

Wiktor - 我尊重地不同意将非复杂逻辑放入服务层没有意义的说法。我认为更好的问题是有多少个功能的消费者存在或将存在?例如,拿美元转欧元的计算来说,这并不是一个非常复杂的计算,但是有许多潜在的客户需要使用此服务,因此将其作为服务公开是完全有意义的。话虽如此,我完全同意OP需要在这个“层”中使用领域对象。 - Karl Anderson
1
@KarlAnderson:我理解你的想法,并且同意你的观点。我所说的“非复杂”指的仅是一个简单的CRUD,再加上另一层并没有多大意义。你的例子说明得很清楚。 - Wiktor Zychla
好的,我理解了你对服务层的定义以及映射如何在控制器中进行。是的,我的服务层中会有比我在这里展示的更复杂的逻辑,只是我还没有在我的项目中着手处理它。但我觉得,如果我要采取那种方法,那么所有的操作都应该在那里完成,而不是CRUD操作留在控制器中,只有更复杂的逻辑移到服务层中。把所有操作都放在同一个地方不是更清晰吗? - Steve Giordano
虽然我想,使用当前的示例,服务层将没有任何返回值,因为在这种情况下,我一次只操作一个领域模型。 - Steve Giordano
为了清晰起见,您可以在服务层中将所有内容都展现出来。 - Wiktor Zychla

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接