Related table displaying nothing instead of name after attempting to look up name from related table - asp.net-core

EDIT:
Changed a couple of classes compared to what they are below to match the Microsoft guide on Relationships.
Devices.cs
public partial class Devices
{
public int DeviceType { get; set; }
...
public DeviceTypes DeviceTypes { get; set; }
}
DeviceTypes.cs
public partial class DeviceTypes
{
public int DeviceType { get; set; }
public string Name { get; set; }
public Devices Devices { get; set; }
}
I then changed my view to start reading off of DeviceTypes.Name.
Index.cshtml
#Html.DisplayFor(modelItem => item.DeviceTypes.Name)
dbContext.cs
modelBuilder.Entity<Devices>(entity =>
{
entity.HasKey(e => e.DeviceId);
...
entity.Property(e => e.DeviceType).HasComment("ID of the type of devices. Primary key in DeviceTypes table.");
...
entity
.HasOne(p => p.DeviceTypes)
.WithOne(p => p.Devices);
});
....
modelBuilder.Entity<DeviceTypes>(entity =>
{
entity.HasKey(e => e.DeviceType);
entity.HasComment("The types of devices.");
entity.Property(e => e.DeviceType)
.HasComment("Id of the device.")
.ValueGeneratedNever();
entity.Property(e => e.Name)
.IsRequired()
.HasMaxLength(50)
.HasComment("Name of the device.");
entity
.HasOne(b => b.Devices)
.WithOne(i => i.DeviceTypes)
.HasForeignKey("DeviceType");
});
I also added to the model builder, but got stuck again not understanding. I've spent a few more hours trying but I can't figure it out. The current error I get is:
InvalidOperationException: You are configuring a relationship between 'DeviceTypes' and 'Devices' but have specified a foreign key on 'DeviceType'. The foreign key must be defined on a type that is part of the relationship.
This is a question about using related tables in my CRUD editors in .NET Core 3.1. I want to use the Name instead of the ID as a display on the index/details/delete pages. And on the create/edit pages I want to use a dropdown to select the value from a list.
There are quite a few examples I have found, but nothing I have tried yet has worked. I had too many different ideas mixed in my code before so I tossed all changes and I am trying again, but got stuck.
I am starting simple with adding my model again and trying to simply get it to display in my paginated list. Currently, the paged list displays no value at all rather than a name or ID. Obviously I am trying to get the Name to display. Then I will move toward something more complicated like getting this value in a dropdown on the create page.
I know I am not figuring out the part about linking the data from the database back into my view model, but I have spent so many hours on this today and haven't quite found the missing piece. I have had so many variations on this that didn't work and this was trying to start by keeping it as simple as I could and then ask you all for advice.
Here is the code I am working with:
Devices.cs - DeviceType is really the ID
public partial class Devices
{
[MaxLength(10)]
public string DeviceId { get; set; }
public int DeviceType { get; set; }
...
[NotMapped]
public DeviceTypes DeviceTypes { get; set; }
}
DeviceTypes.cs - This is what I want to look up by DeviceType (int) and use the name in the editors
public partial class DeviceTypes
{
public int DeviceType { get; set; }
public string Name { get; set; }
}
DevicesViewModel.cs
public class DevicesViewModel
{
public Devices Devices { get; set; }
public PaginatedList<Devices> DevicesPagedList { get; set; }
}
DevicesController.cs - This is in the Index method where I get the data and then later what I am passing into my view
var devices = from dev in _context.Devices
select dev;
...
return base.View(new DevicesViewModel { DevicesPagedList = await PaginatedList<Devices>.CreateAsync(devices.AsNoTracking(), currentPage, resultsPerPage ?? 10), ResultsPerPageList = SelectListItemHelper.ResultsPerPageList().Where(l => l.Text != (resultsPerPage ?? 10).ToString()) });
}
Finally, here is where I am trying to display the value in Index.cshtml
#model DevicesViewModel
...
#foreach (var item in Model.DevicesPagedList)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.DeviceTypes.Name)
</td>
...

Related

Bootstrap Mvc.Grid for .NET MVC not accepting complex model

I am building a small Asp.NET MVC web application for document management system where I need to keep track of all the changes that happened with files and folders in jsTree (jQuery implementation of a tree control).
I have found Mvc.GRID Bootstrap control that enables (very easily) a grid representation of data. Let me describe what I did:
This is a model that partial view uses - List<List<HistoryVM>> and HistoryVM looks like this:
public class HistoryViewModel
{
[Display(Name="Name")]
string ItemName { get; set; }
[Display(Name="Original Path")]
public string ItemPath { get; set; }
[Display(Name="Type")]
public string ItemType { get; set; }
[Display(Name="Author")]
public string EventAuthorName { get; set; }
[Display(Name="Comment")]
public string EventActionDesc { get; set; }
[Display(Name="Timestamp")]
public DateTime DateOfEvent { get; set; }
[Display(Name="Action Type")]
public string ActionType { get; set; }
}
Controller looks like this:
[HttpGet]
public ActionResult ShowHistory(string path)
{
OperationService operations = new OperationService();
var historyRecord = operations.FindItemHistory(path);
if (historyRecord != null & historyRecord.Count() > 0)
{
return PartialView("../Shared/Partial/ShowHistory", historyRecord);
}
else
{
return PartialView("../Shared/Partial/ShowHistoryNoHistory");
}
}
And finally this is the view:
#model List<List<LaneWebApp.Models.HistoryViewModel>>
#using GridMvc.Html
#Html.AntiForgeryToken()
<div>
#Html.Grid(Model).Columns(c => {
c.Add(x => x.ElementAt(0).ItemType);
c.Add(x => x.ElementAt(0).ItemName);
c.Add(x => x.ElementAt(0).ItemPath);
c.Add(x => x.ElementAt(0).EventAuthorName);
c.Add(x => x.ElementAt(0).DateOfEvent.ToString());
c.Add(x => x.ElementAt(0).EventActionDesc);
})
</div>
Which gives me only the first element of each list. But I would like to have all history records, not just the first one.
How can I render this List of List in Grid.MVC?
It looks like current version of Bootstrap Grid.MVC does not support complex models - take a look at the official site.
So, I had to come up with my own view and sorting.

Multiple one to many relations of the same entity type code first

I have problems defining an entity that has two one-to-many relations (two lists)
public class Calendar
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public Guid ID { get; set; }
public virtual IList<Appointment> FreeSlots { get; set; }
public virtual IList<Appointment> AppointmentsList { get; set; }
...
}
public class Appointment
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int AppointmentID { get; set; }
public DateTime Start { get; set; }
public DateTime End { get; set; }
public String Type { get; set; }
public String Info { get; set; }
public Guid CalendarID { get; set; }
public virtual Calendar Calendar { get; set; }
}
And code-first code:
modelBuilder.Entity<Appointment>().HasKey(u => new {u.AppointmentID, u.CalendarID });
modelBuilder.Entity<Appointment>().HasRequired(u => u.Calendar).WithMany(c => c.FreeSlots).HasForeignKey(f => f.CalendarID).WillCascadeOnDelete(true);
modelBuilder.Entity<Appointment>().HasRequired(u => u.Calendar).WithMany(c => c.AppointmentsList).HasForeignKey(f => f.CalendarID).WillCascadeOnDelete(true);
Appointment has two PK because I want the appointment to be deleted if the calendar is deleted.
When I try to add a new appointment to the FreeSlot, I get the following error:
An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception.
I have tried with this mapping too with no luck:error 0040: Type Calendar_FreeSlots is not defined in the namespace Project.DAL (Alias=Self).
modelBuilder.Entity<Appointment>().HasKey(u => new {u.AppointmentID, u.CalendarID });
modelBuilder.Entity<Calendar>().HasMany(c => c.FreeSlots).WithRequired(c => c.Calendar).HasForeignKey(c => c.CalendarID).WillCascadeOnDelete();
modelBuilder.Entity<Calendar>().HasMany(c => c.AppointmentsList).WithRequired(c => c.Calendar).HasForeignKey(c => c.CalendarID).WillCascadeOnDelete();
I guess the problem is that I have two one-to-many relations to the same type of entity but I do not know the way to make it correctly.
The mistake in your mapping is that you use Appointment.Calendar twice for two different relationships. That's not possible. You would need a second pair of FK and navigation properties in Appointment (or map one relationship without inverse properties):
modelBuilder.Entity<Calendar>()
.HasMany(c => c.FreeSlots)
.WithRequired(c => c.Calendar1)
.HasForeignKey(c => c.Calendar1ID)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Calendar>()
.HasMany(c => c.AppointmentsList)
.WithRequired(c => c.Calendar2)
.HasForeignKey(c => c.Calendar2ID)
.WillCascadeOnDelete(true);
(For at least one relationship you must disable cascading delete. Otherwise you'll end up with a multiple cascading delete exception.)
However, I have the feeling that you actually should have only one relationship and collection Calendar.Appointments and a kind of status in Appointment, like a bool IsFree property. You could then always extract the free slots of a calendar entry with calendar.Appointments.Where(a => a.IsFree).

Posting DropDownList value from View to Controller in MVC4

I have a MVC4 application and although I have get parameters for my DropDownList from the database, I encounter some kind of problems while posting the DropDownList value to the database. There is lots of samples for different approach, but I would like to apply a method without using an extra approach i.e. Ajax, Javascript, etc. On the other hand, I have run into "FormCollection" to pass data, but I am not sure if FormCollection is the best way in this scene. Here are some part of the view, controller and model I use:
View:
#using (Html.BeginForm("Add", "Product", FormMethod.Post,
new { enctype = "multipart/form-data" }))
{
<p>Product Type : #Html.DropDownListFor(m => m.SelectedLookupId, new SelectList(Model.Lookups.Where(x => x.LookupType == "Product Type"), "LookupID", "LookupValue"), "--- Select ---") </p>
Controller:
[HttpPost]
public ActionResult Add(Product product)
{
if (ModelState.IsValid)
{
product.ProductType = // ??? Cannot get the SelectedLookupId
...
repository.SaveProduct (product);
TempData["message"] = string.Format("{0} has been saved", product.Name);
return View("Completed");
}
else
{
//there is something wrong with the data values
return View(product);
}
}
ViewModel:
public class ProductViewModel
{
public IEnumerable<Product> Products { get; set; }
public IEnumerable<Lookup> Lookups { get; set; } //Lookup for Product Types
public int SelectedLookupId { get; set; }
public Product Product { get; set; }
}
Thanks in advance for your helps.
Your action method should be receiving the view model, not the Product itself, like so:
[HttpPost]
public ActionResult Add(ProductViewModel productViewModel)
Unless I'm confused. But I assume the view snippet you posted above is from the Add view and that view's model is of type ProductViewModel. In your action method you are returning the Add view when the model state is invalid however you are passing a Product to that view. Again I may be confused because this should give you a runtime error that the types don't match.
Thanks for reply. Actually by using ViewModel rather than View, I have managed to solve the problem. On the other hand, after some research, I have applied another effective method in order to populate Dropdownlist without needing ViewModel. Furthermore with this example, I could use multiple foreign keys on the same Lookup table as shown below. Here is an an Applicant entity having 3 foreign keys and Lookup entity related to these keys. What I wanted to achieve with this example is exactly to use a Lookup table for only several Dropdownlist parameters i.e. Gender, Yes/No, Status,... due to no needing to create a table for the several parameters (these parameters are distinguished LookupType property on Lookup table). Here is the full example (I have shorted unrelated properties for brevity) below:
Applicant Entity:
public class Applicant
{
[Key]
public int ApplicantID { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
// for using "Multiple foreign keys within same table using Fluent API"
public int? HasDoneAnyProject { get; set; }
public int? IsInterestedAnyProgramme { get; set; }
public int? InterestedProgrammeId { get; set; }
public virtual Lookup PrimaryLookup { get; set; }
public virtual Lookup SecondaryLookup { get; set; }
public virtual Lookup TertiaryLookup { get; set; }
}
Lookup Entity:
public class Lookup
{
[Key]
public int LookupID { get; set; }
public string LookupType { get; set; }
public string LookupValue { get; set; }
// for using "Multiple foreign keys within same table using Fluent API"
public virtual ICollection<Applicant> PrimaryLookupFor { get; set; }
public virtual ICollection<Applicant> SecondaryLookupFor { get; set; }
public virtual ICollection<Applicant> TertiaryLookupFor { get; set; }
}
DbContext:
public class EFDbContext : DbContext
{
public DbSet<Applicant> Applicants { get; set; }
public DbSet<Lookup> Lookups { get; set; }
//for using "Multiple foreign keys within same table using Fluent API"
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Applicant>()
.HasOptional(b => b.PrimaryLookup)
.WithMany(a => a.PrimaryLookupFor)
.HasForeignKey(b => b.HasDoneAnyProject)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Applicant>()
.HasOptional(b => b.SecondaryLookup)
.WithMany(a => a.SecondaryLookupFor)
.HasForeignKey(b => b.IsInterestedAnyProgramme)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Applicant>()
.HasOptional(b => b.TertiaryLookup)
.WithMany(a => a.TertiaryLookupFor)
.HasForeignKey(b => b.InterestedProgrammeId)
.WillCascadeOnDelete(false);
}
}
Controller:
private void PopulateLookupsDropDownList(string lookupType, string foreignKey, object selectedLookups = null)
{
var lookupsQuery = repository.Lookups
.Select(x => x)
.Where(x => x.LookupType == lookupType)
.OrderBy(x => x.ParentLookupID).ToList();
ViewData[foreignKey] = new SelectList(lookupsQuery, "LookupID", "LookupValue", selectedLookups);
}
and for calling the Method for each of three Dropdownlist:
PopulateLookupsDropDownList("YesNo", "HasDoneAnyProject", applicant.HasDoneAnyProject);
PopulateLookupsDropDownList("YesNo", "IsInterestedAnyProgramme", applicant.IsInterestedAnyProgramme);
PopulateLookupsDropDownList("Programme", "InterestedProgrammeId", applicant.InterestedProgrammeId);
View: : Populating each of three Dropdownlist from the same Lookup table with different LookupType parameter:
<label>Has done any project before?</label>
#Html.DropDownList("HasDoneAnyProject", "---- Select ----")
<label>Are you interested in any programme?</label>
#Html.DropDownList("IsInterestedAnyProgramme", "---- Select ----")
<label>Interested programme name?</label>
#Html.DropDownList("InterestedProgrammeId", "---- Select ----")
I hope this approach will be useful for those who want to populate Dropdownlists from the same Lookup table. On the other hand, it is not only suitable for this, also can be used for populating Dropdownlists from different tables.
Regards.

Entity Framework 5 One to One relationship (e.g. User -> Profile) - ModelBuilder ASP.NET MVC4

I am trying to do a similar thing to what this previous answer had here:
How to declare one to one relationship using Entity Framework 4 Code First (POCO)
The problem is, im very new to this and am using Entity Framework 5 code first and the HasConstraint doesnt exist anymore, not to mention Im not good at lamda. I was wondering if anyone could help expand on this so I can map a User class to a Profile class effectively and easily? I need to know how to do this for the configuration files and model builder
Each user has one profile
Also, another quick question, say the profile model had Lists in this, how would I put these effectively in the model builder and configuration files?
Thank you
e.g.
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public Profile Profile { get; set; }
// public int ProfileId { get; set; }
}
public class Profile
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string PostalCode { get; set; }
}
// ...
modelBuilder.Entity<User>()
.HasOptional(x => x.Profile)
.WithRequired();
ProfileId is useless, FK is on the 'other side of the fence' (in Profile).
(this makes most sense IMO)
If you do need an Id in User (e.g. to be able to fill in Profile just by its ID when adding User - which if one-to-one is not really used - as you create both profile and user), then you can reverse...
modelBuilder.Entity<User>()
.HasRequired(x => x.Profile)
.WithOptional();
...and your ProfileId is actually in the Id (pk -> pk).
That solution worked for me
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ApplicationUser>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Id).HasMaxLength(450);
entity.HasOne(d => d.Profile).WithOne(p => p.User);
});
modelBuilder.Entity<UserProfile>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Id).HasMaxLength(450);
entity.Property(e => e.Type)
.HasMaxLength(10)
.HasColumnType("nchar");
entity.HasOne(d => d.User).WithOne(p => p.Profile);
});
}

LINQ-to-NHibernate: Cannot use Linq Skip() and Take() with FetchMany

I have these entities:
public class BlogPost {
public virtual int Id { get; set; }
public virtual IList<Keyword> Keywords { get; set; }
public virtual IList<BlogComment> Comments { get; set; }
}
public class BlogComment {
public virtual int Id { get; set; }
public virtual BlogPost Post { get; set; }
}
public class Keyword {
public virtual int Id { get; set; }
public virtual IList<BlogPost> BlogPosts { get; set; }
}
I want to load a paged-list of BlogPosts by their Keywords and comments-count. So I try this:
var entities = session.Query<BlogPost>()
.Where(t => t.Published)
.FetchMany(t => t.Keywords)
.OrderByDescending(t => t.UpdatedAt)
.Skip((pageNumber - 1) * pageSize).Take(pageSize)
.Select(t => new {
CommentsCount = t.Comments.Count(),
Post = t
})
.ToList();
But the folowing error occurs:
Specified method is not supported.
And when I remove .Skip((pageNumber - 1) * pageSize).Take(pageSize) it works! e.g.
var entities = session.Query<BlogPost>()
.Where(t => t.Published)
.FetchMany(t => t.Keywords)
.OrderByDescending(t => t.UpdatedAt)
// remove the below line
//.Skip((pageNumber - 1) * pageSize).Take(pageSize)
.Select(t => new {
CommentsCount = t.Comments.Count(),
Post = t
})
.ToList();
Have you any idea please to take a number of rows by including Keywords? Thanks for any suggestion.
I'm using NHibernate 3.2 mapping by code.
The problem is that the nhibernate linq provider isn't fully implemented yet.
You could move the skip / take calls to be after the ToList() but then you're going to be filtering on the entire result set rather than querying specifically for the records matching that range.
Alternatively you could use the QueryOver<> api which has proper support for Take and Skip as per this answer: https://stackoverflow.com/a/5073510/493
This should now be supported in 3.3.3.GA
http://sourceforge.net/p/nhibernate/news/2013/03/nhiberate-333ga-released/