Arian Shahalami
Arian Shahalami

Reputation: 1449

How to have a view model which contains two models

In a view i need a view model which contains two models. I dont know how to retrieve data in the repository.

There is a Products model which has a foreign key to another model. Here is my PRODUCTS model:

 public class Products
 {
    public int ProductId {get; set;}
    public string Productname {get; set;}
    public int ProductTypeID { get; set; }

    [ForeignKey("ProductTypeID")]
    public virtual ProductTypes ProductTypes { get; set; }
 }

And this is my PRODUCTTYPE model:

 public class ProductTypes
 {
    public int ProductTypeId {get; set;}
    public int ProductTypeName {get; set;}
 }

Now I want to use them in a View, so I made a viewmodel

 public class ProductsViewModel
 {
    public Products Products { get; set; }
    public IEnumerable<ProductTypes>  ProductTypes { get; set; }

 }

Here is my problem. I don't know how to retrieve data from a viewmodel in a repository like this:

 public async Task<IEnumerable< XXX >> GetAllProducts()
 {
    return await _RepositoryContext.Set<xxx>.ToListAsync();
 } 

Finally this is my Controller:

 public async Task<IActionResult> index()
 {
    return View(await ProductRepository.GetAllProducts());
 }

Upvotes: 1

Views: 547

Answers (3)

poke
poke

Reputation: 387547

It depends on what you actually want to do within your view: A view model is supposed to contain exactly the data necessary to display the view. Usually, you would not want to include more data there. So using database models inside of a view model is not the best choice; it would be better to design actual view models that match the stuff you display, and then you decide how to properly get that data.

From how your view model looks, I could assume two use cases:

  1. You want to display a set of products, and list those product types that that set of products have.
  2. You want to display a set of products, and a list of all product types.

Option 2 is really simple and requires you to only query each entity type once:

var viewModel = new ProductsViewModel
{
    Products = await db.Products.ToListAsync(),
    ProductTypes = await db.ProductTypes.ToListAsync(),
};

Option 1 can be solved naively by including the product types through the navigation property in the Product entity:

var products = await db.Products.Include(p => p.ProductType).ToListAsync();

var viewModel = new ProductsViewModel
{
    Products = products,
    ProductTypes = products.Select(p => p.ProductType).Distinct().ToList(),
};

This however has the downside that with few distinct product types you will be loading each product type multiple times. Because your product type has only an id and a name, this is not that problematic but for more complex types it can be.

A different approach would be to query the product type ids first from the list of products, and then loading the product types afterwards:

var products = await db.Products.Include(p => p.ProductType).ToListAsync();
var productTypeIds = product.Select(p => p.ProductTypeId).Distinct().ToList();

var viewModel = new ProductsViewModel
{
    Products = products,
    ProductTypes = await db.ProductTypes.Select(t => productTypeIds.Contains(t.Id)).ToListAsync(),
};

Two notes:

  1. I ignored the existence of your repository here and just assumed that you are using an EntityFramework DbContext here. If you want to abstract that into a repository, you can do so.
  2. The entity is named Products but only contains information about a single product. Similarly, the view model contains a property Products of that exact same type. That does not make a lot of sense to me, so I just assumed that the entity is called Product and represents a single item, while the ProductsViewModel has Products, a list of Product.

Upvotes: 0

Aamir
Aamir

Reputation: 695

In your repository you use include expression like this

public async Task<IEnumerable< Products >> GetAllProducts()
 {
var products =   _RepositoryContext.Set<Products> ();
return await products.include(x =>x.ProductType).ToListAsync();
}

But I think you need to modify your models. you are returning domain classes in controller as view Model which is not a good idea, you should have separate model classes and should use Automapper to map domain to model classes so that on client side you send only model classes which may have many extra columns than domain classes . Here is how your model classes should look like

public class Products
 {
    public int ProductId {get; set;}
    public string Productname {get; set;}
    public int ProductTypeID { get; set; }

    [ForeignKey("ProductTypeID")]
    public virtual ProductTypes ProductTypes { get; set; }
 }
public class ProductTypes
 {
    public int ProductTypeId {get; set;}
    public int ProductTypeName {get; set;}
 }

Now models should be like this

public class ProductsModel
 {
    public int ProductId {get; set;}
    public string Productname {get; set;}
    public int ProductTypeID { get; set; }
    public virtual ProductTypesModel  ProductTypes { get; set; }
    public string  ProductTypeName {get; set;}
 }

public class ProductTypesModel
 {
    public int ProductTypeId {get; set;}
    public int ProductTypeName {get; set;}
 }

Now repository method should look like this

public async Task<IEnumerable< Products >> GetAllProducts()
 {
var  products = _RepositoryContext.Set<Products> ();
return await products.include(x =>x.ProductType).ToListAsync();

}

Finally this would be your controller Controller,

 public async Task<IActionResult> index()
 {
  var productList=   await ProductRepository.GetAllProducts() 
  var ProductModels =  Mapper.Map<IEnumerable<Products>, IEnumerable<ProductsModel>>( productList)
    return View(ProductModels);
 }

To Know how to setup AutoMapper please refer to my this post, How to setup Automapper in ASP.NET Core

Upvotes: 1

Amir Molaei
Amir Molaei

Reputation: 3810

It all depends on your architecture. However, Repository might not be the best place to do the mapping between models and viewmodels because once the ORM is changed the repository will possibly be modified too while this mapping could be somewhere to avoid unnecessary changes. I personally prefer to do the mapping either in BLL or in viewmodel itself without using AutoMapper. Here is one possible way of mapping in viewmodels:

public class ProductsDto
{
    public int ProductId { get; set; }
    public string Productname { get; set; }
    public virtual ProductTypeDto ProductTypes { get; set; }
    public void SetDto(Products obj)
    {
        if (obj == null)
            return;
        this.ProductId = obj.ProductId;
        this.Productname = obj.Productname;
        if (obj.ProductTypes != null)
        {
            this.ProductTypes = new ProductTypeDto();
            this.ProductTypes.SetDto(obj.City);
        }
    }
}

public class ProductTypeDto
{
    public int ProductTypeId { get; set; }
    public int ProductTypeName { get; set; }
    public void SetDto(ProductType obj)
    {
        if (obj == null)
            return;
        this.ProductTypeId = obj.ProductTypeId;
        this.ProductTypeName = obj.ProductTypeName;
    }
}

Using viewmodels:

 public ProductsDto GetProductsDto(Products obj)
    {
        var dto = new ProductsDto();
        dto.SetDto(obj);
        return dto;
    }
----------------------------------------------------------
    var products = _RepositoryContext.Products.ToList();
    var productsDto = products?.Select(GetProductsDto);

Upvotes: 0

Related Questions