I have setup a couple of objects:
Product.cs
namespace Print_Solutions.Models
{
public class Product
{
public int ID { get; set; }
public string Manufacturer { get; set; }
public string Model { get; set; }
public string PartNumber { get; set; }
public int ProductCategoryID { get; set; }
public virtual ICollection<ProductCategory> ProductCategory { get; set; }
}
}
ProductCategory.cs
namespace Print_Solutions.Models
{
public class ProductCategory
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
}
The DB Context
namespace Print_Solutions.DAL
{
public class ApplicationContext : DbContext
{
public ApplicationContext() : base("DefaultConnection")
{
}
public DbSet<ProductCategory> ProductCategories { get; set; }
public DbSet<Product> Products { get; set; }
public DbSet<ProductDetail> ProductDetails { get; set; }
public DbSet<ProductDocument> ProductDocuments { get; set; }
public DbSet<ProductImage> ProductImages { get; set; }
public DbSet<RelatedProduct> RelatedProducts { get; set; }
}
}
The Controller
namespace Print_Solutions.Controllers
{
public class DashboardController : Controller
{
private ApplicationContext db = new ApplicationContext();
public ActionResult Index()
{
ViewBag.Products = db.Products.ToList();
ViewBag.ProductCategories = db.ProductCategories.ToList();
return View();
}
}
}
The Problem
I have tried a couple of things in the view. Neither one of these seems to work. How can I access products from productcategories, and vice versa?
<ul>
#foreach(Print_Solutions.Models.ProductCategory category in #ViewBag.ProductCategories)
{
<li>#category.Name
<ul>
#foreach(var product in #category.Products)
{
<li>#product.Name</li>
}
</ul>
</li>
}
</ul>
or
<ul>
#foreach (Print_Solutions.Models.Product product in #ViewBag.Products)
{
<li>#product.Name - #product.ProductCategory.Name</li>
}
</ul>
I can access products, and product categories, but not through their related objects. What am I missing?
Your problem in the first example is that you're using the # in front of category.Products. That's going to raise a syntax error.
Your problem in the second example is that the inappropriately named ProductCategory property is actually a collection, not a single object. In other words, you can't access a property like Name directly off it. You would have to loop through this collection as well or otherwise join the values. For example, in this scenario, you'd probably just want to list all the categories the product belongs to, so you could do something like:
#string.Join(", ", product.ProductCategory.Select(m => m.Name))
Which creates an enumerable containing only the value for Name of each ProductCategory in the collection, and then joins them all together delineated by a comma and a space: Category1, Category2, Category3, ....
While I've got you, though, there's some important changes you should make:
Since these are related things, don't do two separate database queries (one for products and one for categories), instead, select one (probably the products based on your usage), and then join the other in the same query:
var products = db.Products.Include("ProductCategory").ToList();
Don't use ViewBag. Like ever. There's some times where its use may not be so bad, but until you can properly distinguish a good usage from a bad usage, it's better to just put a moratorium on the whole concept. The problem with ViewBag is that it uses dynamics; in other words, it is not evaluated at all at compile time, and either works or fails at runtime. The golden rule of software development is to always fail at compile. You want to know when you're building that something doesn't work, not when it's been deployed to your user days, weeks, months or even years after it's been developed. So, yeah, ViewBag is evil. Instead, use a strongly-typed view and pass your "model", in this case, your products into it:
Controller
var products = db.Products.Include("ProductCategory").ToList();
return View(products);
View
#model IEnumerable<Namespace.To.Product>
#foreach (var product in Model)
{
...
}
Since Product Category is a collection, you may wish to loop thru the ProductCategory List (within the Product Object) within the product loop to print them off.
Alternately, you can create a View Model which contains only what you need. (Product Name, List of ProductCategoryNames) and pass that into things.
If you intend to do a post back, you can add ProductID to this view model (or CategoryID as well for that matter) and put them into a #Html.HiddenFor() so you can reference them later as needed.
Related
I want to make a tree structured product category view. I have found this article by Ole Michelsen from 2011, which I think looks promising. The article describes a method similar to what I almost had in mind, using Razor to recursively render the tree structure.
My database table is almost identical to the one described in the article:
[ProductCategory]
Id
ParentId
Title
I'm not sure what my view model should look like. Should it be an IEnumerable of ProductCategory, or should it be an instance of ProductCategory and have an IEnumerable of ProductCategories in it?
Something like this ...
public class ViewModelProductCategory
{
public int Id { get; set; }
public int? ParentId { get; set; }
public string Title { get; set; }
public int SortOrder { get; set; }
public int NumOfProducts { get; set; }
}
... or more like this?
public class ViewModelProductCategory
{
public IEnumerable<ProductCategory> ProductCategory { get; set; }
//public int NumOfProducts { get; set; }//<--Not too sure about this property
public IEnumerable<Product> Products { get; set; }
}
If the second one is closer to the "correct" one, how can I have SortOrder and NumOfProducts (how many products are in the current category)?
And the thing I'm most confused about, is how the razor rendering comes together. How do I send the viewmodel to the recursive partial view, and how does the partial view know which item to render?
I have some razor-code, but I can guarantee that posting it will NOT make anything clearer as of what I'm trying to achieve.
This pseudo-code is perhaps more describing:
_recursivePartial.cshtml
#model [my unknown viewmodel goes here, is it IEnumerable or not?]
<ul>
foreach item in model
{
<li>#Html.Partial("_recursivePartial.cshtml", [what goes here?])
// 1: How does _recursivePartial.cshtml know where in the tree it is?
// 2: How does this structure relate to the data structure in the DB?
}
</ul>
I’m not sure there is a “correct” way of doing things, but I like to keep my database models different to the view models. I often use AutoMapper to get the data between models.
In your situation I’d probably have two View Models:
public class ProductCategoryItemModel
{
public int Id { get; set; }
public int? ParentId { get; set; }
public string Title { get; set; }
public int SortOrder { get; set; }
public int NumOfProducts { get; set; }
}
and
public class ProductCategoriesModel
{
public List<ProductCategoryItemModel> Categories { get; set; }
}
I always convert ToList() rather than passing an IEnumerable to views, but again I’m not sure that’s the “correct” way.
I am trying to create a list of custom objects in LINQ, and I am not sure how to do it. Here is my classes...
public class MenuModel
{
public IList<MenuCategoriesWithArticles> Menu { get; set; }
}
public class MenuCategoriesWithArticles
{
public Category Category { get; set; }
public IList<Article> Articles { get; set; }
}
and I would like to create MenuModel from the following functions that return Category and IList in order.
businessCategory.GetAllCategories();
businessArticle.GetArticlesByCategory(int categoryId);
I have something like below but I am not sure...
businessCategory.GetAllCategories().Select(x=> new .....)
any help would be great. I dont want to loop to get each categories' articles.
Maybe something like this could help you.
businessCategory.GetAllCategories().Select(x=> new MenuCategoriesWithArticles{
Category = x,
Articles = businessArticle.GetArticlesByCategory(x.categoryId).ToList();
});
The only thing is, if GetArticlesByCategory does a database search this code won't be optimal; if that is the case, you should query all the articles separately depending on the categories you select first.
I assume you are using Entity Framework.
The best approach for you will be to create a relationship between articles and category in your sql server, change your classes that represent table to have references to each other, and let you context know about it.
public class Category
{
public int CategoryId { get; set; }
public IList<Article> Articles { get; set; }
}
public class Article
{
public int ArticleId { get; set; }
public Category Category { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Article>().HasOne(article => article.Category)
.WithMany(category=> category.Articles);
}
When you have this changes, you can get your categories this way.
await dbContext.Category.Include("Articles").ToListAsync()
After executing, your Category object will have list of Articles. You can find more info here http://www.entityframeworktutorial.net/code-first/configure-one-to-many-relationship-in-code-first.aspx
I want to get to the product categories in a MVC project and use them to create a menu. The model resides on a WCF project so I have instantiated it as follows:
ServiceReference1.WSClient client = new ServiceReference1.WSClient();
My Product model is like this:
public class Product
{
public int ProductID { get; set; }
public string ProductName { get; set; }
public string ProductImagePath { get; set; }
public string Specifications { get; set; }
public string Options { get; set; }
public double? UnitPrice { get; set; }
public int? CategoryId { get; set; }
public Category Category { get; set; }
public int Stock { get; set; }
}
My Category model is like this:
public class Category
{
public int CategoryID { get; set; }
public string CategoryName { get; set; }
public string Despcription { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
I want to get the product categories like this:
public PartialViewResult Menu()
{
List<Product> products = client.GetAvailableProducts().ToList();
IEnumerable<string> categories = products
.Select(myproduct => myproduct.Category.CategoryName) // <- offending line
.Distinct()
.OrderBy(x => x);
return PartialView(categories);
}
The method GetAvailableProducts() works because I get a list of products so I know that the service is working. However, when run the application, I get a null reference exception at the Linq query(see offending line above).
It seems to me that categories has to be instantiated but then, how to construct the LINQ query so that the Category is also instantiated? Can anyone point out how to do it?
BR,
Gabriel
When you serialize the Products in the service you should use LoadWith to also serialize any linked entities. This is because the default is lazy loading and EF won't load the linked entities until accessed. When you serialize the Products, Category is not accessed. LoadWith will perform an eager load so that all data will be serialized.
Example:
public IEnumerable<Product> GetAvailableProducts()
{
var ctx = new ProductsContext();
DataLoadOptions dlo = new DataLoadOptions();
dlo.LoadWith<Product>(p => p.Category);
ctx.LoadOptions = dlo;
return ctx.Products.ToList();
}
Edit:
Guess it's too late in the afternoon. :(
LoadWith is used in conjuction with Linq to SQL.
With Entity Framework you should use Include instead:
public IEnumerable<Product> GetAvailableProducts()
{
var ctx = new ProductsContext();
return ctx.Products.Include("Category").ToList();
}
Disclaimer: Code not tested
You're pulling down the Products successfully but your service is not pulling down the related categories.
Thus, when you do myproduct.Category.CategoryName, Category is always null.
You need to tell the service to return the related categories.
It looks like your relationship is a 0 or 1 to many since CategoryId is an int?. If the Category is null, then you can't do Category.CategoryName. That's a null reference.
On your offending line, myproduct.Category, there is one or more null Category. You need to change
client.GetAvailableProducts().ToList();
to also pull down the Category that each product is composed of.
You should also guard for the inevitable null Category.
So my DB has a one-to-many association between a customer and orders.
Mapping the data to show a customer and his orders is no problem. But is there a way to map these when creating a customer?
For example:
"The very basic viewModel just to test the Mapping"
public class CVM
{
public string ContactName { get; set; } //Part of the Customer Table
public DateTime OrderDate { get; set; } //Part of the Orders Table and
//would have to be passed into the Orders List of the EF-Customer-Object
}
So the create view just has 2 inputs for Name and Date.
"The very basic controller just to test the Mapping ;)"
[HttpPost]
public ActionResult Create(CVM model)
{
Mapper.CreateMap<CVM, Customer>();
Customer customer = Mapper.Map<CVM, Customer>(model);
return View();
}
So ContactName gets mapped properly. The Problem is the OrderDate. AutoMapper would have to create an Order instance, set the value of OrderDate and pass it to the OrdersCollection of the Customer object. Is AutoMapper able to do this or am I totally wrong?
Hope you understand my explanation and someone has an Answer to me.
Thanks Folks
I think you are going about it the wrong way. What you should be doing is to instantiate a Customer instance and then map it s properties using AutoMapper.
Thus, your code would look like:
[HttpPost]
public ActionResult Create(CVM model)
{
Mapper.CreateMap<CVM, Customer>();
Customer customer = /* Construct or get a Customer instance, eg from DB. */
Mapper.Map<CVM, Customer>(model, customer);
return View();
}
BTW, you should make sure to have Mapper.CreateMap<CVM, Customer>() directives only during application startup, otherwise you are needlessly performing this (possibly costly) step on every request.
Edit
It seems I read the original question wrong. If the aim is to create a Customer with associated objects, then Automapper can help you in a few different ways (I am going forward with the Person/PhoneNumber example you gave in the comments).
Given that your Entities and View Models are:
public Person {
public string Name { get; set; }
public string List<PhoneNumber> Numbers { get; set; }
}
public PersonVM {
public string Name { get; set; }
public string IList<PhoneNumberVM> Numbers { get; set; }
}
public PhoneNumber {
public int Type { get; set; }
public string Number { get; set; }
}
public PhoneNumberVM {
public int Type { get; set; }
public string Number { get; set; }
}
then you have a few alternatives:
You can try to write a custom Mapping rule so that each PhoneNumberVM instance is mapped to a PhoneNumber instance, or
You can add a Mapper.CreateMap<PhoneNumberVM, PhoneNumber>() and just call Mapper.Map<PersonVM, Person>(model) to have your model mapped to your entity.
Of course, you would have to make sure that your model gets constructed properly, but that is not very hard as long as you use the same model to generate your HTML form.
I have some hierarchal data. The Model class I use looks like this:
public class Category
{
[Key]
public int CategoryID { get; set; }
[Required]
[StringLength(64)]
public string Name { get; set; }
public int? ParentCategoryID { get; set; }
[ForeignKey("ParentCategoryID")]
public Category ParentCategory { get; set; }
[Required]
public int ListOrder { get; set; }
// left/right
public int TreeLeft { get; set; }
public int TreeRight { get; set; }
} // eo class Category
I've used the techniques outlined here to store my data, and inserting and retrieving data is not a problem.
What I would like to do, is add a Category collection to this class:
public virtual IEnumerable<Category> {get; set; }
I've used this technique in the past (learned from the Mvc tutorials), to include related tables when getting data. However, when I tried this I received an error with regard to IEnumerable<> being abstract (which is understandable, I guess the framework couldn't figure out what I want to do)...
... and indeed, being new to LINQ, I have no idea what the LINQ would look like that would give me back a collection of Category instances each of which had their children inside them.
If it's not possible I guess I can construct the list manually, use a regular LINQ query to get all the categories at a particular position (and their children) and manually populate it all.
I was wondering if LINQ could do this for me?
Thanks in advance!
If you have a self reference fk than the collection should be generated automatically when you add the table to the dbml file
And will look something like this:
[global::System.Data.Linq.Mapping.AssociationAttribute(Name="Category_Category", Storage="Categories", ThisKey="pkCategoryID", OtherKey="ParentCategoryID")]
public EntitySet<Category> Categories
{
get
{
return this._Categories;
}
set
{
this._Categories.Assign(value);
}
}
Dont use IEnumerable<Category> but Collection<Category> . If that is your problem..