Calling a model within a model in a view. (asp.net MVC) - c#

Bare in mind, I'm quite new to ASP.NET MVC. So using EF code first method, I created 2 models, Movie and Producer:
public class Movie {
public int ID {get;set;}
public string Name {get;set;}
public string Genre {get;set;}
public Producer Producer {get;set;}
}
public class Producer {
public int ID {get;set;}
public string Name {get;set;}
public DateTime DOB {get;set;}
public List<Movie> Movies {get;set;}
}
And in the controller class "Movies", I called a view:
public class MoviesController : Controller
{
//context just has DbSet< .. > of both classes.
MoviesContext db = new MoviesContext();
public ActionResult Index()
{
var movies = from m in db.Movies
select m;
return View(movies.ToList());
}
}
But if I call the producer within the view
#foreach(var item in Model)
{
<p>#item.Producer.Name</p>
}
MVC crashes with "Object reference not set to an instance of an object." error, even though when I look at the database, the Producer_ID field (which Code first made) was filled in and all of the producers with the according ID's exist in the database as well.
I did put the values inside the database manually, is that what would be causing it?
Any kind of help is more than appreciated! :)

Try loading Producer data explicitly, by calling function Include()
public ActionResult Index()
{
var movies = from m in db.Movies.Include(b => b.Producer)
select m;
return View(movies.ToList());
}
If lazy loading is enabled, your property Producer would be loaded first time it is needed. However, if lazy loading is disabled, you need to explicitly specify that you want to load other related entities.
The method Include that accepts a lambda is an extension method, so you will need to add this using at the top of your file:
using System.Data.Entity;
Alternative is to specify include by using string:
public ActionResult Index()
{
var movies = from m in db.Movies.Include("Producer")
select m;
return View(movies.ToList());
}
The result is the same, but first approach does not contain "magic strings", which is often preferable.

This is wrong, just put a break point before the return and check if the movies.Producer is not null, i beat yes..
You need to Include producer like this:
var movies = from m in db.Movies.Include(m => m.Producer)
Or maybe do a join with Producer table, see a 3 table join example (in your case is just 2) here

Related

How to retrieve data from multiple tables and display in a view using viewmodel

I'm trying to develop a messeging system to my mvc application using mvc 5. I have tables called Event, EventUser, EventObject. Each of those tables have following;
Event
ID
CreatedBy
StartTime
IsShared
Budget
EventUser
EventID
UserID
IsAccepted
EventObject
EventID
ObjectID
in my messageController i have the index method which receive the parameter of the user id.i need to display every event that user has invited using this method..
namespace MvcApp.Controllers
{
public class MessageController : Controller
{
private EPlannerDatabaseEntities db = new EPlannerDatabaseEntities();
// GET: /Message/
public ActionResult Index(int UId)
{
/* linq expressions */
return View();
}
}
}
when the parameter has passed in, i want to;
*Select from EventUser table where UID=UserID and join the result with Event and EventObject tables by using EventID attribute.
*Finally by using the final result i need to display every event's infomation that user has invited; like CreatedBy , StartTime, Budget,other users,objects etc..
i'm new to mvc and viewmodel concept.I heard that viewmodel concept can help with these situations.can i overcome this problem by using viewmodel concept.if yes what are the things i need to add in view model?? otherwise what are the other ways to do this?
one way i can see of doing this is creating a custom return object and using EF to join all the tables together. Example
public class MyObject{
public DateTime DateCreated{get;set}
// add remaining properties here
// properties to get back
}
then in code you would use Entity Framework to create a joined data set into a nice list of objects. Example:
var results = (from b in bla join bla2 in (Some Second Query Here)
from SomeSecondQueryHere
where cond1 and cond2 Select new MyObject{
// add properties in here})
where you would replace the bla and bla2,etc with respective table names needed. Then all you need to do is
return View(results);
And the changes will be accessible in the View
If you question is regarding querying with an ORM like Entity Framework, you need to post your entities, not your table schemas. The whole purpose of an ORM is to abstract away the underlying database structure, so while the schema will often be similar to the entity class, it can also be quite different. As a result, I'll have to make assumptions about your entity classes.
To query everything, you just need something like the following:
var events = db.Events.Where(m =>
m.EventUsers.Any(u => u.UserID == UId && u.IsAccepted)
).Include(m => m.EventObjects);
That assumes entity classes along the lines of:
public class Event
{
...
public virtual ICollection<EventObject> EventObjects { get; set; }
public virtual ICollection<EventUser> EventUsers { get; set; }
}
public class EventUser
{
...
public int UserID { get; set; }
public bool IsAccepted { get; set; }
}
You end up with an enumerable of Event. If you need to access the EventObjects for an individual event, you have to use the appropriate collection property. For example:
foreach (var item in events)
{
foreach (var obj in item.EventObjects)
{
// do something with `obj` (an invidual `EventObject` instance)
}
}
If you need the actual User object, you're better object querying that first and including related Events and EventObjects:
var user = db.Users.Include("EventUsers.Event.EventObjects").SingleOrDefault(m => m.UserID == UId);
That assumes entities like:
public class User
{
...
public virtual ICollection<EventUser> EventUsers { get; set; }
}
public class EventUser
{
...
public virtual Event Event { get; set; }
}
public class Event
{
...
public virtual ICollection<EventObject> EventObjects { get; set; }
}
With that method, however, there's no way to filter the included Events by whether they're accepted or not. There's a potential way around that, but it requires disabling lazy-loading of EventUsers entirely and complicates querying the information you need. If you need to go that route, see: https://msdn.microsoft.com/en-us/data/jj574232.aspx#explicitFilter.
Otherwise, you can just exclude non-accepted events before iterating over the collection:
var events = user.EventUsers.Where(m => m.IsAccepted).Select(m => m.Event);
Really you don't need a view model, per se, for any of this. As you can either pass the lists of events (which will include any related EventObjects) or the the single user instance (which includes related events and related EventObjects) directly to your view.
A very high level description of how to solve your scenario using Entity Framework would be something like this:
First you've got to create a series of entity data objects that will represent your tables in the EF data model using EF Code first techniques.
Then you create DbContext objects with DbSets for your previously created entities.
Then you create at least one Service class that will have a property representing DbContext and a set of methods encapsulating Linq queries to your entities.
In the MVC controller you call an instance of Service that you previously create and assign it to a property ant Controller's construction time. Finally, in the Action method you should call the correct Service method and pass any result to the view.
( I am assuming this is a small Ad-Hoc system with a handful of tables , an elaborate System with production quality would require using IoC techniques).

viewmodel and scaffolding migration

I am trying to create a multiple options select for my dropdown menu. I am able to get the listbox working fine, however, I am trying to store the selected items so I tried using a viewmodel to store it as a collection, however, my .cshtml file was already calling the db model, so I couldn't include the viewmodel in the .cshtml file since a view can only have one model.... also even when I tried just calling the viewmodel in my .cshtml using other means, it kept saying the collection was null exception error...
So following this wonderful youtube video tutorial I decided to bring the ICollection to my existing model like this:
public virtual ICollection<Country> Countries{ get; set; }
Everything was smooth until it said, i should do a database migration because the model has changed.
Due to security reasons, I need to send any update queries to the sysadmin to run any table changes against the database, so my question is, what is really happening when I use
public virtual ICollection<Country> Countries{ get; set; }
What is changing in my existing table? What am I altering? And if nothing is changing, then how come I need to do a db migration for this to take effect? Each time I run update-database in the package manager console, I get an error which says I don't have permissions (an error I am familiar with, which requires me to send table changes to the sysadmin)
Any ideas where to store my collection, in the viewmodel or the model. The youtube example shows in the model?
When you add a property like:
public virtual ICollection<Country> Countries{ get; set; }
Then a foreign key needs to be added on the table backing Country to point to the model that has this property.
I'm not sure what problem exactly you're having using view models, but it's pretty straight-forward. Just add whatever properties you need to edit from your model to the view model and then make the view model the model for the view.
If you got an error saying a collection is null, then you need to initialize the collection. Again, this is all pretty basic stuff here. You can either do it in the constructor of your view model, manually in your action, etc. Whatever makes the most sense for your application.
Then, for a select list, you must remember that the value that will be posted is going to be a scalar type: string, int, etc. You can't directly post to something that expects a class like Country because the modelbinder will have no idea how to instantiate Country based on the posted value. Usually, what you would do is use the Id as the value of the option, and then in your post action, use the id values to look up the actual objects from the database, before finally attaching those to your model.
View Model
public class FooViewModel
{
public FooViewModel()
{
SelectedCountryIds = new List<int>();
}
...
public List<int> SelectedCountryIds { get; set; }
public IEnumerable<SelectListItem> CountryChoices { get; set; }
}
Controller
private void PopulateCountryChoices(FooViewModel model)
{
model.CountryChoices = db.Countries.Select(m => new SelectListItem
{
Value = m.Id.ToString(),
Text = m.Name
});
}
public ActionResult Foo()
{
var model = new FooViewModel();
PopulateCountryChoices(model);
return View(model);
}
[HttpPost]
public ActionResult Foo(FooViewModel model)
{
if (ModelState.IsValid)
{
var foo = new Foo
{
// map properties from view model
Countries = db.Countries.Where(m => model.SelectedCountryIds.Contains(m.Id))
}
db.Foos.Add(foo);
db.SaveChanges();
return RedirecToAction("Index");
}
// Posted form has errors
PopulateCountryChoices(model);
return View(model);
}
View
#model Namespace.To.FooViewModel
...
#Html.ListBoxFor(m => m.SelectedCountryIds, Model.CountryChoices)

Does every property of an entity need to be included in an update

I have an entity "Rep"...
public partial class rep
{
public string repid { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Title { get; set; }
public string Department { get; set; }
many, many more properties...
}
I then have a form that updates only the rep's first and last name. When the form is submitted, the first and last name are updated, but every other property is set to null.
I want the other properties to be left alone so they retain there existing values.
I could include all of the properties of the entity with hidden form fields to pass their values along to the Edit method, but is there a better/easier way?
I'm familiar with setting the IsModified property...
entry.Property(e => e.Title).IsModified = true;
But that seems unnecessary as well. Is this the way to go, or is there a better way?
The code you use to render the view and update the entity are quite relevant but missing, but I can guess what they look like:
#model ...rep
#using (Html.BeginForm()) {
#Html.HiddenFor(m => m.repid )
#Html.TextBoxFor(m => m.FirstName )
#Html.TextBoxFor(m => m.LastName)
<input type="submit" />
}
And in your controller:
[HttpPost]
public ActionResult Update(rep model)
{
using (var context = new MyContext())
{
context.reps.Attach(model);
context.SaveChanges();
}
return RTA(...);
}
This is attaching the entity that was posted, which is an entirely new object that is unknown to the context and its change tracker. By attaching an entity like that, you tell the context this is the new representation you want saved, i.e. with most properties set to null.
You can easily fix this by first retrieving the entity and then updating only the required properties:
[HttpPost]
public ActionResult Update(rep model)
{
using (var context = new MyContext())
{
var entity = context.reps.Find(rep.repid);
entity.FirstName = model.FirstName;
entity.LastName = model.LastName;
context.SaveChanges();
}
return RTA(...);
}
But don't do this. See What is ViewModel in MVC? or ViewModel Best Practices or search the web or this site on "mvc viewmodel".
Mainly because your database entity contains columns you don't want displayed or updated (mass assignment) and because sooner or later you will want to use extra properties in your view (dropdown data, script variables, related entities) that you can't stick in your database entities.
Genarally you can say: don't use entity models for view models, especially not when they are posted back. So the viewmodel is the way to go:
public class RepresentativeViewModel
{
public string RepID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
You then only have to alter your view's model declaration:
#model ...RepresentativeViewModel
And your controller just looks like the easiest solution:
[HttpPost]
public ActionResult Update(RepresentativeViewModel model)
{
using (var context = new MyContext())
{
var entity = context.reps.Find(rep.RepID);
entity.FirstName = model.FirstName;
entity.LastName = model.LastName;
context.SaveChanges();
}
return RTA(...);
}
You can of course replace the entity.FirstName = model.FirstName; mapping code into some automated solution, for example using AutoMapper.
You either use the IsModified property like you mentioned or you can create a view model that has the properties you want to change and use a mapper, like AutoMapper, to copy the values from the input to the entity. Typically I have a base class that has my modifiable data and a subclass that has the relationships and data I don't want to change like CreatedBy and stuff. Hidden fields is defnitely NOT the way to go.
You have two methods you could try
Create a view model with only the properties you want to update.
Store the values I the other properties in hidden fields they will not be returned null

MVC dbContext find parent record when current record has no elements

I'm a MVC/C# beginner so go easy.
I have an existing database where a Customer has zero or many Projects. I've built an ASP.Net project from ADODB Entity data model and dbContext code generators.
I have a customer: Joe Bloggs (ID=7). I click on the 'Projects' link for Joe Bloggs in my form to see his projects. He doesn't have any. I want to create a project for him so I call the Create action on the project controller.
I need to pass Joe Bloggs ID to the Create action for three reasons (which may not be necessary - please enlighten me if need be)
Because this is a project for Joe Bloggs I need to pass ID=7 into the controller so I can set the default Customer ID for him when generating the model data for the create view
When I hit the Cancel button on the Create view, I want to go back to the originally filtered view of projects based on Joe Blogs
When I hit save I want to got back to the originally filtered view of projects based on Joe Bloggs.
Anyway if I do have one or more projects for Joe Bloggs then I can use this monster below to access his id from the model data within the view:
<input type="button" title = "New Project" value="New Project" onclick="location.href='#Url.Action("Create", new { id = Model.First().Customer_ID })'" />
Here is the issue: if he doesn't have any projects, Model.First() doesn't return anything so I can't find the 'parent' Customer_ID record.
I used dbContext to generate the model classes (this is a data-first development). Something tells me I could extend these classes or create a new class to allow for the case above.
My current workaround is to use ViewBag to pass the various id's and strings from viewer > controller like a hot potato but this means if get three deep (Customer > Proejct > Task), and I want to display the customer name on the task, I've passed it twice. It smells.
I notice that the drop down in the Project Index view has the Customer in it. Here is my code for that:
#Html.DropDownList("Customer_ID", null, "Select a Customer to Filter", new { #onchange = "this.form.submit();" })
I might be able to hack it out of there but really I want to be able to traverse up from a class which might be two or three deep and far removed from this drop down
Here is an abridged Customer and CustomerProject class generated from dbContext
public partial class Customer
{
public Customer()
{
this.Tasks = new HashSet<Task>();
this.CustomerProjects = new HashSet<CustomerProject>();
}
public int Customer_ID { get; set; }
public string Customer_Name { get; set; }
public virtual ICollection<CustomerProject> CustomerProjects { get; set; }
}
public partial class CustomerProject
{
public CustomerProject()
{
this.Tasks = new HashSet<Task>();
this.CustomerProjectTasks = new HashSet<CustomerProjectTask>();
}
public int CustomerProject_ID { get; set; }
public int Customer_ID { get; set; }
public string Project_Name { get; set; }
public virtual ICollection<Task> Tasks { get; set; }
public virtual Customer Customer { get; set; }
public virtual ICollection<CustomerProjectTask> CustomerProjectTasks { get; set; }
}
I'm sure there is an obvious solution but my strength is in databases and VB6, not C#
At Mystere Man's suggestion I have built a ViewModel class, but I'm having a little trouble:
Here is what's in my model class file (as I understand it this is just a wrapper around the the existing project entity):
namespace zzz.Models
{
using System;
using System.Collections.Generic;
public class ProjectsViewModel
{
public int Customer_ID { get; set; }
public ICollection<CustomerProject> CustomerProjects;
}
}
Here's whats in the Index action of my controller (I havejust added my existing Project collection to the new ViewModel class):
public ViewResult Index(int pCustomer_ID = 0, string pProjectName_Filter = "")
{
// Set up drop down
ViewBag.Customer_ID = new SelectList(db.Customers.OrderBy(x => x.Customer_Name), "Customer_ID", "Customer_Name");
//ViewBag.Selected_Customer_ID = Customer_ID;
// If no parameters entered, show nothing
// Otherwise optionally filter each parameter
var projects = from p in db.CustomerProjects
orderby p.Active, p.Project_Order
where
(p.Project_Name.Contains(pProjectName_Filter) || pProjectName_Filter.Equals("")) &&
(p.Customer_ID == pCustomer_ID || pCustomer_ID.Equals(0)) &&
!(pCustomer_ID.Equals(0) && pProjectName_Filter.Equals(""))
select p;
var customerprojects = new ProjectsViewModel
{
Customer_ID = pCustomer_ID,
CustomerProjects = projects.ToList()
};
return View(customerprojects);
}
Here's an extract from my view (I have tried to iterate through the Project collection within the ViewModel class):
#model IEnumerable<BistechPortal.Models.ProjectsViewModel>
<table>
#foreach (var item in Model.CustomerProjects)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Project_Name)
</td>
</tr>
}
</table>
When I run the Index action, on the foreach line I get:
'System.Collections.Generic.IEnumerable<zzzzPortal.Models.ProjectsViewModel>' does not contain a definition for 'CustomerProjects' "
Please translate - why can't it find my 'CustomerProjects' collection inside my ViewModel?
This is why you should not pass entity objects directly to the view. In most cases, the view needs more information (or different information) than what the defined entity provides.
This is why View Models exist. You would create a View Model that contains the Customer_ID and the Projects (and any other data you need). Then, you can access the Customer_ID even if the user has no projects.
public class ProjectsViewModel {
public int Customer_ID {get;set;}
public List<CustomerProject> CustomerProjects;
}
Then you pass this new object to your view.
(added by the OP:)
To pass this object to your view you need to tell the view that it is no longer accepting data of type CustomerProjects, and (this took me three hours to work out) that it is no longer getting 'enumerable' data.
So in your view change
#model IEnumerable<Portal.Models.CustomerProjects>
to
#model Portal.Models.ProjectsViewModel
Also in your view you now want to iterate over Model.CustomerProject, so change
#foreach (var item in Model)
to
#foreach (var item in Model.CustomerProjects)

Is it correct to have 2 methods from my repository running in the same controller ActionResult?

I am creating a store with ASP.NET 4.0 MVC and C# and am fairly new to it.
I have come to creating the View page that displays the products within a certain category.
On the specific category page I want to have the product list and also want the category name with its relevant description taken from the database.
Currently the way I have done this is to have two methods in my Repository:
Retrieve the list of products for a specific category with a string
Retrieve the specific category with a string
I then use both these in one ActionResult and then pass them to the view.
Is there a way that I can retrieve both the product list and category name, description etc from 1 method call to the database or have i done it correctly?
Thank you for any help in advance.
My code is as follows:
StoreRepository
public class StoreRepository : topsports.Models.IStoreRepository
{
private topsportsEntities db = new topsportsEntities();
public IQueryable<Product> FindProductsByCategory(string c)
{
var products = from p in db.Products
where p.Category.Name == c
select p;
return products;
}
public Category FindCategory(string c)
{
return db.Categories.SingleOrDefault(cg => cg.Name == c);
}
}
IStoreRepository
public interface IStoreRepository
{
IQueryable<Product> FindProductsByCategory(string c);
Category FindCategory(string c);
}
StoreController
public class StoreController : Controller
{
IStoreRepository storeRepository;
public StoreController()
: this(new StoreRepository())
{
}
public StoreController(IStoreRepository repository)
{
storeRepository = repository;
}
public ActionResult Index(string c)
{
var category = storeRepository.FindCategory(c);
var products = storeRepository.FindProductsByCategory(c).ToList();
var viewModel = new StoreViewModel
{
Products = products,
Category = category
};
return View(viewModel);
}
}
StoreViewModel
public class StoreViewModel
{
public List<Product> Products { get; set; }
public Category Category { get; set; }
}
Category.aspx
<h2><%: Model.Category.Name %></h2>
<p><%: Model.Category.Description %></p>
<ul>
<% foreach (var item in Model.Products) { %>
<li>
<%: item.Name %>,
<strong><%: item.Description %></strong>
</li>
<%} %>
</ul>
The purpose of repositories is to decouple your data access layer from your business logic. When you choose to retrieve products through the category entity, you're depending on lazy loading which is an implementation detail of the entity framework. When you would e.g. later on decide to switch to a different data access layer (hand created queries e.g.), it could be you would not have this facility anymore.
A second issue is that when you put a lot of functionality into a single repository method, it becomes unclear what the responsibility of this method is. As #Andrew Barber describes, yes, you will get a lot of small methods. These can then be combined to produce useful functionality. When you would choose to create bigger methods that return more results, you get another problem. When the method that returns e.g. three or four data sets, you get the issues that when you need only on or two of two of these data sets, you either are going to create a new method which does less as the original one, or you are going to run four queries where one or two would have been enough.
The small methods of the repository are meant to produce meaningful results when composed in a larger whole. Many methods is not necessarily a problem. Your code looks fine :).
It might seem like you don't need to get the Category object separately, since you could reference it from one of the items. However, I think it could be good to do it like you have because, for instance, your method will cover the case where a Category does not have any items in it at the moment, for whatever reason.

Categories

Resources