Entity Framework queries and MVC ViewModels

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

Question

I am new to both MVC and Entity Framework and I have a question about the right/preferred way to do this.

I have sort of been following the Nerd Dinner MVC application for how I am writing this application. I have a page that has data from a few different places. It shows details that come from a few different tables and also has a dropdown list from a lookup table.

I created a ViewModel class that contains all of this information:

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
}

In the Nerd Dinner code, their examples is a little too simplistic. The DinnerFormViewModel takes in a single entity: Dinner. Based on the Dinner it creates a SelectList for the countries based on the dinners location.

Because of the simplicity, their data access code is also pretty simple. He has a simple DinnerRepository with a method called GetDinner(). In his action methods he can do simple things like:

Dinner dinner = new Dinner();

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

OR

Dinner dinner = repository.GetDinner(id);

return View(new DinnerFormViewModel(dinner));

My query is a lot more complex than this, pulling from multiple tables...creating an anonymous type:

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

My question is as follows:

What should my repository class look like? Should the repository class return the ViewModel itself? That doesn't seem like the right way to do things, since the ViewModel sort of implies it is being used in a view. Since my query is returning an anonymous object, how do I return that from my repository so I can construct the ViewModel in my controller actions?

1
19
6/29/2012 5:40:38 AM

Accepted Answer

You are correct a repository should not return a view model. As changes to your view will cause you to change your data layer.

Your repository should be an aggregate root. If your property1, property2, Foo, Bar are related in some way I would extract a new class to handle this.

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

If Foo and Bar are not related at all it might be an option to introduce a service to compose your FooBarDetails.

FooBarDetails details = _service.GetFooBar(id);

where GetFooBar(int) would look something like this:

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

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

This all is conjecture since the design of the repository really depends on your domain. Using generic terms makes it hard to develop potential relationships between your objects.

Updated From the comment if we are dealing with an aggregate root of an Order. An order would have the OrderItem and also the customer that placed the order.

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

Your repo should return a fully hydrated order object.

var order = _rep.Get(orderId);

Since your order has all the information needed I would pass the order directly to the view model.

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

Now having a viewmodel with only one item might seem overkill (and it most likely will be at first). If you need to display more items on your view it starts to help.

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;
  }
}
8
5/23/2017 12:14:20 PM

Popular Answer

While most of the answers are good, I think they are missing an in-between lines part of your question.

First of all, there is no 100% right way to go about it, and I wouldn't get too hung up on the details of the exact pattern to use yet. As your application gets more and more developped you will start seeing what's working and what's not, and figure out how to best change it to work for you and your application. I just got done completely changing the pattern of my Asp.Net MVC backend, mostly because a lot of advice I found wasn't working for what I was trying to do.

That being said, look at your layers by what they are supposed to do. The repository layer is solely meant for adding/removing/and editing data from your data source. It doesn't know how that data is going to be used, and frankly it doesn't care. Therefore, repositories should just return your EF entities.

The part of your question that other seem to be missing is that you need an additional layer in between your controllers and the repositories, usually called the service layer or business layer. This layer contains various classes (however you want to organize them) that get called by controllers. Each of these classes will call the repository to retrieve the desired data, and then convert them into the view models that your controllers will end up using.

This service/business layer is where your business logic goes (and if you think about it, converting an entity into a view model is business logic, as it's defining how your application is actually going to use that data). This means that you don't have to call specific conversion methods or anything. The idea is you tell your service/business layer what you want to do, and it gives you business entities (view models) back, with your controllers having no knowledge of the actual database structure or how the data was retrieved.

The service layer should be the only layer that calls repository classes as well.



Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow