ASP.NET MVC/EF Code First error: Unable to retrieve metadata for model - asp.net-mvc-4

I'm trying to create a controller in MVC4 and I'm getting an error I don't understand (I'm new to MVC). It says "Unable to retrieve metadata for 'CIT.ViewModels.DashboardViewModel'..." and then gives 2 possible problems. One is that the DashboardViewModel has no key defined. The other is that EntitySet 'DashboardViewModels' has no key defined.
I defined a key for DashboardViewModel, but that didn't solve the problem. Here is my DashboardViewModel;
public class DashboardViewModel
{
public DashboardViewModel() { }
[Key]
public int Id { get; set; }
public Hardware Hardware { get; set; }
public Software Software { get; set; }
public HardwareType HardwareType { get; set; }
public Manufacturer Manufacturer { get; set; }
public SoftwarePublisher SoftwarePublisher { get; set; }
}
As you can see it is composed of classes. I did this so I could have multiple classes accessible from the same view. I didn't think it needed a key, but I added one and that didn't fix the problem. The other error sounded like it was looking for a DbSet for DashboardViewModels. As I understand it, your DbSets are your tables. I don't want or need a DashboardViewModels table. I'm only doing that so I can have multiple tables/classes accessible in my view. That's working fine up to this point.
When I am trying to create the controller, I am using the DashboardViewModel as as my model and Context as my context. Here is my context:
public class Context : DbContext
{
public DbSet<Software> Softwares { get; set; }
public DbSet<Location> Locations { get; set; }
public DbSet<SoftwarePublisher> SoftwarePublishers { get; set; }
public DbSet<SoftwareType> SoftwareTypes { get; set; }
public DbSet<Hardware> Hardwares { get; set; }
public DbSet<Manufacturer> Manufacturers { get; set; }
public DbSet<HardwareType> HardwareTypes { get; set; }
}
How do I address these errors?

Related

The new ASP.NET Core 3.0 Json serializer is leaving out data

I'm porting a web application to ASP.NET Core 3, and after a bit of a battle, I'm almost at the finish line. Everything seems to work, but all of a sudden my JSON data returned from the api is missing some levels.
It seems the options.JsonSerializerOptions.MaxDepth is default at 64 levels, so it can be that. Some other places where an option can be playing tricks on me?
This is the code (and a quickview of the value):
And this is the JSON I get in the browser:
So the ParticipantGroups property/collection is completely missing in the generated output.
Any ideas where this happens?
EDIT:
I've added a repo on Github that showcases the issue. Standard ASP.NET Core 3.0 solution, created from the template, with a change to the result returned from the Weatherforecast controller:
https://github.com/steentottrup/systemtextjsonissue
For now I've gone back to using Newtonsoft.Json, with the Microsoft.AspNetCore.Mvc.NewtonsoftJson package. Then when I have some time, I'll try finding out what the solution is, without Newtonsoft.Json.
The problem seems to be an error in the new version 3.0. At least it seems like an error to me.
It seems System.Text.Json will convert the class mentioned in the hierarchy, not the actual class. So if you are using an abstract class in the hierarchy, you're in trouble. The second I removed the base class, and used the actual class I'm returning, the problem goes away it seems.
So this doesn't work:
public class SurveyReportResult {
public Guid Id { get; set; }
public String Name { get; set; }
public Int32 MemberCount { get; set; }
public IEnumerable<OrganisationalUnit> OrganisationalUnits { get; set; }
}
public abstract class OrganisationalUnit {
public Guid Id { get; set; }
public String Name { get; set; }
public Int32 MemberCount { get; set; }
}
public class OrganisationalUnitWithParticipantGroups : OrganisationalUnit {
public IEnumerable<ParticipantGroup> ParticipantGroups { get; set; }
}
public class ParticipantGroup {
public Guid Id { get; set; }
public String Name { get; set; }
public Int32 MemberCount { get; set; }
}
This will only return the properties of the OrganisationalUnit class, not the additional property of the OrganisationalUnitWithParticipantGroups.
This works:
public class SurveyReportResult {
public Guid Id { get; set; }
public String Name { get; set; }
public Int32 MemberCount { get; set; }
public IEnumerable<OrganisationalUnitWithParticipantGroups> OrganisationalUnits { get; set; }
}
public class OrganisationalUnitWithParticipantGroups /*: OrganisationalUnit*/ {
public Guid Id { get; set; }
public String Name { get; set; }
public Int32 MemberCount { get; set; }
public IEnumerable<ParticipantGroup> ParticipantGroups { get; set; }
}
public class ParticipantGroup {
public Guid Id { get; set; }
public String Name { get; set; }
public Int32 MemberCount { get; set; }
}

EF Core 2.2: migrating starts reporting that there are pending model changes for a context

Existing ASP.NET Core 2.1 app (running against netcore2.1) was migrated to ASP.NET Core 2.2 (installed the sdk and changed the target). Now, whenever I ran the app, it starts showing the traditional "There are pending model changes for ApplicationDbContext".
If I follow the instructions and try to add a migration, I've noticed that it does in fact generate a new migration file. By running a diff, I can see that it's adding these lines to the Application context snapshot:
modelBuilder.HasAnnotation("ProductVersion", "2.2.0-rtm-35687")
And it will also add the following to my entity:
b.Property<long?>("UserServiceId1");
b.Property<long?>("UserServiceServiceId");
b.Property<long?>("UserServiceUserId");
I'm not sure on where it gets the UserServiceId1 name (the entity has a UserServiceId property). Btw, here's the entity class code:
[Table("UserIdentifiers", Schema = "Gov")]
public class UserIdentifiers
{
[Required]
public long UserId { get; set; }
[Required]
public long ServiceId { get; set; }
[Required]
public long UserServiceId { get; set; }
[Required]
public long IdentifierId { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Required]
public long UserIdentifierId { get; set; }
public virtual Identifiers Identifier { get; set; }
public virtual UserServices UserService { get; set; }
}
The table that maps to this entity has a composite key built from the the UserId, ServiceId, UserServiceId, IdentifierId and UserIdentifierId. The snapshot has it defined like this:
b.HasKey("UserId", "ServiceId", "UserServiceId", "IdentifierId", "UserIdentifierId");
Oh, and yes, there are also migration files for dropping the UserServiceId column and adding the "new" UserServiceId1 column.
I'm not really an EF expert, so I'm not sure on why this stopped working after migrating from 2.1 to 2.2.
So, can anyone point me in the right direction?
btw, is there a way to disable migrations on ef core?
thanks
EDIT: adding the classes referenced by the UserIdentifiers entity (only showing the relations between classes):
// identifiers
[Table("Identifiers", Schema = "Gov")]
public class Identifiers
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Required]
public long IdentifierId { get; set; }
[Required]
public int IdentityResourceId { get; set; }
[Required]
public long ServiceId { get; set; }
public virtual Services Service { get; set; }
}
//Services
[Table("Services", Schema = "Gov")]
public class Services
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Required]
public long ServiceId { get; set; }
public virtual List<Identifiers> Identifiers { get; set; }
public virtual List<UserServices> UserServices { get; set; }
public virtual List<ClientServices> ClientServices { get; set; }
}
// userservices
[Table("UserServices", Schema = "Gov")]
public class UserServices
{
[Required]
public long UserId { get; set; }
[Required]
public long ServiceId { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Required]
public long UserServiceId { get; set; }
public virtual List<UserIdentifiers> UserIdentifiers { get; set; }
public virtual Services Service { get; set; }
public virtual ApplicationUser User { get; set; }
}
And finally, here's the configuration performed inside the OnModelCreating method:
builder.Entity<Identifiers>()
.HasKey(x => new { x.ServiceId, x.IdentifierId });
builder.Entity<UserIdentifiers>()
.HasKey(x => new { x.UserId, x.ServiceId, x.UserServiceId, x.IdentifierId, x.UserIdentifierId });
builder.Entity<UserServices>()
.HasKey(x => new { x.UserId, x.ServiceId, x.UserServiceId });
builder.Entity<ClientServices>()
.HasKey(x => new { x.ServiceId, x.ClientId, x.ClientServiceId });
A friend of mine solve it by adding the "missing" foreignkey info to the model:
[ForeignKey("ServiceId, IdentifierId")]
public virtual Identifiers Identifier { get; set; }
[ForeignKey("UserId, ServiceId, UserServiceId")]
public virtual UserServices UserService { get; set; }
And now everything works out as expected.
thanks again

Automapper and EF Navigation Properties

With ASP.NET MVC Core and Entity Framework Core I'm trying to create a simple website.
I've defined my Model:
public class Club
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public virtual IEnumerable<Team> Teams { get; set; }
}
public class Team
{
[Key]
public int Id { get; set; }
public int ClubId { get; set; }
[MaxLength(32)]
public string Name { get; set; }
public virtual Club Club { get; set; }
}
As well as the corresponding View Models:
public class ClubViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public virtual IEnumerable<TeamViewModel> Teams { get; set; }
}
public class TeamViewModel
{
public int Id { get; set; }
public int ClubId { get; set; }
public string Name { get; set; }
public virtual ClubViewModel Club { get; set; }
}
I've defined an Automapper Profile with the corresponding mappers:
CreateMap<Club, ClubViewModel>();
CreateMap<ClubViewModel, Club>();
CreateMap<Team, TeamViewModel>();
CreateMap<TeamViewModel, Team>();
I try to load a Club entity, with the navigation property Teams included (_context.Club.Include(c => c.Teams).ToList()). This works as expected, it returns a Club with a list of Teams. But when I try to map this instance to a ClubViewModel, I get an 502.3 error and my debug session is ended immediately.
It seems like I am missing something trivial, but I simply do not see it. There's no information in the Windows Event Log and I can't find any usefull information in the IIS Express logging (%userprofile%\documents\IISExpress)
What is causing the crash?
You can't perform this mapping because it is circular. You'll have to remove this line
public virtual ClubViewModel Club { get; set; }
from your TeamViewModel and the mapping should work as expected.

How do I Get Asp.net web api to join 2 tables (1 to many relation)

I am new to Web Api and just trying to learn by playing with different examples. I am completely stuck on trying to write a Get request to return a complex type. I have 3 entities, 1 of the entities has a list of another entity, So I am trying to figure out how to return the data from within both.
I looked at some examples on stack overflow, that showed to use the .Include linq statement, but when I try that, I am getting compiler errors (type argument cannot be inferred.
Basically, I have a class of Players, Teams and Specialties. Once I get this working, I am planning on writing an angular project. The specialties table is a multiselect for a given player.
Here is what I have written so far
public class Player
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int JerseyNo { get; set; }
public DateTime DateAquired { get; set; }
public string Bio { get; set; }
[ForeignKey("TeamID")]
public virtual Team Team { get; set; }
public virtual IEnumerable<Specialty> Specialites { get; set; }
}
public class Specialty
{
public int Id { get; set; }
public string Speciality { get; set; }
public virtual Player Player { get; set; }
}
public class Team
{
public int Id { get; set; }
public string TeamName { get; set; }
public virtual Player Player { get; set; }
}
public class dbContext :DbContext
{
public DbSet<Player> Players { get; set; }
public DbSet<Team> Teams { get; set; }
public DbSet<Specialty> Specialties { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder builder)
{
builder.UseSqlServer(#"Server=(localdb)\mssqllocaldb;Database=Test;Trusted_Connection=True;");
}
}
When I created the database using migrations, it looks how I want it to, but cannot figure out Web Api's joins to get the data from my specialties table. The .Include cannot recognize any value I enter as parameters
private dbContext db = new dbContext();
// GET: api/values
[HttpGet]
public IEnumerable<Player> Get()
{
var teams = db.
Players
.Include("Specialties")
.Select(p=> new Player
Looks like this an Entity Framework question.
Try if you can get this to work, for debugging purpose:
var teams = db.Players.ToList();
foreach (var player in teams)
{
// Force lazy loading of Specialities property
player.Specialities.ToList();
}
If this doesn't work, it looks like EF cannot figure out the mapping to the database.

ASP.NET MVC4: How to pass a view model and other data to the view

I have created a ViewModel called DashboardViewModel:
public class DashboardViewModel
{
public Hardware Hardware { get; set; }
public Software Software { get; set; }
}
I am passing the ViewModel to the view in my ActionResult. But I need to pass other things too. Here is my ActionResult:
public ActionResult Index()
{
HardwareType hwt = new HardwareType { HType = "PC" };
IEnumerable<Hardware> Pcs = db.Hardware.Where(h => h.HardwareType.Contains(hwt));
DashboardViewModel dvm = new DashboardViewModel();
return View(dvm);
}
How do I pass Pcs to the view if I am already passing dvm? I don't even know if this is the right approach. What I am trying to accomplish is to create navigation on the page. So not only will I have PCs, but I'll have monitors and printers to pass to the view, as well as software. Here is my hardware class:
public class Hardware
{
public int Id { get; set; }
public virtual ICollection<DeviceType> Type { get; set; }
public string AssetTagId { get; set; }
public virtual ICollection<Manufacturer> Manufacturer { get; set; }
[Required]
[StringLength(50)]
public string ServiceTagId { get; set; }
[Required]
[StringLength(50)]
public string SerialNumber { get; set; }
[Required]
[StringLength(75)]
public string ProductNumber { get; set; }
// [Required]
[StringLength(20)]
public string PurchaseDate { get; set; }
[StringLength(20)]
public string WarrantyExpiration { get; set; }
[Required]
[StringLength(20)]
public string WarrantyType { get; set; }
public virtual ICollection<Location> Location { get; set; }
public virtual ICollection<HardwareType> HardwareType { get; set; }
[Required]
[StringLength(2000)]
public string Notes { get; set; }
public string POATag { get; set; }
}
What is the best approach for what I want to do (creating the navigation with various categories of hardware and software)? I'm new to MVC and am trying to follow suggestions on what to do, but I could use a higher level approach as maybe I'm going about this all wrong. Thanks.
You can put your Pcs in ViewBag or ViewData as below:
public ActionResult Index()
{
HardwareType hwt = new HardwareType { HType = "PC" };
IEnumerable<Hardware> Pcs = db.Hardware.Where(h => h.HardwareType.Contains(hwt));
ViewBag.Pcs=Pcs;//or ViewData["Pcs"]=Pcs;
DashboardViewModel dvm = new DashboardViewModel();
return View(dvm);
}
ViewBag is the dynamic object. You can add anything to it. With any name e.g. yous Pcs can also be stored in ViewBag as ViewBag.AnyNameYouLike=Pcs;
**RAZOR SYNTAX:**
Just apply loop and you are done.
#foreach(var pc in ViewBag.Pcs)
{
#pc.Id;//Will give you id
}
You can loop through all properties like this
Create a top level view-model - like you have DashboardViewModel - and add all the necessary models as Properties.
It would be good if you created view-models for each business model required in that top level view-model.
Auto-map the business objects to the new view-models - see AutoMapper for one example. That way you are only passing the information the view actually requires.