MVC,ORM和数据访问模式

时间:2011-03-17 13:18:41

标签: c# asp.net-mvc design-patterns orm asp.net-mvc-3

我认为我已经达到了“分析瘫痪”的状态。 我有一个MVC应用程序,使用EF作为ORM。 所以我正在尝试决定最好的数据访问模式,到目前为止我认为将所有数据访问逻辑放入控制器是要走的路......但它听起来有点不对劲。 另一种选择是创建外部存储库,处理数据交互。 这是我的优点/缺点:

如果嵌入数据访问控制器,我将得到这样的代码:

using (DbContext db = new DbContext())
{
    User user = db.Users.Where(x=>x.Name == "Bob").Single();
    user.Address.Street = "some st";
    db.SaveChanges();
}

所以有了这个,我得到了延迟加载的全部好处,我在完成后立即关闭连接,我对where子句的灵活性 - 所有的细节。 con - 我在一个方法中混合了一堆东西 - 数据检查,数据访问,UI交互。

使用Repository,我正在外部化数据访问,理论上,如果我决定使用ado.net或使用不同的数据库,则可以替换repos。 但是,我没有看到实现延迟加载的良好清洁方式,以及如何控制DbContext /连接的生命周期。 说,我有CRUD方法的IRepository接口,我如何加载属于给定用户的地址列表?像GetAddressListByUserId这样的方法看起来很难看,错了, 并且会让我创建一堆同样难看的方法,并且在使用ORM时没什么用。

我确信这个问题已经解决了数百万次,希望有一个解决方案..


关于存储库模式的另一个问题 - 如何处理属性对象?例如。用户有一个地址列表,您将如何检索该列表?为地址创建存储库?使用ORM,地址对象不必具有回复用户的引用,也不需要具有repo的Id字段 - 它必须拥有所有这些。更多代码,更多暴露属性..

3 个答案:

答案 0 :(得分:7)

您选择的方法很大程度上取决于您要使用的项目类型。对于需要Rapid Application DevelopmentRAD)方法的小型项目,在MVC项目中直接使用EF模型并在控制器中访问数据几乎可以,但项目增长得越多,它会变得越乱,你将开始遇到越来越多的问题。如果您需要良好的设计和可维护性,有几种不同的方法,但一般来说,您可以坚持以下:

保持控制器和视图干净。控制器应该只控制应用程序流,不包含数据访问甚至业务逻辑。视图应该仅用于表示 - 给它一个ViewModel,它将它呈现为Html(没有业务逻辑或计算)。每个视图ViewModel是一种非常干净的方式。

典型的控制器操作如下:

public ActionResult UpdateCompany(CompanyViewModel model)
{
    if (ModelState.IsValid)
    {
        Company company = SomeCompanyViewModelHelper.
                          MapCompanyViewModelToDomainObject(model);
        companyService.UpdateCompany(company);
        return RedirectToRoute(/* Wherever you go after company is updated */);
    }
    // Return the same view with highlighted errors
    return View(model);
}

由于上述原因,最好抽象您的数据访问(可测试性,易于切换数据提供者或ORM或其他等)。 Repository模式是一个不错的选择,但在这里您还可以获得一些实现选项。关于通用/非通用存储库的讨论总是很多,无论是否应该返回IQueryable等等。但最终你可以选择。

顺便问一下,你为什么要加载延迟?通常,您确切地知道特定视图所需的数据,那么为什么要选择以延迟方式获取数据,从而进行额外的数据库调用,而不是在一次调用中急需加载所需的所有内容?就个人而言,我认为有多个Get方法来获取有或没有孩子的对象是可以的。例如。

public class CompanyRepository
{
    Get(int Id);
    Get(string name);
    GetWithEmployees(int id);
    ...
}

看起来有点矫枉过正,你可能会选择不同的方法,但只要你有一个你遵循的模式,维护代码就容易多了。

答案 1 :(得分:5)

我个人这样做:

我有一个抽象的Domain层,它不仅包含CRUD方法,还包含专门的方法,例如UsersManager.Authenticate()等。它内部使用数据访问逻辑或数据访问层抽象(取决于抽象我需要)。

至少拥有一个抽象依赖总是更好。以下是它的一些优点:

  • 您可以在以后用另一个实现替换一个实现。
  • 您可以在需要时对控制器进行单元测试。

从控制器本身开始,让它有2个构造函数:一个具有抽象域访问类(例如域的外观),另一个(空)构造函数选择默认实现。这样,您的控制器在Web应用程序运行时(调用空构造函数)和单元测试期间(注入模拟域层)就可以正常运行。

此外,为了能够在以后轻松切换到另一个域,请务必注入域创建者,而不是域本身。通过这种方式,将域图层构造本地化到域创建者,您可以随时切换到另一个实现,只需重新构建域创建者(创建者,我的意思是某种工厂)。

我希望这会有所帮助。

<强>加成

  • 我不建议在域层中使用CRUD方法,因为每当您丰富单元测试阶段时,这将成为一场噩梦,甚至更多,当您需要稍后将实现更改为新的时。 / LI>

答案 2 :(得分:0)

这真的归结为您想要代码的位置。如果您需要对对象进行数据访问,可以将其放在IRepository对象后面或控制器中并不重要:您仍将使用一系列GetByXXX调用或等效代码。无论哪种方式,您都可以延迟加载并控制连接的生命周期。所以现在你需要问问自己:我希望我的代码在哪里生效?

就个人而言,我会争辩说要把它从控制器中拿出来。我的意思是将它移到另一层。可能使用IRespository类型的模式,您可以在其中进行一系列GetByXXX调用。当然他们很难看。错误?我会反驳说。至少它们都包含在同一个逻辑层中,而不是分散在整个控制器中,它们与验证代码等混合在一起。