How to Load Related Data in MVC with complex table structure - asp.net-mvc-3

I have several classes which I will shorten for brevity. Below they are listed with the related properties/fields associated with this question.
public class AcademicYear
{
[Key]
public int AcademicYearId { get; set; }
}
public class Division
{
public Division()
{
this.CareerFields = new HashSet<CareerField>();
}
[Key]
public int DivisionId { get; set; }
// Foreign Keys
public int AcademicYearId { get; set; }
// Navigation Properites
public virtual AcademicYear AcademicYear { get; set; }
public virtual ICollection<CareerField> CareerFields { get; set; }
}
public class CareerField
{
public CareerField()
{
this.Clusters = new HashSet<Cluster>();
}
[Key]
public int CareerFieldId { get; set; }
// Foreign Keys
public int AcademicYearId { get; set; }
public int DivisionId { get; set; }
// Navigation Properties
public virtual AcademicYear AcademicYear { get; set; }
public virtual Division Division { get; set; }
public virtual ICollection<Cluster> Clusters { get; set; }
}
public class Cluster
{
public Cluster()
{
this.CareerFields = new HashSet<CareerField>();
}
[Key]
public int ClusterId { get; set; }
{
// Foreign Keys
public int AcademicYearId { get; set; }
// Navigation Properties
public virtual AcademicYear AcademicYear { get; set; }
public virtual ICollection<CareerField> CareerFields { get; set; }
}
}
public class Pathway
{
public Pathway()
{
this.CareerMajors = new HashSet<CareerMajor>();
}
[Key]
public int PathwayId { get; set; }
// Foreign Keys
public int AcademicYearId { get; set; }
public int ClusterId { get; set; }
// Navigation Properties
public virtual AcademicYear AcademicYear { get; set; }
public virtual Cluster Cluster { get; set; }
public virtual ICollection<CareerMajor> CareerMajors { get; set; }
}
public class CareerMajor
{
public CareerMajor()
{
this.Courses = new HashSet<Course>();
}
[Key]
public int CareerMajorId { get; set; }
public string FirstYearOffered { get; set; }
// Foreign Keys
public int AcademicYearId { get; set; }
public int PathwayId { get; set; }
// Navigation Properties
[HiddenInput(DisplayValue = false)]
public virtual AcademicYear AcademicYear { get; set; }
public virtual Pathway Pathway { get; set; }
public virtual ICollection<Course> Courses { get; set; }
}
public class Course
{
public Course()
{
this.CareerMajors = new HashSet<CareerMajor>();
}
[Key]
public int CourseId { get; set; }
// Foreign Keys
public int AcademicYearId { get; set; }
public int? InstructorId { get; set; }
// Navigation Properties
public virtual ICollection<CareerMajor> CareerMajors { get; set; }
public virtual AcademicYear AcademicYear { get; set; }
}
I also have a ViewModel class to load all this for my controller
public class CMSIndex
{
public IEnumerable<Division> Divisions { get; set; }
public IEnumerable<CareerField> CareerFields { get; set; }
public IEnumerable<Cluster> Clusters { get; set; }
public IEnumerable<Pathway> Pathways { get; set; }
public IEnumerable<CareerMajor> CareerMajors { get; set; }
public IEnumerable<Course> Courses { get; set; }
}
I have a razor cshtml page (the auto CRUD defined Index page) where I start with the Division and I can load the related CareerFields. However, when I try to load Clusters, I get the error message
Server Error in '/' Application.
Value cannot be null.
Parameter name: source
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.ArgumentNullException: Value cannot be null.
Parameter name: source
Here is my index method from the controller (note the commented out lines are what I have tried as well)
public ViewResult Index(Int32? divisionID, Int32? careerFieldID, Int32? clusterID, Int32? pathwayID, Int32? careerMajorID, Int32? courseID)
{
var viewModel = new CMSIndex();
viewModel.Divisions = db.Divisions
.Include(d => d.AcademicYear)
.Include(d => d.CareerFields)//
.Include(d => d.CareerFields.Select(cf => cf.Clusters))
//.Select(c => c.Clusters.Select(cl => cl.Pathways.Select(p => p.CareerMajors.Select(cm => cm.Courses)))))
.OrderBy(d => d.DivisionName);
if (divisionID != null)
{
ViewBag.DivisionID = divisionID.Value;
viewModel.CareerFields = viewModel.Divisions.Where(d => d.DivisionId == divisionID.Value).Single().CareerFields;
}
if (careerFieldID != null)
{
ViewBag.careerFieldID = careerFieldID.Value;
//viewModel.Clusters = viewModel.CareerFields.Where(cf => cf.CareerFieldId == careerFieldID.Value).Single().Clusters;
//viewModel.Clusters = viewModel.CareerFields.SelectMany(cf => cf.Clusters);
viewModel.Clusters = viewModel.CareerFields.Where(cf => cf.CareerFieldId == careerFieldID.Value).Single().Clusters;
}
return View(viewModel);
}
I am following the Contoso tutorial on loading related data. I seem to be able to load 1:m relationships, but I am unsure how to do this with m:m (e.g. CareerFields and Clusters).
And, I do have an initializer that has loaded data to pull and I am passing an ID (e.g. careerFieldID).
Where the exception is being thrown is on the line:
viewModel.Clusters = viewModel.CareerFields.Where(cf => cf.CareerFieldId == careerFieldID.Value).Single().Clusters;
And each commented variation.
Any help would be extremely appreciated.

Not saying that this is the issue, but have you checked the values are not null? In the line
viewModel.Clusters = viewModel.CareerFields
.Where(cf => cf.CareerFieldId == careerFieldID.Value).Single().Clusters;
is it possible that viewModel.CareerFields is null?
As some side notes:
With your null checks when you have nullables it's better to use their HasValue method e.g.
if (divisionID.HasValue)
{
...
}
The use of Single() can be a bit temperamental and can throw exceptions if no elements exist or if more than one exist. First() can be used to handle more than one (but this throws if none match), SingleOrDefault() will handle zero or one result and FirstOrDefault() will handle most things.
You can save a expression by putting your lambda in the single e.g.
viewModel.CareerFields = viewModel.Divisions
.Single(d => d.DivisionId == divisionID.Value).CareerFields;
Edit: I think the issue you are having is the same as in This Answer could you try changing your classes collections so the are initialised in the get when null. E.g for Division
public class Division
{
[Key]
public int DivisionId { get; set; }
// Foreign Keys
public int AcademicYearId { get; set; }
// Navigation Properites
public virtual AcademicYear AcademicYear { get; set; }
private ICollection<CareerField> careerFields;
public virtual ICollection<CareerField> CareerFields {
get { return careerFields ?? (careerFields = new HashSet<CareerField>()); }
set { careerFields = value; }
}
}
That's actually my preferred method anyway, this imposes it's own problem with your code as the fact that CareerFields was null means that for the given division, there are no associated CareerFields. This means that when your Single() call is hit it will throw the exception The source contains no elements as CareerFields will contain a empty hashset. This could be fixed by a change to SingleOrDefault().

Related

How do I define this relationship with code-first?

I'm trying to create a library of books.
I separated data and users context/database for security reasons (and clarity) but is it that useful?
Specially since BookOfUserEntity.Id should be really a composite key between UserId & BookId.
Something like :
public class BookOfUserEntity
{
[Key]
public Guid UserId { get; set; }
[Key]
public int BookId { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<BookOfUserEntity>()
.HasKey(b => new { b.UserId, b.BookId });
}
But then if I create a composite key what should be HistoryEntity.BookOfUserEntityId type & value?
I also separated BookEntity & BookOfUserEntity to avoid duplicated data and reuse what can be reused (MainCoverArtId etc...), but this added complexity - should I go back to a simpler model?
public class BookEntity
{
public int Id { get; set; }
public Guid? MainCoverArtId { get; set; }
...
public virtual List<BookOfUserEntity>? BookOfUserEntities { get; set; } // Only purpose is to know how many Users have this very book in their list.
}
public class BookOfUserEntity // Bad name here don't mention it
{
public int Id { get; set; }
public Guid UserId { get; set; }
public int BookId { get; set; }
public BookEntity Book { get; set; } // Useful? Should be virtual?
public List<HistoryEntity>? HistoryEntities { get; set; }
...
}
public class HistoryEntity
{
public int Id { get; set; }
public int BookOfUserEntityId { get; set; }
public BookOfUserEntity BookOfUserEntity { get; set; } // Same questions
public int Vol { get; set; }
public double Chapter { get; set; }
public DateTime? ReadDate { get; init; }
}

Trying to Populate ViewModel List from Linq query MVC "List doesn't contain definiton for ..."error"

I am (trying to) build a multiple choice test application that will pass a list of multiple choice questions (select lists) from the controller to a view.
Then populate the view using a foreach loop, post the answers back to the controller, check them and increment the score for each correct answer and then update the db.
I am trying to populate the view model list using a Linq query (to keep my controller thin, I am doing this via a method in my Service layer).
Models
Questions (db first)
namespace AccessEsol.Models
{
using System;
using System.Collections.Generic;
public partial class Question
{
public Question()
{
this.ExamDets = new HashSet<ExamDet>();
}
public int QuID { get; set; }
public string Text1 { get; set; }
public string Text2 { get; set; }
public string CorrectAnswer { get; set; }
public string Foil1 { get; set; }
public string Foil2 { get; set; }
public string Foil3 { get; set; }
public string Level_ { get; set; }
public string GrammarPoint { get; set; }
public virtual ICollection<ExamDet> ExamDets { get; set; }
}
}
Exam
namespace AccessEsol.Models
{
using System;
using System.Collections.Generic;
public partial class Exam
{
public Exam()
{
this.Candidates = new HashSet<Candidate>();
this.ExamDets = new HashSet<ExamDet>();
}
public int ExamID { get; set; }
public string Name { get; set; }
public Nullable<System.DateTime> Date { get; set; }
public int AdminID { get; set; }
public string StartLevel { get; set; }
public virtual Administrator Administrator { get; set; }
public virtual ICollection<Candidate> Candidates { get; set; }
public virtual ICollection<ExamDet> ExamDets { get; set; }
}
}
There is also a model for Candidate that contains the CandID:
And the view model that uses these models:
namespace AccessEsol.Models
{
public class ExamQuestionsViewModel
{
public int QuID { get; set; }
public int CandID { get; set; }
public int ExamId { get; set; }
public string Text1 { get; set; }
public string Text2 { get; set; }
public string CorrectAnswer { get; set; }
public string Foil1 { get; set; }
public string Foil2 { get; set; }
public string Foil3 { get; set; }
public string Level { get; set; }
public string GrammarPoint { get; set; }
public virtual ICollection<ExamDet> ExamDets { get; set; }
}
}
This is the method that is to populate the view model:
public static List<ExamQuestionsViewModel> AddQuestions()
{
AccessEsolDataEntities db = new AccessEsolDataEntities();
string questionLevel = GetLevel();
int currentCand = GetCandID();
int currentExam = GetExamID();
//model = new DataAccess().Populate();
var qu = (from a in db.Questions
where a.Level_ == questionLevel
select a).ToList();
List<ExamQuestionsViewModel> exam = new List<ExamQuestionsViewModel>();
foreach (var IDs in exam)
{
currentCand = exam.CandID;
currentExam = exam.ExamId;
}
return (exam);
}
The error message I am getting is
'System.Collections.Generic.List<AccessEsol.Models.ExamQuestionsViewModel>'
does not contain a definition for 'ExamId' and no extension method
'ExamId' accepting a first argument of type
'System.Collections.Generic.List<AccessEsol.Models.ExamQuestionsViewModel>' could be found (are you missing a using directive or an assembly
reference?
What am I doing wrong here? All feedback much appreciated.
Please try this instead of your foreach:
foreach (var IDs in exam)
{
currentCand = IDs.CandID;
currentExam = IDs.ExamId;
}

Data Annotation for foreign key relationship

I have two classes
public class Project
{
[Key]
public int ID { get; set; }
public string Name { get; set; }
public int ManagerID { get; set; }
public int CoordID { get; set; }
[ForeignKey("ManagerID")]
public virtual Employee Manager { get; set; }
[ForeignKey("CoordID")]
public virtual Employee Coord { get; set; }
}
public class Employee
{
[Key]
public int EmpID { get; set; }
public string Name { get; set; }
[InverseProperty("ManagerID")]
public virtual ICollection<Project> ManagerProjects { get; set; }
[InverseProperty("CoordID")]
public virtual ICollection<Project> CoordProjects { get; set; }
}
The ManagerID and CoordID map to the EmpID column of the Employee table.
I keep getting an error for Invalid Columns becauce EF is not able to map correctly. I think it is looking for wrong column.
I think InverseProperty is used to refer to the related navigation property, not the foreign key, e.g.
public class Employee
{
[Key]
public int EmpID { get; set; }
public int Name { get; set; }
[InverseProperty("Manager")]
public virtual ICollection<Project> ManagerProjects { get; set; }
[InverseProperty("Coord")]
public virtual ICollection<Project> CoordProjects { get; set; }
}
Also, is there a reason why your names are ints and not strings?
Best guess would be to use fluent API in your context via OnModelCreating. By renaming the column, EF can't figure out the original object to map so it's confused. However, Fluent API allows you to manually specify the map using something like the following:
public class MyContext : DbContext
{
public DbSet<Employee> Employees { get; set; }
public DbSet<Project> Projects { get; set; }
protected override OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Project>()
.HasRequired(x => x.Manager)
.WithMany(x => x.ManagerProjects)
.HasForeignKey(x => x.ManagerID);
modelBuilder.Entity<Project>()
.HasRequired(x => x.Coord)
.WithMany(x => x.CoordProjects)
.HasForeignKey(x => x.CoordID);
}
}

updating a record throws 'Multiplicity constraint violated.' in asp.net mvc

I am developing an application in asp.net mvc and using EF code first for my data access. Here is the model I use :
public class Culture
{
[Key()]
public int CultureID { get; set; }
[Required()]
[StringLength(250)]
public string CultureName { get; set; }
[Required()]
[StringLength(250)]
public string CultureDisplay { get; set; }
public virtual List<HomePage> HomePage { get; set; }
public virtual List<Person_Local> PersonLocal { get; set; }
}
public class Person
{
public int PersonID { get; set; }
[StringLength(250)]
public string PersonPicAddress { get; set; }
public virtual List<Person_Local> PersonLocal { get; set; }
}
public class Person_Local
{
//[NotMapped()]
[Key, Column(Order = 1)]
[ForeignKey("Person")]
public int PersonID { get; set; }
[Key, Column(Order = 2)]
[ForeignKey("Culture")]
public int CultureID { get; set; }
public string PersonName { get; set; }
public string PersonFamily { get; set; }
public string PersonAbout { get; set; }
public virtual Culture Culture { get; set; }
public virtual Person Person { get; set; }
}
I dont have any problem adding new person and personlocal to db. But when I want to update the person local, as below:
public ActionResult CreatePerson([Bind(Prefix = "Person")]Person obj,
[Bind(Prefix = "Person.PersonLocal")]IEnumerable<Person_Local> plocals)
{
string photo_guid = obj.PersonPicAddress;
if (obj.PersonID != 0)
{
Person p = da.Persons.FirstOrDefault(x => x.PersonID == obj.PersonID);
TryUpdateModel(p, "Person");
if (obj.PersonLocal[0].Person.PersonID != 0)
{
int cid = obj.PersonLocal[0].Culture.CultureID;
int pid = obj.PersonLocal[0].Person.PersonID;
Person_Local ploc =
da.Person_Locals.First(x => x.CultureID == cid && x.PersonID == pid);
//update ploc
}
da.SaveChanges();
}
}
I got following error :
Multiplicity constraint violated. The role 'Person_Local_Person_Target' of the relationship 'WebApp.Models.Person_Local_Person' has multiplicity 1 or 0..1.
Edited:
I commented
TryUpdateModel(p, "Person");
And the it seems the problem has been solved ?!! Why ?!

cant insert complex object to database using entity frame work

I am developing an asp.net mvc application, which has these enity classes:
public class Person
{
public int PersonID { get; set; }
public string PersonPicAddress { get; set; }
public virtual List<Person_Local> PersonLocal { get; set; }
}
public class Person_Local
{
public int PersonID { get; set; }
public int CultureID { get; set; }
public string PersonName { get; set; }
public string PersonFamily { get; set; }
public string PersonAbout { get; set; }
public virtual Culture Culture { get; set; }
public virtual Person Person { get; set; }
}
public class Culture
{
public int CultureID { get; set; }
[Required()]
public string CultureName { get; set; }
[Required()]
public string CultureDisplay { get; set; }
public virtual List<HomePage> HomePage { get; set; }
public virtual List<Person_Local> PersonLocak { get; set; }
}
I defined an action with [Httppost] attribute, which accepts complex object from a view.
Here is the action :
[HttpPost]
public ActionResult CreatePerson([Bind(Prefix = "Person")]Person obj)
{
AppDbContext da = new AppDbContext();
//Only getting first PersonLocal from list of PersonLocals
obj.PersonLocal[0].Person = obj;
da.Persons.Add(obj);
da.SaveChanges();
return Jsono(...);
}
But when it throws error as below :
Exception:Thrown: "Invalid column name 'Culture_CultureID'." (System.Data.SqlClient.SqlException)
A System.Data.SqlClient.SqlException was thrown: "Invalid column name 'Culture_CultureID'."
And the insert statement :
ADO.NET:Execute Reader "insert [dbo].[Person_Local]([PersonID], [PersonName], [PersonFamily], [PersonAbout], [Culture_CultureID])
values (#0, #1, #2, #3, null)
select [CultureID]
from [dbo].[Person_Local]
where ##ROWCOUNT > 0 and [CultureID] = scope_identity()"
The command text "insert [dbo].[Person_Local]([PersonID], [PersonName], [PersonFamily], [PersonAbout], [Culture_CultureID])
values (#0, #1, #2, #3, null)
select [CultureID]
from [dbo].[Person_Local]
where ##ROWCOUNT > 0 and [CultureID] = scope_identity()" was executed on connection "Data Source=bab-pc;Initial Catalog=MainDB;Integrated Security=True;Application Name=EntityFrameworkMUE", building a SqlDataReader.
Where is the problem?
Edited:
Included EntityConfigurations Code:
public class CultureConfig : EntityTypeConfiguration<Culture>
{
public CultureConfig()
{
HasKey(x => x.CultureID);
Property(x => x.CultureName);
Property(x => x.CultureDisplay);
ToTable("Culture");
}
}
public class PersonConfig : EntityTypeConfiguration<Person>
{
public PersonConfig()
{
HasKey(x => x.PersonID);
Property(x=>x.PersonPicAddress);
ToTable("Person");
}
}
public class Person_LocalConfig : EntityTypeConfiguration<Person_Local>
{
public Person_LocalConfig()
{
HasKey(x => x.PersonID);
HasKey(x => x.CultureID);
Property(x=>x.PersonName);
Property(x => x.PersonFamily);
Property(x => x.PersonAbout);
ToTable("Person_Local");
}
}
Try to remove fields CultureID and PersonID from Person_Local class. Because you already has field Person and Culture
It looks like your schema is out of sync with your model. Make sure you understand EF Code first schema update features, which are described in this blog. If you need more sophisticated schema migration, there are some other approaches in answers to this question.

Resources