ASP MVC - Reading Related Data - asp.net-mvc

Is there a faster/better way to do this?
I have here a simple one to many relationship.
public class Professor
{
public int Id { get; set; }
public string Name { get; set; }
public virtual IEnumerable<Subject> Subjects { get; set; }
}
public class Subject
{
public int Id { get; set; }
public string Name { get; set; }
public int ProfessorId { get; set; }
public virtual Professor Professor { get; set; }
}
Implementation
////////////////////////////////////
public ActionResult Index()
{
TestDBContext db = new TestDBContext();
var profs = db.Professors.ToList();
var subjs = db.Subjects.ToList();
var vm = new ProfStudVM()
{
Professors = profs,
Subjects = subjs
};
return View(vm);
}
View - Loading subject for each professor
<div>
#foreach (var prof in Model.Professors)
{
<p>#prof.Name</p>
foreach (var subj in Model.Subjects)
{
if (subj.ProfessorId == prof.Id)
{
<span>#subj.Name , </span>
}
}
<hr />
}
</div>

Suppose that you are using Entity Framework, and there is a one-many relationship from Professor to Subject.
Instead of loading all Subject, we only need to load some subjects which have their professor, using eager loading. Your code will be:
public ActionResult Index()
{
TestDBContext db = new TestDBContext();
// Remember to add using System.Data.Entity
var profs = db.Professors.Include(x => x.Subjects).ToList();
return View(profs);
}
Then, in the view, you just do like this:
<div>
#foreach (var prof in Model)
{
<p>#prof.Name</p>
foreach (var subj in prof.Subjects)
{
<span>#subj.Name , </span>
}
<hr />
}
</div>
The code is not tested, but I believe it works well.

Ad-hoc query does not compile and haven't Execution Plan so when you use Indexed View you gain the performance better than linq query that join tables on the fly without index and execution plan. but this usefull when you have huge data and or load.
in other hand you can use Automapper to flatten complex objects
like this https://github.com/AutoMapper/AutoMapper/wiki/Flattening
to simplify mapping ViewModel to Model And Vise Versa.
I have used Automapper in some of my projects and I've been able to simplify my projects.
you must trade off based on your needs.

Related

repeater foreach in asp.net mvc

I'm building a website in ASP.Net, using MVC, and need to list a set of results
but i get error in the code
model:
public class Customers
{
public int Id { get; set; }
public string Name { get; set; }
public List<Customers> Itemlst { get; set; }
}
controller:
public ActionResult List()
{
Customers itemobj = new Customers();
return View(itemobj);
}
view:
#foreach(var item in Model.Itemlst)
{
<tr>
<td>Items ID:</td>
<td>#item.ID</td>
<td>Items Name:</td>
<td>#item.Name</td>
</tr>
}
</table>
From the NullReferenceException that you are receiving we can see that the issue is because of the Itemlst not being initialised. One of the ways to solve this is just to make sure that there is a valid list when you create the object:
public class Customers
{
public Customers()
{
Itemlst = new List<Customers>();
}
public int Id { get; set; }
public string Name { get; set; }
public List<Customers> Itemlst { get; set; }
}
So you can add values to the list in your action if need:
public ActionResult List()
{
Customers itemobj = new Customers();
var example = new Customers ();
example.Id = 1;
example.Name = "Example";
itemobj.Add();
return View(itemobj);
}
I don't know if you are just using this as an example for your question, but I can't help but notice that there is something weird. You could use something different like:
public class ViewModel // Name to what makes sense to you
{
// Some other properties...
public List<Customer> Customers { get; set; }
}
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
}
Or you could just use List<Customer> as your model in the view directly (yes, your model can be a object which is simply a list of objects).
When you pass the Customers list to the view, this list itself is the model.
Change Model.Itemlst —> Model inside the foreach loop.
This will iterate the list of customers.

MVC passing object from model through controller to view

I don't have much experience with programming and I'm new to MVC.
I want to fetch some data from database with entity framework and print it in the view.
This is my model:
public class Grad
{
public int ID { get; set; }
public string Naziv { get; set; }
public char KoordinataX { get; set; }
public char KoordinataY { get; set; }
public int BrojStanovnika { get; set; }
}
public class GradDBContext : DbContext
{
public DbSet<Grad> Gradovi { get; set; }
}
this is a controller:
private GradDBContext db = new GradDBContext();
public ActionResult Index()
{
List<int> gradoviList = new List<int>();
foreach (sea.Models.Grad g in db.Gradovi)
{
gradoviList.Add(g.ID);
}
ViewData["Gradovi"] = new SelectList(gradoviList);
return View();
}
and this is a view:
#foreach (var item in ViewData["Gradovi"] as IEnumerable<int>) ---> error appears here as null reference exception
{
<p>item</p>
}
I know that I have to parse data but don't have idea what did I do wrong
The ViewData item with the key "Gradovi" is typeof SelectList, so it would need to be
#foreach (var item in ViewData["Gradovi"] as SelectList)
{
<p>#item.Value</p> // or #item.Text
However there is no point generating IEnumerable<SelectListItem> (which is what SelectList is) when you do not need it, and you should be passing your model to the view. Your code in the controller should be
public ActionResult Index()
{
IEnumerable<int> model = db.Gradovi.Select(x => x.ID);
return View(model);
}
and in the view
#model IEnumerable<int>
#foreach(var item in Model)
{
<p>#item</p>
}
Your code can work like you have it, but I am going to modify it a bit and give you some pointers. I am supplying an answer based on what I see in your post, not what I think you want to achieve at a later stage. There are many ways to accomplish a goal, I will select the simplest way that I will normally use:
public ActionResult Index()
{
// You will have a repository layer for this part
GradDBContext db = new GradDBContext();
// Get a list of your items
List<Grad> gradovis = db.Gradovi.ToList();
// I never work with view data, I just pass my view model to the view
// This way you now have more data to display on the screen (if you need more)
return View(gradovis);
}
And then your view could look like this:
#model List<Project.Model.Grad>
#foreach (var grad in Model)
{
<p>#grad.ID</p>
}

Querying database but getting null back

I'm trying to list the items from my database into my view but I'm getting null back.
I know the connection must be working to a certain extent because in my database the tables didn't exist but once I ran my program it did create the tables. However when I add content into my table my view still returns NULL.
Also, haven't touched the Review table yet, just worried about getting Restaurants working.
Restaurant.cs
namespace OdeToFood.Models
{
public class Restaurant
{
public int Id { get; set; }
public string Name { get; set; }
public string City { get; set; }
public string Country { get; set; }
public ICollection<RestaurantReview> Reviews { get; set; }
}
}
OdeToFood.cs
namespace OdeToFood.Models
{
public class OdeToFoodDb : DbContext
{
public DbSet<Restaurant> Restaurants { get; set; }
public DbSet<RestaurantReview> Reviews { get; set; }
}
}
Controller
OdeToFoodDb _db = new OdeToFoodDb();
public ActionResult Index()
{
var model = _db.Restaurants.ToList();
return View();
}
Index.cshtml
#model IEnumerable<OdeToFood.Models.Restaurant>
#{
ViewBag.Title = "Home Page";
}
#{
if (Model != null)
{
foreach (var item in Model)
{
<div>
<h4>#item.Name</h4>
<div>#item.City, #item.Country</div>
<hr />
</div>
}
}
else
{
<h1>Null</h1>
}
}
You need to pass to model back to the view.
OdeToFoodDb _db = new OdeToFoodDb();
public ActionResult Index()
{
var model = _db.Restaurants.ToList();
return View(model);
}
You never actually send the model to the view. Pass it as an argument:
OdeToFoodDb _db = new OdeToFoodDb();
public ActionResult Index()
{
var model = _db.Restaurants.ToList();
return View(model);
}
Additionally, it's generally a good idea not to create database contexts in a shared scope. Keep the context as close to where it's used as possible and only expand its scope when you really need to. Something like this:
public ActionResult Index()
{
using (var _db = new OdeToFoodDb())
{
var model = _db.Restaurants.ToList();
return View(model);
}
}
Database contexts/connections in a shared scope is just asking for problems unless you pay close attention to what you're doing. As the code gets more complex, it becomes more likely that other methods will try to use it and it may be in an unknown state at that time.

EF and CheckboxFor

Basically I want a view which contains an Owner's details. The Owner can have multiple types of Vehicle's (or none), which can be selected with a Checkbox:
On submit, the database has to be updated, because next time the Owner is displayed in the View, the Owner must have the correct checkboxes checked.
I am unsure how to structure my EF Models, and not sure I am linking it correctly to get the result I want.
I do not want hard-code the Vehicle types as fields into the Owner object, because there are quiete a huge number of vehicles.
Can anyone point me in the right direction of how to link these models?
Will I need two or three tables in the database?
This is what I have at the moment, but it is probably wrong. If you have an idea please stop reading now to avoid confusion.
Any comments are welcome!
The Models :
public class Vehicle
{
public int VehicleID { get; set; }
public string Name { get; set; }
}
public class Owner
{
public int OwnerID { get; set; }
public string Name { get; set; }
public virtual ICollection<OwnerVehicle> OwnerVehicles { get; set; }
}
public class OwnerVehicle
{
public int OwnerVehicleID { get; set; }
public bool Ticked { get; set; }
//Not sure if this is needed, because ticked will always be true
//I delete OwnerVehicle if not needed
public int OwnerID { get; set; }
public virtual Owner Owner { get; set; }
public int VehicleID { get; set; }
public virtual Vehicle Vehicle { get; set; }
}
Controller :
public ActionResult Index()
{
//prepopulate Owner objects on the fly for this example, in my project it would fetched/created with EF into database
Owner owner = getOwner();
return View(owner); // we return the owner to view
}
public Owner getOwner()
{
//Create a owner
Owner owner = new Owner() { OwnerID = 1, Name = "JACK" };
//Create a list of vehicles
List<Vehicle> Vehicles = new List<Vehicle>();
Vehicles.Add(new Vehicle() { VehicleID = 1, Name = "I have a car"});
Vehicles.Add(new Vehicle() {VehicleID = 1, Name = "I have a bike" });
//the owner doesnt have any vehicles yet, therefor object OwnerVehicle is null at the moment
return owner;
}
[HttpPost]
public ActionResult Index(Owner owner)
{
//at this point, the owner needs have his list of Vehicles linked, and written to database
//
//ToDO
//return View();
}
The View below wont compile, because I am lost.
Index.cshtml
#model Owner
#using (Html.BeginForm())
{
<div class="editor-field">
#Html.EditorFor(model => model.OwnerVehicles)
</div>
}
EditorTemplates/OwnerVehicles.cshtml
#model OwnerVehicle
<div>
#Html.CheckBoxFor(x => x.Ticked)
#Html.LabelFor(x => x.TODO, Model.TODO)
#Html.HiddenFor(x => x.TODO)
</div>
I would create an object that would help in presenting what you are trying to output. It would be in the "model", but wouldn't be mapped to the database in any way. It would look something like this:
public class VehicleHelper //or whatever you would like to call it...
{
public Vehicle Vehicle { get; set; }
public bool Ticked { get; set; }
}
Then your Owner would have a list of these VehicleHelpers like so:
public class Owner
{
//include the properties you listed here...
[NotMapped]
public IEnumerable<VehicleHelper> VehicleHelpers { get; set; }
}
Then the Controller would populate the VehicleHelpers property as needed:
public ActionResult Edit(int id)
{
MyContext db = new MyContext();
Owner owner = db.Owners.FirstOrDefault(o => o.OwnerID == id);
if (owner == null) thrown new Exception("Invalid Owner ID");
owner.VehicleHelpers = db.Vehicles
.Select(v => new VehicleHelper() { Vehicle = v, Ticket = false });
foreach (Vehicle ownedVehicle in owner.OwnerVehicles.Select(ov => ov.Vehicle))
{
VehicleHelper helper = owner.VehicleHelpers
.FirstOrDefault(vh => vh.Vehicle == ownedVehicle);
if (helper == null) continue;
helper.Ticked = true;
}
return View(owner);
}
[Note: This following part is where I'm not certain the exact syntax, conventions and such, because I'm just learning MVC and Razer myself, but it should be close. Feel free to edit or suggest edits. ;)]
Then you would have an editor template for the VehicleHelper, which would look something like this:
#model VehicleHelper
<div>
#Html.CheckBoxFor(vh => vh.Ticked)
#model.Vehicle.Name
#Html.HiddenFor(vh => vh.Vehicle.VehicleID)
</div>
I hope that at least helps point you in the right direction. ;)
CptRobby helped me to crawl the web even deeper, and found my question to be a duplicate of this one
Ciaran Bruen created a great tutorial which can be found here, and there is a downloadable solution on the left of the page.
It shows how to create the many to many relationship, the checkboxfor and updating the database.

ASP MVC many-to-many relationship - retrieve data from associated table

I am trying to create a database with a many-to-many relationship using EF code first.
public class Item
{
public int ItemId { get; set; }
public String Description { get; set; }
public ICollection<Tag> Tags { get; set; }
public Item()
{
Tags = new HashSet<Tag>();
}
}
public class Tag
{
public int TagId { get; set; }
public String Text { get; set; }
public ICollection<Item> Presentations { get; set; }
public Tag()
{
Presentations = new HashSet<Item>();
}
}
public class ItemsEntities : DbContext
{
public DbSet<Item> Items { get; set; }
public DbSet<Tag> Tags { get; set; }
}
After that I'm adding an Item to the database
var tag = new Tag { Text = "tag1" };
var item = new Item
{
Description = "description1",
Tags = new List<Tag>()
};
item.Tags.Add(tag);
using (var db = new ItemsEntities())
{
db.Items.Add(item);
db.SaveChanges();
}
The problem is that I can't output items with their associated tags. The controller looks like this:
public ActionResult Index()
{
ItemsEntities db = new ItemsEntities();
return View(db.Items.ToList());
}
and the view page has the following code:
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(model => item.Description)
</td>
<td>
#foreach (var tag in item.Tags)
{
#tag.Text
}
</td>
</tr>
}
I expect the table to contain "description1" and "tag1" but I get only "description1". I really don't understand where the problem is. What is the correct way to do this?
Your navigation properties need to be marked virtual.
public class Item
{
public int ItemId { get; set; }
public String Description { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
public Item()
{
Tags = new HashSet<Tag>();
}
}
public class Tag
{
public int TagId { get; set; }
public String Text { get; set; }
public virtual ICollection<Item> Presentations { get; set; }
public Tag()
{
Presentations = new HashSet<Item>();
}
}
To make your code work, you could mark your collection properties as virtual stated by #danludwig. By marking the collection properties as virtual EF Code First will lazy load those properties when iterating over the items in your view. You run into a SELECT N+1 problem using this approach. Let's examine your view code:
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(model => item.Description)
</td>
<td>
#foreach (var tag in item.Tags)
{
#tag.Text
}
</td>
</tr>
}
In this foreach loop you iterate over all items in your Model that were selected using the EF data context.
db.Items.ToList()
This is your first select. But in your view above every time you access an item's Tags property another select is executed. The important thing is FOR EVERY ITEM. That means if you have 100 Items in db.Items DbSet, you'll execute 101 selects. This is not acceptable for most systems.
A better approach is to pre select the tags for each item. One approach is to use Include or to select the tags related to an item into dedicated object.
public class ItemWithTags
{
public Item Item { get;set; }
public IEnumerable<Tag> Tags { get;set; }
}
public ActionResult Index()
{
ItemsEntities db = new ItemsEntities();
var itemsWithTags = db.Items.Select(item => new ItemWithTags() { Item = item, Tags = item.Tags});
return View(itemsWithTags.ToList());
}
In your view you can iterate over the itemsWithTags collection, access items's properties and for tags you access the Tags property of ItemWithTags.
Another problem with your code is, that the ItemsEntities DbContext is opened in your code but never closed. You can use the VS MVC Templates to generate a Controller that handles DbContext opening and closing correctly!
You can use a tool like MVC Mini Profiler for to inspect the commands executed against the database. This Stackoverflow Question shows how to set up MVC Mini Profiler with EF Code First.

Resources