Entity Framework relate second table - asp.net-mvc

How can I relate the tables below by fluent api?
I need to get the second table data via LinkTable which stores ids of Main and Second tables.
Here are my models:
public class MainTable
{
public int ID { get; set; }
...
...
public ICollection<LinkTable> LinkTable { get; set; }
}
public class LinkTable
{
public int ID { get; set; }
...
...
public MainTable MainTable { get; set; }
public SecondTable SecondTable { get; set; }
}
public class SecondTable
{
public int ID { get; set; }
...
...
public ICollection<LinkTable> LinkTable { get; set; }
}
I mapped like this:
HasRequired(t => t.MainTable).WithMany(t => t.LinkTable).HasForeignKey(t => t.MainTable_ID);
HasRequired(t => t.SecondTable).WithMany(t => t.LinkTable).HasForeignKey(t => t.SecondTable_ID);
And trying to get data :
MyDBContext.MainTable.Include("LinkTable").FirstOrDefault();
When I was trying foreach the LinkTable to get SecondTable data I got an error:
Object reference not set...
#foreach (var item in Model.LinkTable)
{
<p>#item.SecondTable.ID</p>
}

To avoid that exception, you need to initialize your navigation property in a constructor:
public class MainTable
{
public MainTable()
{
LinkTable=new List<LinkTable>();
}
public int ID { get; set; }
...
...
public ICollection<LinkTable> LinkTable { get; set; }
}
Also, you could use Include extension method which is strongly typed and could avoid you possible run-time exceptions:
MyDBContext.MainTable.Include(mt=>mt.LinkTable).FirstOrDefault();
Now if you want to load another level, in this case SecondTable, you can do this:
MyDBContext.MainTable.Include(mt=>mt.LinkTable.Select(lt=>lt.SecontTable)).FirstOrDefault();
In the link I quoted above you can find more examples about how to load different levels.

Related

Want to join two tables on primary key, display the results in one view

IQueryable<Product> product = objContext.Set<Product>().Include(p =>
p.Categories.Name).Where(p => p.Id == 2);
As per the current view, I'm getting an error. It says add other model with their properties. i.e. to include Category model and corresponding Name property.
#model IEnumerable<>crudOneToMany.Models.Product>
using viewmodel, is it possible to join two tables?
View
Error
A specified Include path is not valid. The EntityType 'crudOneToMany.Models.Category' does not declare a navigation property with the name 'Name'.
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int CategoryId { get; set; }
public virtual Category Categories { get; set; }
}
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Product> Products { get; set; }
}
public class ProductDBContext : DbContext
{
public ProductDBContext()
: base("ProductDBContext")
{
}
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>().HasRequired(o => o.Categories).WithMany(o => o.Products).HasForeignKey(o => o.CategoryId);
base.OnModelCreating(modelBuilder);
}
}
Your problem is here:
.Include(p => p.Categories.Name)
Instead you should write .Include(p => p.Categories)
This means that in output there will be loaded Categories navigation collection to product.
Name is simple string property (is not navigation property so it should not be included)
Here is the proposed ViewModel for you.
ProductViewModel.cs
public class ProductViewModel
{
public int Id { get; set; }
[Required(ErrorMessage = "required")]
public string ProductName { get; set; }
public Category Category { get; set; }
public ICollection<Category> Categories { get; set; }
}

How to use #html.hiddenfor() two view in MVC - one for getting data from database and other to store data

fist model get the list of questions.
but i am not able to access them while using #Html.HiddenFor() etc
these item are visible if i use #Html.Hidden() or anything without ....For method...
any idea how can i do this
here are my classes
public class QuestionModel
{
public int Id { get; set; }
public string QuestDes { get; set; }
public int Aspect { get; set; }
}
public class AnswerModel
{
public int Id { get; set; }
public string SelectedAns { get; set; }
public virtual QuestionModel Question { get; set; }
public virtual PersonModel Person { get; set; }
}
my controller code
public ActionResult GPage2()
{
var tview = new Tuple<List<QuestionModel>,AnswerModel>(getQuestions(),new AnswerModel());
return View(tview);
}
private List<QuestionModel> getQuestions()
{
var qList = (from q in dbcon.Questions
orderby q.Id
select q).ToList();
return qList;
}
in cshtml page
#model Tuple<List<QuestionModel>,AnswerModel>
<td> #Html.Label(Model.Item2.SelectedAns)</td>
#Html.LabelFor(.......................) not working
from what you have posted you need to use a view model that includes your 2 models
public class ViewModel{
public List<QuestionModel> Questions { get; set; }
public List<AnswerModel> Answers { get; set; }
}
then on your view
#model ViewModel
using this setup your for helpers should work. since it is a list putting them in a foreach would look something like this.
#foreach(var temp in Model.Questions){
#Html.LabelFor(x => temp.Aspect)
//etc
}

Model count = null

I'm rewriting this question:
I have 2 models. Entry and Topic.
public class Entry
{
public int EntryId { get; set; }
public int UserId { get; set; }
public int TopicId { get; set; }
public String EntryQuestion { get; set; }
public String EntryAnswer { get; set; }
public int EntryReview { get; set; }
public String QuestionValidationURL { get; set; }
public virtual ICollection<Topic> TopicList { get; set; }
}
public class Topic
{
public int TopicId { get; set; }
public String TopicName { get; set; }
}
I followed an example on ASP.Net/MVC to set up my models this way.
What I would like to do is for every entry item I have a TopicId, but then I'd like to convert that to a TopicName by accessing my TopicList.
My question is, how do I load TopicList?
In the examples I'm following I'm seeing something about LazyLoading and EagerLoading, but it doesn't seem to be working.
I tried doing the following from my Entry controller:
db.Entries.Include(x => x.TopicList).Load();
But that still gives me a TopicList of 0 (which is better than null)
How can I do this?
In my view I'm binding to the Entries like this:
#model IEnumerable<projectInterview.Models.Entry>
I would like to access the TopicList here:
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.TopicId)
</td>
...
</tr>
I'd like to use the TopicId in this loop and display the TopicName that is part of the object in the collection.
I'm assuming you're following an Entity Framework example. You're trying to create a one-to-many relationship, as far as I can tell, although I'm unsure about which end is which.
In the general case, to establish a one-to-many relationship, you have to do something like this:
public class One
{
[Key]
public int Id { get; set; }
public virtual ICollection<Many> Many { get; set; }
}
public class Many
{
[Key]
public int Id { get; set; }
[ForeignKey("One")]
public int OneId { get; set; }
public virtual One One { get; set; }
}
If what you're trying to do is have one Entry relating to many Topic objects, then you're almost there but you're lacking something.
For the ICollection<Topic> to actually contain anything, the (many) Topic objects need to have a foreign key to the (one) Entry. (It also doesn't hurt to explicitly mark the primary key on both sides, rather than relying on the EF conventions.)
public class Topic
{
[Key]
public int TopicId { get; set; }
public String TopicName { get; set; }
[ForeignKey("Entry")]
public int EntryId { get; set; }
public virtual Entry Entry { get; set; }
}
public class Entry
{
[Key]
public int EntryId { get; set; }
public int UserId { get; set; }
public int TopicId { get; set; }
public String EntryQuestion { get; set; }
public String EntryAnswer { get; set; }
public int EntryReview { get; set; }
public String QuestionValidationURL { get; set; }
public virtual ICollection<Topic> TopicList { get; set; }
}
Now TopicList should be an actual and populated collection, without the need to do an Include.
If, on the other hand, you want one Topic relating to many Entry objects, then you have it a little backwards. The correct way would be:
public class Topic
{
[Key]
public int TopicId { get; set; }
public String TopicName { get; set; }
public virtual ICollection <Entry> Entries { get; set; }
}
public class Entry
{
[Key]
public int EntryId { get; set; }
public int UserId { get; set; }
public String EntryQuestion { get; set; }
public String EntryAnswer { get; set; }
public int EntryReview { get; set; }
public String QuestionValidationURL { get; set; }
[ForeignKey("Topic")]
public int TopicId { get; set; }
public virtual Topic Topic { get; set; }
}
In this case, you may or may not use db.Entries.Include(x => x.Topic) depending on whether you want them loaded all at once or one-by-one on demand. Regardless of what you choose, the following expression should return the proper value:
myEntry.Topic.TopicName
If I understand you correctly you have added the list of Topics to the Entry just to get the name of the topic when displaying the entry. The best way to do this is to actually have a Topic property in your entry model. So your model would look like this:
public class Entry
{
public int EntryId { get; set; }
public int UserId { get; set; }
public int TopicId { get; set; }
public String EntryQuestion { get; set; }
public String EntryAnswer { get; set; }
public int EntryReview { get; set; }
public String QuestionValidationURL { get; set; }
//Change this.....
public virtual Topic Topic { get; set; }
}
Then in your view you would use (assuming the Model is an IEnumerable):
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => modelItem.Topic.TopicName )
</td>
...
</tr>
This link has a great example of how to do this:
http://weblogs.asp.net/manavi/archive/2011/03/28/associations-in-ef-4-1-code-first-part-2-complex-types.aspx
In my opinion problem is with casting. In view you have IEnumerable<projectInterview.Models.Entry> while Topics is ICollection<Topic>, which is a collection of different type
Topics = null means there are no Topics in the list to iterate over. How do you fill them? Your view expects IEnumerable how do you cast your topics to the entries?
Based on the original question I've added a small working example, maybe it helps you to find your bug.
Controller:
public class TestController : Controller
{
public ActionResult Index()
{
var viewModel = new ViewModel()
{
Topics = new List<Topic>()
};
viewModel.Topics.Add(new Topic() { header = "test" });
viewModel.Topics.Add(new Topic() { header = "test2" });
return View(viewModel);
}
}
Model:
public class ViewModel
{
public virtual ICollection<Topic> Topics { get; set; }
public int getCount()
{
return Topics.Count;
}
}
public class Topic
{
public string header { get; set; }
}
View:
#model testProject.Models.ViewModel
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#Model.getCount()
#foreach(var item in Model.Topics)
{
<div>#item.header</div>
}
Output:
Index
2
test
test2
It seems that you are not initializing your Topics anywhere in the code. If the collection is null it means it is not initialized. If you instantiate it with
ICollection<Topic> Topics = new List<Topic>();
Once initialized you should receive zero when calling Topics.Count. If you do not make a call to a database it will stay zero.
In your case check whether you are instantiating the Topics.

Database not being auto-generated properly Entity Framework + Repos Pattern

I've set up Entity Framework Code First with the Generic Repository Pattern.
Here are my models:
public interface IEntity {
int Key { get; set; }
}
public class Product : IEntity {
public int Key {
get {
return ID;
}
set {
ID = value;
}
}
public int ID { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public IEnumerable<Category> Category { get; set; }
}
public class Category : IEntity {
public int Key {
get {
return ID;
}
set {
ID = value;
}
}
public int ID { get; set; }
public string Name { get; set; }
public int ParentID { get; set; }
}
Here is my context that hooks into my generic repo:
public class EntitiesContext : DbContext, IDbContext {
public DbSet<Product> Products { get; set; }
public new IDbSet<T> Set<T>() where T : class {
return base.Set<T>();
}
}
As you can see Product has a IEnumerable of Category. If I were to create a database to match this is would be like so:
Product
- ID
- Name
- etc.
Category
- ID
- Name
- etc.
ProductCategories
- ProductID
- CategoryID
How come when my database is created that there is no joining table?
I'm pretty sure it is because you are defining the collection as an IEnumerable<T>. I think that Entity Framework needs at least an ICollection<T> to make the relationship. This SO post covers most of it.
So, change this:
public IEnumerable<Category> Category { get; set; }
To this:
public ICollection<Category> Category { get; set; }
Further, if you want to lazy load the collection, then also make it virtual:
public virtual ICollection<Category> Category { get; set; }

LINQ to entities against EF in many to many relationship

I'm using ASP.NET MVC4 EF CodeFirst.
Need help to write LINQ (to entities) code in Index action to get collection of Courses which are attended by selected student. The relationship is many to many with join table with payload.
//StudentController
//-----------------------
public ActionResult Index(int? id)
{
var viewModel = new StudentIndexViewModel();
viewModel.Students = db.Students;
if (id != null)
{
ViewBag.StudentId = id.Value;
// *************PROBLEM IN LINE DOWN. HOW TO MAKE COURSES COLLECTION?
viewModel.Courses = db.Courses
.Include(i => i.StudentsToCourses.Where(t => t.ObjStudent.FkStudentId == id.Value));
}
return View(viewModel);
}
The error I got is:
The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties.
I have modeles (the third one is for join table with payload):
//MODEL CLASSES
//-------------
public class Student
{
public int StudentId { get; set; }
public string Name { get; set; }
public virtual ICollection<StudentToCourse> StudentsToCourses { get; set; }
}
public class Course
{
public int CourseId { get; set; }
public string Title { get; set; }
public virtual ICollection<StudentToCourse> StudentsToCourses { get; set; }
}
public class StudentToCourse
{
public int StudentToCourseId { get; set; }
public int FkStudentId { get; set; }
public int FkCourseId { get; set; }
public string Classroom { get; set; }
public virtual Student ObjStudent { get; set; }
public virtual Course ObjCourse { get; set; }
}
Then, here is modelview I need to pass to view
//VIEWMODEL CLASS
//---------------
public class StudentIndexViewModel
{
public IEnumerable<Student> Students { get; set; }
public IEnumerable<Course> Courses { get; set; }
public IEnumerable<StudentToCourse> StudentsToCourses { get; set; }
}
EF does not support conditional include's. You'll need to include all or nothing (ie no Whereinside the Include)
If you need to get the data for just certain relations, you can select it into an anonymous type, something like (the obviously untested);
var intermediary = (from course in db.Courses
from stc in course.StudentsToCourses
where stc.ObjStudent.FkStudentId == id.Value
select new {item, stc}).AsEnumerable();
Obviously, this will require some code changes, since it's no longer a straight forward Course with a StudentsToCourses collection.

Resources