MVC ViewModels和Entity Framework查询

时间:2011-02-22 14:41:36

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

我是MVC和Entity Framework的新手,我对正确/首选的方法有疑问。

我一直在关注Nerd Dinner MVC应用程序,以了解我如何撰写此应用程序。我有一个页面,其中包含来自几个不同地方的数据。它显示来自几个不同表的详细信息,并且还有一个查找表中的下拉列表。

我创建了一个包含所有这些信息的ViewModel类:

class DetailsViewModel {
    public List<Foo> DropdownListData { get; set; }

    // comes from table 1
    public string Property1 { get; set; } 
    public string Property2 { get; set; }

    public Bar SomeBarObject { get; set; } // comes from table 2
}

在Nerd Dinner代码中,他们的例子有点过于简单化了。 DinnerFormViewModel采用单个实体:Dinner。根据晚餐,它根据晚餐地点为各国创建一个SelectList。

由于简单,它们的数据访问代码也非常简单。他有一个简单的DinnerRepository,其方法名为GetDinner()。在他的行动方法中,他可以做一些简单的事情,如:

Dinner dinner = new Dinner();

// return the view model
return View(new DinnerFormViewModel(dinner));

OR

Dinner dinner = repository.GetDinner(id);

return View(new DinnerFormViewModel(dinner));

我的查询比这复杂得多,从多个表中提取...创建一个匿名类型:

var query = from a in ctx.Table1
            where a.Id == id
            select new { a.Property1, a.Property2, a.Foo, a.Bar };

我的问题如下:

我的存储库类应该是什么样的?存储库类是否应该返回ViewModel本身?这似乎不是正确的做事方式,因为ViewModel意味着它正在视图中使用。由于我的查询返回一个匿名对象,我如何从我的存储库返回它,以便我可以在我的控制器操作中构造ViewModel?

5 个答案:

答案 0 :(得分:10)

虽然大多数答案都很好,但我认为他们错过了问题的中间部分。

首先,没有100%正确的方法可以解决这个问题,而且我也不会太熟悉使用的确切模式的细节。随着您的应用程序得到越来越多的开发,您将开始了解哪些工作正常,哪些工作正常,并找出如何最好地将其更改为适合您和您的应用程序。我刚刚完成了我的Asp.Net MVC后端模式的改变,主要是因为我发现很多建议都不符合我的目的。

话虽如此,请按照他们应该做的事情来看待你的图层。存储库层仅用于添加/删除/编辑数据源中的数据。它不知道如何使用这些数据,坦率地说它并不关心。因此,存储库应该只返回您的EF实体。

您的问题中另一个似乎缺少的部分是您需要在控制器和存储库之间添加一个额外的层,通常称为服务层或业务层。该层包含由控制器调用的各种类(但是您希望组织它们)。这些类中的每一个都将调用存储库来检索所需的数据,然后将它们转换为控制器最终将使用的视图模型。

此服务/业务层是您的业务逻辑所在(如果您考虑它,将实体转换为视图模型业务逻辑,因为它定义了您的应用程序实际上的方式使用该数据)。这意味着您不必调用特定的转换方法或任何东西。我们的想法是告诉您的服务/业务层您想要做什么,它会为您提供业务实体(视图模型),您的控制器不了解实际的数据库结构或如何检索数据。

服务层也应该是调用存储库类的唯一层。

答案 1 :(得分:8)

您是正确的,存储库不应返回视图模型。由于对视图的更改将导致您更改数据层。

您的存储库应该是aggregate root。如果你的property1,property2,Foo,Bar以某种方式相关,我会提取一个新类来处理这个问题。

public class FooBarDetails
{
   public string Property1 {get;set;}
   public string Property2 {get;set;}
   public Foo Foo {get;set;}
   public Bar Bar {get;set;}
}

var details = _repo.GetDetails(detailId);

如果Foo和Bar根本不相关,可能会选择引入服务来组成你的FooBarDetails。

FooBarDetails details = _service.GetFooBar(id);

其中GetFooBar(int)看起来像这样:

_fooRepo.Get(id);
_barRepo.Get(id);

return new FooBarDetails{Foo = foo, Bar = bar, Property1 = "something", Property2 = "something else"};

这一切都是猜想,因为存储库的设计实际上取决于您的域。使用通用术语很难在对象之间建立潜在的关系。

<强>更新 如果我们正在处理订单的聚合根,请从评论中获取。订单将包含OrderItem以及下订单的客户。

public class Order
{
    public List<OrderItem> Items{get; private set;}
    public Customer OrderedBy {get; private set;}
    //Other stuff
}

public class Customer
{
  public List<Orders> Orders{get;set;}
}

你的回购应该返回一个完全水合的订单对象。

var order = _rep.Get(orderId);

由于您的订单包含所需的所有信息,我会将订单直接传递给视图模型。

public class OrderDetailsViewModel
{
  public Order Order {get;set;}
  public OrderDetailsViewModel(Order order)
  {
    Order = order;
  }
}

现在拥有一个只有一个项目的视图模型可能看起来有点过分(而且最有可能是最初的)。如果您需要在视图上显示更多项目,它将开始提供帮助。

public class OrderDetailsViewModel
{
  public Order Order {get;set;}
  public List<Order> SimilarOrders {get;set;}
  public OrderDetailsViewModel(Order order, List<Order> similarOrders)
  {
    Order = order;
    SimilarOrders = similarOrders;
  }
}

答案 2 :(得分:2)

存储库应该只适用于非匿名类型的模型,它应该只实现CRUD操作。如果您需要一些过滤,可以为此添加服务层。

对于ViewModel和Models之间的映射,您可以使用任何映射库,例如Automapper

答案 3 :(得分:2)

目前的答案非常好。我只想指出你在滥用匿名类型;它们只应用于中间传输步骤,并且永远不会传递到代码中的其他位置(例如视图模型构造函数)。

我的方法是将视图模型注入所有相关的模型类。例如。动作方法可能如下所示:

var dinner = dinnerRepository.Get(dinnerId);
var bar = barRepository.Get(barId);

var viewModel = new DinnerAndBarFormViewModel(dinner, bar);
return View(viewModel);

答案 4 :(得分:-1)

我对海报有同样的疑问,但我仍然不相信。我个人不太喜欢将存储库限制为仅执行基本CRUD操作的给定建议。恕我直言,在开发一个真正的应用程序时应始终考虑性能,并用一个SQL外部联接替换两个不同的主 - 细节关系查询对我来说听起来不太好。 此外,这种方式只需要查询所需字段的原则完全丢失:使用这种方法,我们不得不总是检索所有相关表格的所有字段,这在非玩具应用程序中非常疯狂!