【问题标题】:Best practice for moving code to Service layer将代码移动到服务层的最佳实践
【发布时间】:2013-10-17 19:30:47
【问题描述】:

我问了一个关于将 ViewModels 映射到控制器中的实体框架模型的最佳实践的问题,并被告知我的代码是正确的(使用 LINQ 投影),但也可以使用 AutoMapper。

现在我觉得我需要/想要将 Controller 方法中发生的大部分内容转移到新的 Service 层,这样我就可以在需要时在该层添加业务逻辑,然后在我的控制器中调用方法。但我不确定该怎么做。当然,我的 ViewModel 都将保留在 Web 项目中,那么我在服务层中的方法应该是什么样的?我应该在哪里/如何映射 ViewModel?

这是当前 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();   
        }
    }

【问题讨论】:

  • 存储库模式怎么样? stackoverflow.com/questions/11985736/…
  • 你有一个domain layer,它包含你的domain objects,还是你的models在表示层中的那个?
  • PASSEntities 上下文引用了我来自实体框架的模型,这些模型包含在一个单独的项目中。我相信这是我的领域模型...?

标签: c# asp.net-mvc entity-framework


【解决方案1】:

您的服务层应该返回您的域模型。控制器负责将它们映射到视图模型并将其返回给视图。一个小例子:

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);
}

使用这种方法,您需要一个核心/域层来存放您的域实体。服务层包含业务逻辑并与域模型交互(例如通过存储库)并将物化对象返回给控制器。正如您所建议的,您的视图模型确实应该在网站项目中。

还可以查看this question,我在其中提供了类似解决方案的示例。

更新

服务层中的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 将包含该类别的所有相关产品。

【讨论】:

  • 因此在您的示例中,GetLaboratories 方法将存在于服务层中。在我的示例中,我使用 LINQ 将域模型中的数据转换为 ViewModel。我会在服务层的 GetLaboratories 方法中执行此操作。如果 ViewModel 将保留在网站项目中,我应该将 LINQ 投影到服务层中,然后将其映射回控制器中?我是否在服务层中定义了另一个与我的域层中的实体框架模型相同的模型?
  • @SteveGiordano 服务层将域模型返回给控制器,控制器将其映射到视图模型。
  • @HenkMillema 如果我需要返回一个可以映射到控制器中的视图模型的域模型,您能否根据上面的 Laboratories() 控制器方法指出代码的样子?我还会使用 LINQ 吗?
  • @SteveGiordano 您可以使用 LINQ Select 方法来投影视图模型。或使用AutoMapper
  • @HenkMillema 是的,这就是我目前在控制器中所做的事情。但是如果我将该代码移动到服务层,我将不再投影到服务层的视图模型中(因为视图模型应该保留在网站层中)那么 LINQ 在服务层中会做什么呢?我是否要实例化一个新的域模型并以某种方式将多个模型投影到其中?
【解决方案2】:

我认为将这种简单的代码重构到另一层是没有意义的,但是这个问题在其他情况下是有意义的,在这些情况下会发生更复杂的处理,例如,您创建一个帐户,为其设置默认密码并分配在单个业务事务中进行很少插入和可能选择的角色。

服务层由服务组成。您的服务在域实体级别运行。

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 需要使用此“层”中的域对象。
  • @KarlAnderson:我理解你的推理并同意你的看法。我所说的“非复杂”只是指一个简单的 CRUD,它没有多大意义,它还有另一层。你的例子很有意义。
  • 好的,我了解您对服务层的定义以及映射仍将如何在控制器中发生。是的,我在服务层中的逻辑将比我在这里展示的更复杂,我只是在我的项目中还没有得到它。但我觉得如果我要走那条路,那么一切都应该在那里完成,而不是 CRUD 操作留在控制器中,只有更复杂的逻辑移动到服务层。将所有操作都在同一个地方进行不是更简洁吗?
  • 虽然我认为在我当前的示例中服务层将没有任何返回,因为在这种情况下我一次只操作一个域模型?
  • 为了清楚起见,您可以将所有内容都放在服务层中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-09-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多