MVC ASP.NET Entity Framework Not Saving a List of Assocciated Objects - asp.net

This question is in reference to the project discussed here. After resolving the previous problem I have run into a new one. When The Student object is saved, the list of courses associated with it is not saved. I can see the collection of course objects when I mouse over the student object after setting a breakpoint:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AddCourseVM (AddCourseViewModel vModel)
{
Student stu = db.Students.Find(vModel.Student.ID);
foreach (Course c in vModel.PossibleCourses)
{
if (c.Selected)
{
BaseCourse bc = db.BaseCourses.Find(c.BaseCourse.ID);
c.BaseCourse = bc;
c.Student = stu;
stu.CoursesTaken.Add(c);
}
}
if (stu != null)
{
db.Entry(stu).State = EntityState.Modified; //breakpoint here
db.SaveChanges();
}
return RedirectToAction("ListTakenCourses", stu);
}
public ActionResult ListTakenCourses (Student stu)
{
List<Course> taken = stu.CoursesTaken.ToList();
foreach (Course c in taken)
{
c.BaseCourse = db.BaseCourses.Find(c.BaseCourse.ID);
}
ViewBag.CoursesTaken = taken;
return View(stu);
}
But when I pass the object to the next method, the list of courses taken comes back null. The courses are being saved to the database, I can see them when I go into the SQL Server explorer, but for some reason they are not being attached to the student object. The code for the objects:
public class Student
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string WNumber { get; set; }
public int HoursCompleted { get; set; }
public double GPA { get; set; }
public Concentration StudentConcentration { get; set; }
public virtual ICollection<Course> CoursesTaken { get; set; }
public virtual ICollection<Course> CoursesRecommended { get; set; }
}
and:
public class Course
{
public int ID { get; set; }
public string Semester { get; set; }
public Grade? Grade { get; set; }
public bool Selected { get; set; }
public BaseCourse BaseCourse { get; set; }
public Student Student { get; set; }
}
Something that may be important, but that I don't really understand: when I look at the table for the Course object in the database, there are three columns, called Student_ID, Student_ID1, and Student_ID2. I assume they relate to the student associated with the object and the two ways it can be associated (recommended or taken), but the odd thing is that Student_ID is always null, while the other two sometimes have a value and sometimes do not. I have not even begun to implement the recommendation process, so there is no way that list is being filled.
I reworked the classes and now it seems to be working. I changed the Course object to:
public class Course
{
public int ID { get; set; }
public string Semester { get; set; }
public Grade? Grade { get; set; }
public bool Selected { get; set; }
public int BaseCourseID { get; set; }
public int StudentID { get; set; }
public BaseCourse BaseCourse { get; set; }
public Student Student { get; set; }
}
and the controller methods to:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AddCourseVM (AddCourseViewModel vModel)
{
Student stu = db.Students.Find(vModel.Student.ID);
foreach (Course c in vModel.PossibleCourses)
{
if (c.Selected)
{
BaseCourse bc = db.BaseCourses.Find(c.BaseCourse.ID);
c.BaseCourse = bc;
c.Student = stu;
stu.CoursesTaken.Add(c);
db.Entry(c).State = EntityState.Added;
}
}
if (stu != null)
{
db.Entry(stu).State = EntityState.Modified;
db.SaveChanges();
}
return RedirectToAction("ListTakenCourses", stu);
}
public ActionResult ListTakenCourses (Student stu)
{
List<Course> taken = db.Courses.Where(c => c.StudentID == stu.ID).ToList();
foreach (Course c in taken)
{
c.BaseCourse = db.BaseCourses.Find(c.BaseCourseID);
c.Student = stu;
stu.CoursesTaken.Add(c);
}
ViewBag.CoursesTaken = taken;
return View(stu);
}
And it is now displaying the courses I add on the next page, but it seems odd that I have to save the child objects separately from the parent and that I have to get the list from the database manually instead of being able to use the object structure. Is this intended behavior, or is there a better way of doing what I'm trying to do (add a list of child objects (courses) to a student object, save the relationship to the database, and then display the list of added objects)?

You are not "passing the object to the next method". You are serializing the object and passing it on the URL, then deserializing it on the other end with this method:
return RedirectToAction("ListTakenCourses", stu);
This is not the way to go about things. What you should be doing is passing a single id, such as the student id. Then, in ListTakenCourses you look up the student again in the database, which if you are doing your query correctly will fully populate the objects.
return RedirectToAction("ListTakenCourses", new { id = stu.StudentID });
public ActionResult ListTakenCourses (int id)
{
List<Course> taken = db.Courses.Where(c => c.StudentID == id).ToList();
//...
}

Related

Entity Framework not tracking List

I'm using EF6 with ASP.Net. I'm trying to add items to the Jobs list in the following model:
EDIT:
My goal is to save the changes I make to the Timecards.Jobs list through a PUT method in such a way that I can retrieve them through a GET method.
public class Timecard
{
[Key]
public long TimecardID { get; set; }
[Required]
public DateTime StartDate { get; set; }
[Required]
public DateTime EndDate { get; set; }
[Required]
public long EmployeesID { get; set; }
[Required]
public decimal Hours { get; set; }
[Required]
public ICollection<int> Jobs { get; set; } = new List<int>();
public List<DateTime> Days { get; set; } = new List<DateTime>();
}
And I believe i'm doing so, i'm checking the states change in my PUT method:
// PUT: api/TimecardsAPI/5
[ResponseType(typeof(void))]
public IHttpActionResult PutTimecard(int id, Job job)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
try
{
Timecard card = db.Timecards.Where(x => x.TimecardID == id).First();
var state = db.Entry(card).State;
db.Timecards.Attach(card);
state = db.Entry(card).State;
card.Jobs.Add((int)job.JobID);
db.Entry(card).State = EntityState.Modified;
state = db.Entry(card).State;
var result = db.SaveChanges();
state = db.Entry(card).State;
var change = db.Timecards.Where(x => x.TimecardID == id).First().Jobs;
}
catch (DbUpdateConcurrencyException)
{
if (!TimecardExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return StatusCode(HttpStatusCode.NoContent);
}
Before returning from the put method, i have a var change to check the results of the Jobs list once i'm done working on it. Before I leave the put method, the changes to the Jobs list are accurate. However, when I do a get, I get all the correct data EXCEPT the list. It comes back as a 0 length list. Here's my get method, which also has the jobs list in a variable. This is where the list comes back as size 0:
// GET: api/TimecardsAPI
public IQueryable<Timecard> GetTimecards()
{
var change = db.Timecards.Where(x => x.TimecardID == 6).First().Jobs;
//In this example, six is the id of the timecard in question. Only hardcoded here
//for debugging.
return db.Timecards;
}
and my dbcontext:
public class ClockedWebContext : DbContext
{
public ClockedWebContext() : base("name=ClockedWebContext")
{
}
public DbSet<Job> Jobs { get; set; }
public System.Data.Entity.DbSet<ClockedWeb.Models.PayPeriod> PayPeriods { get; set; }
public System.Data.Entity.DbSet<ClockedWeb.Models.Employee> Employees { get; set; }
public System.Data.Entity.DbSet<ClockedWeb.Models.Timecard> Timecards { get; set; }
}
There are many similar questions on SO but I have not found information yet that has helped me solve my issue. I have no idea what I'm doing wrong, but I've lost days on this and I could really use some help. thank you.
Generally storing multiples values in column is an indication of poor database design. Relational databases are designed specifically to store one value per row/column combination. In order to store more than one value, you must serialize your list into a single value for storage, then deserialize it upon retrieval or you can use many-to-one relationship then you should use an extra table with a foreign key constraint. There is no other way to do so in RDMS.
If you use serialize approach, then your model look like--
public class Timecard
{
[Key]
public long TimecardID { get; set; }
[Required]
public DateTime StartDate { get; set; }
[Required]
public DateTime EndDate { get; set; }
[Required]
public long EmployeesID { get; set; }
[Required]
public decimal Hours { get; set; }
[NotMapped]
public List<int> JobList { get; set; } = new List<int>();
[Required]
public string Jobs
{
get => string.Join(",", JobList);
set
{
if (string.IsNullOrEmpty(value)) JobList = new List<int>();
else
{
JobList = !string.IsNullOrWhiteSpace(value) && value.Contains(",")
? value.Split(',').Select(s => Convert.ToInt32(s.Trim())).ToList()
: !string.IsNullOrWhiteSpace(value) && !value.Contains(",")
? new List<int>()
: new List<int>();
}
}
}
//have to change also
public List<DateTime> Days { get; set; } = new List<DateTime>();//Follow previous technique
}
Then you can do your operation as you doing. just it's insert data as a coma separated string.
I am not getting you correctly but if you not getting the update after you changed your entity then can you please add below line
db.savechanges();

List of same model MVC 4

I have a model enrollment that have a list of components that have a list of subcomponents. This is what i have:
public class Enrollment
{
...
public virtual int ClassroomId { get; set; }
public virtual Classroom Classroom { get; set; }
public virtual List<Components> Components { get; set; }
}
public class Component
{
...
public virtual int EnrollmentId { get; set; }
public virtual Enrollment Enrollment{ get; set; }
public virtual List<Subcomponent> Subcomponents { get; set; }
}
public class Subcomponent
{
...
public virtual int ComponentId { get; set; }
public virtual Component Component{ get; set; }
public virtual List<Subcomponent> Subcomponent { get; set; } (???)
}
The user needs to be able to create subsubcomponents and so on, as much as he wants. For example, the enrollment Matematics have a component Test that have a subcomponent Group1 that can have a subsubcomponent Question 1 and a subsubsubcomponent Question 1.1 and so on.
I have this create methods on the Subcomponent controller:
public ActionResult CreateMoreSubcomponents(int id)
{
var x = db.Subcomponents.FirstOrDefault(e => e.SubcomponentId == id);
if (x == null)
{
return HttpNotFound();
}
var subcomponent = new Subcomponent()
{
ComponentId = x.ComponentId,
SubcomponentId = x.SubcomponentId
};
return View("CreateMoreSubcomponents", subcomponent);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CreateMoreSubcomponents(Subcomponent subcomponent)
{
if (ModelState.IsValid)
{
db.Subcomponents.Add(subcomponent);
db.SaveChanges();
return RedirectToAction(...);
}
return View(subcomponent);
}
Note that this code may be all wrong, this was just a way i tried to do. The question is how can i do it? Am i close to the solution or not so close and if so how is it possible to do what i want? Thanks
EDIT:
I changed the code since it needs the componentId anyways. In this way it gives me an error on db.SaveChanges();
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_dbo.Subcomponents_dbo.Components_ComponentId". The conflict occurred in database "SGPContext-20160719133511", table "dbo.Components", column 'ComponentId'. The statement has been terminated

How to join my tables with identity tables?

I started a default MVC project with Identity and EF.
In my app users will be able to create and edit some records.
In the table for these records, I want to have the ids of users who created the record and who updated lastly.
My model class is like:
public class Record
{
public int ID { get; set; }
public DateTime CreateTime { get; set; }
public string CreatingUserID { get; set; }
public string UpdatingUserID { get; set; }
public DateTime UpdateTime { get; set; }
public Enums.RecordStatus Status { get; set; }
}
And in RecordsController, I save new records to db like this:
[Authorize]
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(FormCollection form, RecordCreateVM vm)
{
string userId = User.Identity.GetUserId();
DateTime now = DateTime.Now;
Record rec = new Record ();
if (ModelState.IsValid)
{
int newRecordId;
using (RecordRepository wr = new RecordRepository())
{
UpdateModel(rec);
rec.CreateTime = now;
rec.UpdateTime = now;
rec.CreatingUserID = userId;
rec.UpdatingUserID = userId;
rec.Status = Enums.RecordStatus.Active;
Record result = wr.Add(rec);
wr.SaveChanges();
newRecordId = result.ID;
}
}
}
When I am listing these records, I also want my page to display these users' usernames.
I get all the active records from the repository I created.
public ActionResult Index()
{
RecordListVMviewModel = new RecordListVM();
using (RecordRepository wr = new (RecordRepository())
{
viewModel.Records = wr.GetAll();
}
return View(viewModel);
}
And this is the repository code:
public class RecordRepository: Repository<Record>
{
public override List<Record> GetAll()
{
IQueryable<Record> activeRecords = DbSet.Where(w => w.Status == Enums.RecordStatus.Active);
return activeRecords.ToList();
}
}
Where do I have to make changes? Can you give me an sample code for usages like this?
Thank you.
You need to change
public string CreatingUserID { get; set; }
public string UpdatingUserID { get; set; }
to something like:
public User CreatingUser { get; set; }
public User UpdatingUser { get; set; }
Set the ID's during the creation of new RecordRepository()
Then access them as Record.CreatingUser.FirstName ect

ASP.NET MVC Relational Model Error

I have a two relational Model first one is
Teacher.cs
public class Teachers
{
[Key]
public int TeacherID { get; set; }
public string TeacherName { get; set; }
public string TeacherLname { get; set; }
public int DepartmentID { get; set; }
public string Image { get; set; }
public Department Department { get; set; }
}
and second is Department.cs
public int DepartmentID { get; set; }
public string DepartmentName { get; set; }
public string Image { get; set; }
public List<Teachers> Teachers { get; set; }
When I'm creating a new record, I' choose a Department Name for teacher, and It's adding fine. But When I want to Delete a record there is a error like this
The ViewData item that has the key 'DepartmentID' is of type 'System.Int32' but must be of type 'IEnumerable<SelectListItem>'.
Line 32: #Html.DropDownList("DepartmentID", String.Empty)
I don't understand what I need to do. Can you help me?
Thanks a lot
TeacherController
EDIT :
//
// GET: /Teachers/Delete/5
[Authorize(Roles = "A")]
public ActionResult Delete(int id = 0)
{
Teachers teachers = db.Teachers.Find(id);
if (teachers == null)
{
return HttpNotFound();
}
return View(teachers);
}
//
// POST: /Teachers/Delete/5
[Authorize(Roles = "A")]
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
Teachers teachers = db.Teachers.Find(id);
db.Teachers.Remove(teachers);
db.SaveChanges();
return RedirectToAction("Index");
}
When you pass an empty string into Html.DropDownList() it looks for a list of items to populate the dropdownlist from the first parameter in the ViewData collection. However, there is already an item in that collection that is of type Int32.
This is one of the many confusing scenarios that happen when you use Html.DropDownList() rather than using a strongly typed model and Html.DropDownListFor()
I suggest you do this:
#Html.DropDownListFor(x => x.DepartmentID, Model.Departments)
You will need to populate your model with a Departments object that is a list of Departments

LinqPad Query to Visual Studio - how to use a nested query to populate a viewmodel

This is a follow up to an earlier question.
I want to populate a ViewModel, which has 3 properties, and one list of Occ class (which also has 3 properties.
public class RatesViewModel
{
public string TypeName { get; set; }
public long TypeID { get; set; }
public int TypeCount { get; set; }
public virtual IQueryable<Occ> Occs { get; set; }
}
public class Occ
{
public string occ { get; set; }
public decimal ratetocharge { get; set; }
public int numOfOcc { get; set; }
public virtual RatesViewModel RatesViewModel { get; set; }
}
When I run the following Linq query in LinqPad:
var rooms = tblRoom
.GroupBy(p => p.tblType)
.Select(g => new
{
TypeName = g.Key.type_name,
TypeID = g.Key.type_id,
TypeCount = g.Count(),
Occs = rates.Where(rt => rt.type_id == g.Key.type_id &&
(
(rt.type_id == g.Key.type_id)
))
.GroupBy(rt => rt.occ)
.Select(proj => new
{
occ = proj.Key,
ratetocharge = proj.Sum(s => s.rate),
numOfOcc = proj.Count()
})
});
rooms.Dump();
...as before, it correctly returns the data model I'm looking for:
...and when I click on Occs it drills down into the Occs class:
The complete view in LinqPad is:
My query in Visual Studio is:
var rooms = dbr.Rooms
.GroupBy(p => p.RoomTypes).Select(g => new RatesViewModel
{
TypeName = g.Key.type_name,
TypeID = g.Key.type_id,
TypeCount = g.Count()
,
Occs = db.Rates.Where(rt => rt.type_id == g.Key.type_id &&
(
(rt.type_id == g.Key.type_id)
))
.GroupBy(rt => rt.occ)
.Select(proj => new Occ
{
occ = proj.Key,
ratetocharge = proj.Sum(s => s.rate),
numOfOcc = proj.Count()
})
})
.ToList();
However when running this, I get an error:
The specified LINQ expression contains references to queries that are associated with different contexts.
I think I understand the error - but I'm not sure how to separate the query into 2 separate queries, and then join those query results together again to get my original results set.
My model classes are:
public class Rates
{
public int id { get; set; }
public long type_id { get; set; }
public DateTime ratedate { get; set; }
public decimal rate { get; set; }
public string occ { get; set; }
public List<RoomType> Type { get; set; }
}
public class Rental
{
[Key]
public long rental_id { get; set; }
public long room_id { get; set; }
public DateTime check_in { get; set; }
public DateTime check_out { get; set; }
public virtual Room Room { get; set; }
}
public class Room
{
[Key]
public long room_id { get; set; }
public long type_id { get; set; }
public virtual RoomType RoomTypes { get; set; }
public virtual ICollection<Rental> Rentals { get; set; }
}
public class RoomType
{
[Key]
public long type_id { get; set; }
public string type_name { get; set; }
public IQueryable<Rates> Rates { get; set; }
public virtual ICollection<Room> Room { get; set; }
}
Can anyone help either review my query or models, so it works with one query, or show me how to separate the query into two, and then combine the result sets?
Thank you,
Mark
apitest.Models.RoomContext' does not contain a definition for 'Rates'...
(your comment on hydr's answer)
Well, there you go: not only two different context instances but two different context classes. I suspect your linqpad query was directly against the database connection, which means it used one linq-to-sql DataContext (created on the fly).
You need to use one context class (and one instance of it) in your query. And connect to it in Linqpad to make sure you test the same query provider as Visual Studio.
dbr and db seem to be two different instances of the same context. But in one query you should only use one context. So I would suggest the following:
Occs = dbr.Rates.Where(rt => rt.type_id == g.Key.type_id && ....
If this doesn't help can you quote the lines where you initialize the contexts?

Resources