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!
Related
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. :)
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>
}
}
Im converting ASP.NET Identity 2.0: Implementing Group-Based Permissions Management to ASP.NET Core , all is good, but i cannot get the role from groups
heres the code :
ApplicationGroup Entity
public sealed class ApplicationGroup
{
[Key]
public string Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public ICollection<ApplicationGroupRole> ApplicationGroupRoles { get; set; }
public ICollection<ApplicationGroupUser> ApplicationGroupUsers { get; set; }
public ApplicationGroup()
{
Id = Guid.NewGuid().ToString();
ApplicationGroupRoles = new List<ApplicationGroupRole>();
ApplicationGroupUsers = new List<ApplicationGroupUser>();
}
}
ApplicationGroupRole Entity
public class ApplicationGroupRole
{
public string ApplicationRoleId { get; set; }
public string ApplicationGroupId { get; set; }
[ForeignKey("ApplicationGroupId")]
public ApplicationGroup ApplicationGroup { get; set; }
}
ApplicationGroupUser Entity
public class ApplicationGroupUser
{
public string ApplicationUserId { get; set; }
public string ApplicationGroupId { get; set; }
[ForeignKey("ApplicationGroupId")]
public ApplicationGroup ApplicationGroup { get; set; }
}
DbContext OnModelCreating :
modelBuilder.Entity<ApplicationGroup>()
.HasMany(u => u.ApplicationGroupUsers);
modelBuilder.Entity<ApplicationGroupUser>()
.HasKey(r => new {r.ApplicationUserId, r.ApplicationGroupId});
modelBuilder.Entity<ApplicationGroup>()
.HasMany(g => g.ApplicationGroupRoles);
modelBuilder.Entity<ApplicationGroupRole>()
.HasKey(gr => new { gr.ApplicationRoleId, gr.ApplicationGroupId});
modelBuilder.Entity<ApplicationGroupUser>().ToTable("ApplicationUserGroups");
modelBuilder.Entity<ApplicationGroupRole>().ToTable("ApplicationRoleGroups");
modelBuilder.Entity<ApplicationGroup>().ToTable("ApplicationGroups");
Controller :
public ActionResult Details(string id)
{
if (id == null)
{
return new StatusCodeResult(400);
}
ApplicationGroup applicationgroup = _groupManager.Groups.FirstOrDefault(g => g.Id == id);
if (applicationgroup == null)
{
return new NotFoundResult();
}
var groupRoles = _groupManager.GetGroupRoles(applicationgroup.Id);
var RoleNames = groupRoles.Select(p => p.Name).ToArray();
ViewBag.RolesList = RoleNames;
ViewBag.RolesCount = RoleNames.Count();
return View(applicationgroup);
}
View :
#model ApplicationGroup
#{
ViewBag.Title = "Details";
}
<h2>Details</h2>
<div>
<h4>ApplicationGroup</h4>
<hr />
<dl class="dl-horizontal">
<dt>
#Html.DisplayNameFor(model => model.Name)
</dt>
<dd>
#Html.DisplayFor(model => model.Name)
</dd>
<dt>
#Html.DisplayNameFor(model => model.Description)
</dt>
<dd>
#Html.DisplayFor(model => model.Description)
</dd>
</dl>
</div>
<h4>List of permissions granted this group</h4>
#if (ViewBag.PermissionsCount == 0)
{
<hr />
<p>No users found in this role.</p>
}
<table class="table">
#foreach (var item in ViewBag.RolesList)
{
<tr>
<td>
#item
</td>
</tr>
}
</table>
<p>
#Html.ActionLink("Edit", "Edit", new { id = Model.Id }) |
#Html.ActionLink("Back to List", "Index")
</p>
The result is when i got to the view, theres no roles, please help fix to fix this, where do i got wrong ?
Screen Shoot
Update for solution:
Need to change the controller to this :
public ActionResult Details(string id)
{
if (id == null)
{
return new StatusCodeResult(400);
}
var applicationgroup = _groupManager.Groups
.Where(g => g.Id == id)
.Select(g => new ApplicationGroup()
{
ApplicationGroupRoles = g.ApplicationGroupRoles
}).FirstOrDefault();
if (applicationgroup == null)
{
return new NotFoundResult();
}
var groupRoles = _groupManager.GetGroupRoles(applicationgroup.Id);
var RoleNames = groupRoles.Select(p => p.Name).ToArray();
ViewBag.RolesList = RoleNames;
ViewBag.RolesCount = RoleNames.Count();
return View(applicationgroup);
}
and need to change some query in other class to :
var grp = _db.ApplicationGroups
.Where(y => y.Id == groupId)
.Select(g => new ApplicationGroup()
{
ApplicationGroupRoles = g.ApplicationGroupRoles
})
.FirstOrDefault();
In Identity 2 I did something similar to the group permissions project you referenced. Except I named them Profiles instead of Groups. I started to do the same thing in Identity Core but then realized there is a new Identity table named UserRoleClaim that I can use instead. With this new table I accomplished the same thing without having to make modifications to the Identity Core database schema. Also, I don't have to write my own EF code. Everything can be done with the built-in methods in the UserManager and RoleManager classes.
You can equate a group to a role now. Each role can have many claims and many users can belong to a role. The key to authorizing users is the claims that are related to the role(s) the user belongs to. You cannot use the claims directly in the authorize attribute (that would be messy). You have to associate claims to a policy. Policies are the awesomeness in Identity Core! They make it so you can organize your claims into profiles. Then you only have to add a profile to your Authorize attribute. Which is much better than adding a list of roles and/or claims like you had to in Identity 2.
The profiles are created with the services.AddAuthorization() method in the ConfigureServices() method of class Startup.
services.AddAuthorization(options =>
{
options.AddPolicy("Product", policy => policy.RequireClaim("Product"));
options.AddPolicy("ProductEdit", policy => policy.RequireClaim("Product", "Edit"));
});
You can use these policies in an Authorize attribute.
[Authorize(Policy = "Product")]
public class ProductController : Controller
{
[HttpGet]
public IActionResult Index()
{
return View();
}
[HttpGet]
[Authorize(Policy = "ProductEdit")]
public IActionResult Edit()
{
return View();
}
}
I would read up on Authorization in Identity Core before making any design decisions. The documentation is actually pretty good and easy to read. I suggest at least reading these articles.
Claims-Based Authorization
Policy-Based Authorization
I've described the basics of claims and policy based authorization in Identity Core. But follow the rest of the articles in the documentation I linked to above and you will see that there are many more options available to you.
Step 1 is to check your database directly, and make sure that you have some ApplicationGroupRole rows that have an ApplicationGroupId that corresponds to the Id guid that can be seen in that screenshot.
If you do, then your problem might be that you're not including the ApplicationGroupRoles in your query.
To include your ApplicationGroupRoles, you can use the Include Extension method. Make sure you have this using statement.
using System.Data.Entity
And then do your query like this
ApplicationGroup applicationgroup = _groupManager.Groups
.Include(g => g.ApplicationGroupRoles)
.FirstOrDefault(g => g.Id == id);
Just a note: You don't need Include statements, if you project your result onto a view model before you return from the db. I.E.
var VM = _groupManager.Groups
.Where(g => g.Id == id)
.Select(g => new MyGroupViewModel()
{
Roles = g.ApplicationGroupRoles
})
.FirstOrDefault();
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();
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");
}