display datas from one-to-many relation .net - c#

first of all im real beginner with c# and .net i started 10 days ago so
im trying to display datas from a one-to-many relation but i have an error System.NullReferenceException : 'Object reference not set to an instance of an object.(i checked db there are datas in this table) and i dont understand why. I read many post about this but it never works, my code is almost the same than the example on the microsoft doc page but mine doesnt work.
model
[Display(Name = "Formations")]
public virtual ICollection<Courses> Course { get; set; }
}
public class Courses
{
[Key]
public int CourseId { get; set; }
[Display(Name = "Formations")]
public string Course { get; set; }
public virtual Users users { get; set; }
}
Controller
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
var users = await _context.Users
.FirstOrDefaultAsync(m => m.Id == id);
if (users == null)
{
return NotFound();
}
return View(users);
View
#foreach (var item in Model.Course)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Course)
</td>
</tr>
}

I asume that your model is of type User.
When querying for an entity (User), related entities (Courses) are not read automatically from the database, therefore your collection is/was null.
You can change that by using Include() in your Linq query: await _context.Users.Include(u => u.Courses).FirstOrDefaultAsync(m => m.Id == id);
Just some small additional suggestions to improve your code, which helps you understand what's going on:
Rename your Courses class to Course since your class only represent one single course.
Rename the Course property to Courses since it is a collection of many objects of type Course
Initialize your course collection with an empty list/hash set like so public virtual public virtual ICollection<Courses> Course { get; set; } = new HashSet<Course>(); to avoid a null reference exception
In your view, rename item to course. "Foreach (single) course in (many) courses" is much better to read and unserstand

fix the action
var users = await _context.Users.Include(i=>i.Course)
.FirstOrDefaultAsync(m => m.Id == id);
since your are usind displayfor you will have to replace foreach loop by for loop
#model User
....
#if (Model.Course!=null)
{
#for (var i=0; i<= Model.Course.Count; i+=1)
{
<tr>
<td>
#Html.DisplayFor(model=> model.Course[i].Course)
</td>
</tr>
}
}

Related

Entity Framework loading some, none or all navigation properties dynamically

Looking at this documentation I can see that you can load multiple navigation entities using the following syntax:
using (var context = new DbContext())
{
var userDocs = context.UserDocuments
.Include(userDoc => userDoc.Role.User)
.ToList();
}
This will give me Role and User navigation properties hung off my UserDocument object, however if I want to use the string overload of Include, how might I construct the code to handle multiple includes?
This does not work:
return await ctx.UserDocuments.Where(x => x.UserId == userId)
.Include("Role.User").ToList();
I am trying to do it this way as my methods may want some, all or no navigation properties returned depending on the calling code. My intention is to add a string array to the repository method which will build any required navigation properties accordingly. If this is the wrong approach, does anyone have another recommendation, I'm wondering if lazy loading would be a more elegant solution...?
Edit
This is the Entity which has nav props:
public partial class UserDocument
{
public int Id { get; set; }
public Guid UserId { get; set; }
public int RoleId { get; set; }
public int AccountId { get; set; }
public virtual Role Role { get; set; } = null!;
public virtual User User { get; set; } = null!;
}
I think you are looking for something like this:
public async Task<List<UserDocument>> MyMethod(List<string> propertiesToInclude)
{
IQueryable<UserDocument> currentQuery = _context.UserDocuments.Where(x => x.UserId == userId);
foreach(var property in propertiesToInclude)
{
currentQuery = currentQuery.Include(property);
}
return await currentQuery.ToListAsync();
}
If you're using the Include(String) method, you don't need to include the lambda to specify the property path.
Instead of doing .Include(x => "Role.User"), try .Include("Role.User")
First of all, you must told us what is "Role.User"?
We cannot answer you if we don't know excactly what you wrote.
So, now we are know that are two differents entities you can do this one
var userDocs = await context.UserDocuments
.Include(x => x.Role)
.ThenInclude(x => x.User)
.ToListAsync();
I hope this one helps you. :)

Multiple query results on one view

I'm trying to learn C#.net and figured with all the hype around .net core I'd start there, but I'm a little out of my depth.
I have a list of (lets say 'Countries') from a database. I click on a country and it shows me details of the item. This works:
// GET: Database/Details/00000000-0000-0000-0000-000000000000
public async Task<IActionResult> Details(Guid? id)
{
if (id == null)
{
return NotFound();
}
var country = await _context.countries.SingleOrDefaultAsync(s => s.Id == id);
if (subscription == null)
{
return NotFound();
}
return View(country);
}
On the details view, I want to show two tables.
The first being 'country', which will show generic information (Capital, Currency, Continent). I can currently do this fine.
The second being a list of cities (from a table called 'Cities'). How would I do this? I can't work out how to return a second result set to the view.
(Hope the analogy helped explain it!)
You need to return a ViewModel. So create a class IndexViewModel or how your action is called (this is just a best practice, you can name it how you want) and add 2 properties:
public class IndexViewModel
{
public Country Country { get; set; }
public List<City> Cities { get; set; }
}
Then in your controller return the model:
// GET: Database/Details/00000000-0000-0000-0000-000000000000
public async Task<IActionResult> Details(Guid? id)
{
if (id == null || subscription == null)
{
return NotFound();
}
var model = new IndexViewModel();
model.Country = await _context.countries.SingleOrDefaultAsync(s => s.Id == id);
model.Cities = SomeFunction();
return View(model);
}
In your View add a reference at the top of the document:
#model IndexViewModel
And, you can access the data by:
#Model.Country
#Model.Cities
You will need to create a ViewModel that gives you a place to store both lists and pass that to your view. Example below.
public class MyViewModel{
public IEnumerable<Country> Countries { get; set; }
public IEnumerable<City> Cities { get; set; }
}

Updating related Data with Entity Framework 6 in MVC 5

Using EntityFramework 6, I would like to update Customer in the following scenario:
public class Customer
{
public int Id {get; set;}
// 100 more scalar properties
public virtual IList<Consultant> Consultants {get;set;}
}
public class Consultant
{
public int Id {get; set;}
public virtual IList<Customer> Customers{get;set;}
}
This is my ViewModel for the edit view:
public class CustomerViewModel
{
public string[] SelectedConsultants { get; set; }
public IEnumerable<Consultants> AllConsultants{ get; set; }
public Customer Customer{ get; set; }
}
This is my Edit-ActionMethod:
[HttpPost]
public ActionResult Edit(CustomerViewModel vm)
{
if (ModelState.IsValid)
{
// update the scalar properties on the customer
var updatedCustomer = vm.Customer;
_db.Customers.Attach(updatedCustomer );
_db.Entry(updatedCustomer ).State = EntityState.Modified;
_db.SaveChanges();
// update the navigational property [Consultants] on the customer
var customer = _db.Customers
.Include(i => i.Customers)
.Single(i => i.Id == vm.Customer.Id);
Customer.Consultants.Clear();
_db.Consultants.Where(x => vm.SelectedConsultants
.Contains(x.Id)).ToList()
.ForEach(x => customer.Consultants.Add(x));
_db.Entry(customer).State = EntityState.Modified;
_db.SaveChanges();
return RedirectToAction("Index");
}
return View(vm);
}
This works and both scalar properties and consultants are updateable from the edit view. However, I am doing two _db.SaveChanges(); in my controller. Is there a less complex way to update Customer? Because Customer has many properties, I'd preferably not do a manual matching of all parameters on Customer and vm.Customer.
I have found the following resources:
asp.net official seems overly complicated (see section Adding
Course Assignments to the Instructor Edit Page) plus would require
me to explicitly write all parameters of Customer)
this popular thread on SO. Method 3 looks like what I need but I could not get the navigational property updated.
I don't think it's necessary to call the SaveChanges twice.
Have you tried something like this:
var customer = _db.Customers
.Where(c => c.Id== vm.Customer.Id)
.Include(c => c.Consultants)
.SingleOrDefault();
customer.Consultants = _db.Consultants
.Where(x => vm.SelectedConsultants.Contains(x.Id)).ToList();
_db.SaveChanges();
Edit:
Ok, not sure if this will work, but you can try using Automapper:
var customer = Mapper.Map<Customer>(vm.Customer);
_db.Entry(customer).State = EntityState.Modified;
customer.Consultants = _db.Consultants.Where(x => vm.SelectedConsultants.Contains(x.Id)).ToList();
_db.SaveChanges();

Lazy loading not working when trying to access related entity

This is microsoft's scaffolding code for the action Details of the entity MyEntity:
public async Task<ActionResult> Details(Guid? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
MyEntity myEntity = await db.MyEntities.FindAsync(id);
if (myEntity == null)
{
return HttpNotFound();
}
return View(myEntity);
}
Now let's say I want to display the name of the owner of this entity in details.cshtml, if I write the following code:
<dt>
Owner
</dt>
<dd>
#Html.DisplayFor(m => m.User.FullName)
</dd>
User shows up as null, even after trying to access Model.User to trigger the lazy loading.
Edit: Adding the model as requested
public class MyEntity
{
public Guid? Id { get; set; }
public string Name { get; set; }
public ApplicationUser User { get; set; }
}
Add .Include("User") to your linq query.
I was running into the same issue, and Matt's answer led me down a similar road. I thought I'd share what I found.
This article indicates that Lazy Loading doesn't fit well with the Async pattern. Good to know.
With that, I looked into eager loading. I found my answer there.
MyEntity myEntity = await db.MyEntities.Where(m => m.Id == id)
.Include(m => m.User)
.FirstOrDefaultAsync();
Hope that helps someone else!

MVC 4 Edit Controller/ View Many to Many relation and checkboxes

I'm working with ASP.NET MVC 4 and Entity Framework and I was searching for some way to make many to many relation and checkboxes from my db for a Create/Edit controller and view, I have found the answer with #Slauma answer for Create in MVC 4 - Many-to-Many relation and checkboxes but, I'd really like to see how this extends to Edit and Delete functionality as well like some other partners in this solution. Could someone please show how I would populate the ClassificationSelectViewModel in the Edit controller method to get both the "checked" and "unchecked" values? this is a Matt Flowers question that will solve mine too.
The following is a continuation of this answer that describes Create GET and POST actions for a model with many-to-many relationship between entities Subscription and Company. Here is the procedure for the Edit actions how I would do it (except that I probably wouldn't put all the EF code into the controller actions but extract it into extension and service methods):
The CompanySelectViewModel remains unchanged:
public class CompanySelectViewModel
{
public int CompanyId { get; set; }
public string Name { get; set; }
public bool IsSelected { get; set; }
}
The SubscriptionEditViewModel is the SubscriptionCreateViewModel plus the Subscription's key property:
public class SubscriptionEditViewModel
{
public int Id { get; set; }
public int Amount { get; set; }
public IEnumerable<CompanySelectViewModel> Companies { get; set; }
}
The GET action could look like this:
public ActionResult Edit(int id)
{
// Load the subscription with the requested id from the DB
// together with its current related companies (only their Ids)
var data = _context.Subscriptions
.Where(s => s.SubscriptionId == id)
.Select(s => new
{
ViewModel = new SubscriptionEditViewModel
{
Id = s.SubscriptionId
Amount = s.Amount
},
CompanyIds = s.Companies.Select(c => c.CompanyId)
})
.SingleOrDefault();
if (data == null)
return HttpNotFound();
// Load all companies from the DB
data.ViewModel.Companies = _context.Companies
.Select(c => new CompanySelectViewModel
{
CompanyId = c.CompanyId,
Name = c.Name
})
.ToList();
// Set IsSelected flag: true (= checkbox checked) if the company
// is already related with the subscription; false, if not
foreach (var c in data.ViewModel.Companies)
c.IsSelected = data.CompanyIds.Contains(c.CompanyId);
return View(data.ViewModel);
}
The Edit view is the Create view plus a hidden field for the Subscription's key property Id:
#model SubscriptionEditViewModel
#using (Html.BeginForm()) {
#Html.HiddenFor(model => model.Id)
#Html.EditorFor(model => model.Amount)
#Html.EditorFor(model => model.Companies)
<input type="submit" value="Save changes" />
#Html.ActionLink("Cancel", "Index")
}
The editor template to select a company remains unchanged:
#model CompanySelectViewModel
#Html.HiddenFor(model => model.CompanyId)
#Html.HiddenFor(model => model.Name)
#Html.LabelFor(model => model.IsSelected, Model.Name)
#Html.EditorFor(model => model.IsSelected)
And the POST action could be like this:
[HttpPost]
public ActionResult Edit(SubscriptionEditViewModel viewModel)
{
if (ModelState.IsValid)
{
var subscription = _context.Subscriptions.Include(s => s.Companies)
.SingleOrDefault(s => s.SubscriptionId == viewModel.Id);
if (subscription != null)
{
// Update scalar properties like "Amount"
subscription.Amount = viewModel.Amount;
// or more generic for multiple scalar properties
// _context.Entry(subscription).CurrentValues.SetValues(viewModel);
// But this will work only if you use the same key property name
// in ViewModel and entity
foreach (var company in viewModel.Companies)
{
if (company.IsSelected)
{
if (!subscription.Companies.Any(
c => c.CompanyId == company.CompanyId))
{
// if company is selected but not yet
// related in DB, add relationship
var addedCompany = new Company
{ CompanyId = company.CompanyId };
_context.Companies.Attach(addedCompany);
subscription.Companies.Add(addedCompany);
}
}
else
{
var removedCompany = subscription.Companies
.SingleOrDefault(c => c.CompanyId == company.CompanyId);
if (removedCompany != null)
// if company is not selected but currently
// related in DB, remove relationship
subscription.Companies.Remove(removedCompany);
}
}
_context.SaveChanges();
}
return RedirectToAction("Index");
}
return View(viewModel);
}
The Delete actions are less difficult. In the GET action you could load a few subscription properties to display on the delete confirmation view:
public ActionResult Delete(int id)
{
// Load subscription with given id from DB
// and populate a `SubscriptionDeleteViewModel`.
// It does not need to contain the related companies
return View(viewModel);
}
And in the POST action you load the entity and delete it then. There is no need to include the companies because in a many-to-many relationship (usually) cascading delete on the link table is enabled so that the database will take care of deleting the link entries together with the parent Subscription:
[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirm(int id)
{
var subscription = _context.Subscriptions.Find(id);
if (subscription != null)
_context.Subscriptions.Remove(subscription);
return RedirectToAction("Index");
}

Categories