在ASP.NET MVC中分离数据访问


35

我想确保我遵循行业标准和最佳实践,这是我第一次真正接触MVC。在这种情况下,它是使用C#的ASP.NET MVC。

我将对我的模型使用Entity Framework 4.1和代码优先对象(数据库已经存在),因此将有一个DBContext对象用于从数据库中检索数据。

在我在asp.net网站上进行的演示中,控制器中包含数据访问代码。这对我来说似乎不正确,尤其是在遵循DRY(不要重复自己)做法时。

例如,假设我正在编写要在公共图书馆中使用的Web应用程序,并且有一个用于创建,更新和删除目录中书籍的控制器。

某些操作可能需要一个ISBN,并且需要返回“ Book”对象(请注意,这可能不是100%有效的代码):

public class BookController : Controller
{
    LibraryDBContext _db = new LibraryDBContext();

    public ActionResult Details(String ISBNtoGet)
    {
        Book currentBook = _db.Books.Single(b => b.ISBN == ISBNtoGet);
        return View(currentBook);
    }

    public ActionResult Edit(String ISBNtoGet)
    {
        Book currentBook = _db.Books.Single(b => b.ISBN == ISBNtoGet);
        return View(currentBook);
    }
}

相反,我是否应该在我的数据库上下文对象中实际上有一个方法可以返回一本Book?对我来说,这似乎是更好的隔离,并且有助于促进DRY,因为我可能需要通过ISBN在Web应用程序中的其他位置获取Book对象。

public partial class LibraryDBContext: DBContext
{
    public Book GetBookByISBN(String ISBNtoGet)
    {
        return Books.Single(b => b.ISBN == ISBNtoGet);
    }
}

public class BookController : Controller
{
    LibraryDBContext _db = new LibraryDBContext();

    public ActionResult Details(String ISBNtoGet)
    {
        return View(_db.GetBookByISBN(ISBNtoGet));
    }

    public ActionResult Edit(ByVal ISBNtoGet as String)
    {
        return View(_db.GetBookByISBN(ISBNtoGet));
    }
}

这是我的应用程序编码中遵循的一组有效规则吗?

或者,我想一个更主观的问题是:“这是正确的方法吗?”

Answers:


55

通常,您希望控制器仅做以下几件事:

  1. 处理传入的请求
  2. 将处理委托给某些业务对象
  3. 将业务处理的结果传递到适当的视图以进行渲染

控制器中不应有任何数据访问或复杂的业务逻辑。

[在最简单的应用程序中,您可能可以在控制器中执行基本的数据CRUD操作,但是一旦开始添加不仅仅是简单的Get和Update调用,您将希望将处理分为一个单独的类。 ]

您的控制器通常将依靠“服务”来进行实际的处理工作。在服务类中,您可以直接使用数据源(以您的情况为DbContext),但是再次,如果您发现除了数据访问之外还编写了许多业务规则,您可能希望将业务分开数据访问中的逻辑。

届时,您可能会有一个只对数据进行访问的类。有时,这称为存储库,但是名称的确无关紧要。关键是所有用于将数据进出数据库的代码都放在一个地方。

对于我从事的每个MVC项目,我总是以如下结构结束:

控制者

public class BookController : Controller
{
    ILibraryService _libraryService;

    public BookController(ILibraryService libraryService)
    {
        _libraryService = libraryService;
    }

    public ActionResult Details(String isbn)
    {
        Book currentBook = _libraryService.RetrieveBookByISBN(isbn);
        return View(ConvertToBookViewModel(currentBook));
    }

    public ActionResult DoSomethingComplexWithBook(ComplexBookActionRequest request)
    {
        var responseViewModel = _libraryService.ProcessTheComplexStuff(request);
        return View(responseViewModel);
    }
}

商业服务

public class LibraryService : ILibraryService
{
     IBookRepository _bookRepository;
     ICustomerRepository _customerRepository;

     public LibraryService(IBookRepository bookRepository, 
                           ICustomerRepository _customerRepository )
     {
          _bookRepository = bookRepository;
          _customerRepository = customerRepository;
     }

     public Book RetrieveBookByISBN(string isbn)
     {
          return _bookRepository.GetBookByISBN(isbn);
     }

     public ComplexBookActionResult ProcessTheComplexStuff(ComplexBookActionRequest request)
     {
          // Possibly some business logic here

          Book book = _bookRepository.GetBookByISBN(request.Isbn);
          Customer customer = _customerRepository.GetCustomerById(request.CustomerId);

          // Probably more business logic here

          _libraryRepository.Save(book);

          return complexBusinessActionResult;

     } 
}

资料库

public class BookRepository : IBookRepository
{
     LibraryDBContext _db = new LibraryDBContext();

     public Book GetBookByIsbn(string isbn)
     {
         return _db.Books.Single(b => b.ISBN == isbn);
     }

     // And the rest of the data access
}

+1总体上不错的建议,尽管我会质疑存储库抽象是否具有任何价值。
MattDavey 2012年

3
@MattDavey是的,一开始(或对于最简单的应用程序)很难看到需要存储库层,但是只要您的业务逻辑具有中等程度的复杂性,它就变得轻而易举分开数据访问。不过,以简单的方式传达这一点并不容易。
埃里克·金

1
@Billy的IoC的内核不具有是在MVC项目。您可以将其放在一个自己的项目中,该项目属于MVC项目,但又取决于存储库项目。我一般不这样做,因为我觉得没有必要。即使这样,如果您不希望MVC项目调用存储库类……也不要。我不抽筋自己,这样我可以保护自己的编程实践的可能性,我不可能搞一个大风扇。
埃里克·金

2
我们完全使用以下模式:Controller-Service-Repository。我想补充一点,让服务/存储库层接受参数对象(例如GetBooksParameters),然后在ILibraryService上使用扩展方法来进行参数整理对我们非常有用。这样,ILibraryService有一个带对象的简单入口点,并且扩展方法可以使参数尽可能疯狂,而不必每次都重写接口和类(例如,GetBooksByISBN / Customer / Date /无论是什么形式的GetBooksParameters对象并调用服务)。组合很棒。
BlackjacketMack 2014年

1
@IsaacKleinman我不记得是哪位伟人写过这本书(鲍勃·马丁?),但这是一个基本问题:您想要Oven.Bake(披萨)还是Pizza.Bake(烤箱)。答案是“取决于”。通常,我们需要外部服务(或工作单元)来操纵一个或多个对象(或披萨!)。但是谁能说那些单独的对象没有能力对要烘烤的烤箱类型做出反应。我更喜欢OrderRepository.Save(order)而不是Order.Save()。但是,我喜欢Order.Validate(),因为订单可以知道它是自己理想的形式。情境和个人。
BlackjacketMack 2015年

2

尽管我将数据提供程序作为通用数据服务接口注入,以便可以交换实现,但我一直都是这样做的。

据我所知,控制器旨在用于获取数据,执行任何操作并将数据传递到视图。


是的,我已经读过有关为数据提供者使用“服务接口”的信息,因为它有助于单元测试。
scott.korin 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.