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.
Related
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.
i have created 2 model classes Mocmodel and mocsubmodel and set navigation property also.for each model id, there will be different submodels for a particular model id. i wanted to create am action link showing count of submodels, when i click on the count, view should display submodel details for each particular modelIDs like below
1220 FXRate count as hyperlink
public class tModel
{
public int ModelID { get; set; }
public string ModelName { get; set; }
public virtual ICollection<tSubModelcs> submodellist { get; set; }
}
public class tSubModelcs
{
public int submodelID { get; set; }
public string submodel { get; set; }
public DateTime launchdate { get; set; }
public int ModelID { get; set; }
}
in my view page i want to display the count of submodel as a link
#foreach (var m in Model)
{
<tr>
<td>#m.ModelID</td>
<td>#m.ModelName</td>
<td>
Html.ActionLink(, "findsubmodels", new { #id = m.ModelID, #class = "linkclick" })
</td>
</tr>
Nothing is clear. Which model are you sending to view ? Post your complete code with action methods. It seems like you are sending IEnumerable of tModel. If this is the case your Actionlink should be like this.
#if(m.submodellist != null)
{
#Html.ActionLink(m.submodellist.Count.ToString(), "findsubmodels",null, new { id = #m.ModelID, #class = "linkclick" })
}
Replace the null with your routevalue if any.
Ok, so I have seen a lot of similar questions, but unfortunalety I can't figure out how to access a list of users from db in the View. I get a System.NullReferenceException. Can one of you experts see what I am doing wrong?
Model
public class MyModel
{
[Key]
public int Id { get; set; }
[Display(Name = "Name")]
public string Name { get; set; }
public bool TriggerOnLoad { get; set; }
public string TriggerOnLoadMessage { get; set; }
public string EmployeeNumber { get; set; }
public IEnumerable<User> Users { get; set; }
public List<MyModel> GetAllUsers()
{
var queryString = "SELECT Name FROM Users";
var adapter = new SqlDataAdapter(queryString, System.Configuration.ConfigurationManager.ConnectionStrings["SQLConn"].ConnectionString);
var current = new DataSet();
adapter.Fill(current, "Name");
return (from DataRow item in current.Tables[0].Rows
select new MyModel()
{
Name = Convert.ToString(item["Name"]),
}).ToList();
}
Controller
public ActionResult GetUser()
{
var model = new MyModel();
_db.GetAllUsers();
return View(model);
}
View
#model ModelName.Models.MyModel
--HTMLCode--
#foreach (var item in Model.Users) <-- Exception.
{
<tr>
<td>#Html.DisplayFor(modelItem => item.Name)</td>
</tr>
}
What am I forgetting?
Your problem is that you are getting the users, but you are not passing the users to the view, instead, you pass an empty model.
A better approach would be to create a ViewModel with all the properties you need in the view.
Let's say your view model looks something like this:
ViewModel:
class YourViewModel
{
public string Name { get; set; }
public bool TriggerOnLoad { get; set; }
public IEnumerable<User> Users { get; set; }
}
After creating your ViewModel what you need to do is that you need to create an instance of the ViewModel in your Controller and fill it with data and then pass the ViewModel to the View. In your case it would look something like this:
Action:
public ActionResult RandomAction()
{
YourViewModel vm = new YourViewModel()
{
Users = _db.GetAllUsers(),
Name = "Random Name",
TriggerOnLoad = true
};
return View(vm);
}
Later on, if you decide you need some extra properties you need to work with in your view, you just add them to your ViewModel and continue on.
Pass users like this. You are not passing the users to View
public ActionResult GetUser()
{
var model = new MyModel();
var users = _db.GetAllUsers();
return View(users);
}
Then in View
#model List<MyModel>
#foreach (var item in Model) //foreach through the list
{
<tr>
<td>#Html.DisplayFor(model => item.Name)</td>
</tr>
}
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.
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>
}