This is a bit of a long winded question. I've been going through a lot trying to learn the ins and outs of EF6 and MVC5 at the same time, so I apologize if anything I'm saying is not making sense or is plain old wrong. (please let me know if it is!)
The problem is arising when I'm trying to scaffold CRUD pages for any models that have foreign keys to the pre-made AspNetUsers table. (This and a few other tables are already part of the DB if you choose a new MVC5 project with authentication.)
I've been able to successfully scaffold fully working CRUD pages for all my models that don't include links to the Users table. I'm wondering if it's something I've misunderstood, or if something I've done is messing this up.
I've been reading a lot of answers to questions here on SA for problems that are similar to mine, but to no avail. It seems to me like such a simple thing should be easier, but I've been struggling with this for days now.
To try to isolate the problem, I've started a new MVC project with authentication and done nothing other than add my models file, migrate the DB to add the tables and try to scaffold the pages. The scaffolding itself completes successfully, but adds this line to the IdentityModels.cs file:
public System.Data.Entity.DbSet<TestWhatever.Models.ApplicationUser> IdentityUsers { get; set; }
which is not correct (learned that from another SA thread). There should only be user generated dbsets in this file.
Upon running the app, I get the following error:
Multiple object sets per type are not supported. The object sets 'IdentityUsers' and 'Users' can both contain instances of type 'TestWhatever.Models.ApplicationUser'.
Edit: It was suggested below that I simply remove the generated IdentityUsers line - however doing that causes compiler errors in the generated CRUD pages. Something is not going right here, I'm starting to think that EntityFramework doesn't know how to use its own UserManager to display and update Users. ::
Is what I'm doing along the right path? Am I not supposed to be using the inbuilt tables for user auth? If not, why are they there? Any insight is appreciated, I've been finding this all very confusing as any documentation or answered questions I find are never covering quite the same topic. Thanks.
The tables that are giving me problems look like this:
public class ExamInProgress
{
[Key]
public int ProgressId { get; set; }
[Required]
[Display(Name = "User")]
public string UserId { get; set; }
[ForeignKey("UserId")]
public ApplicationUser User { get; set; }
[Required]
[Display(Name = "Exam")]
public int ExamId { get; set; }
public virtual Exam Exam { get; set; }
}
public class CompletedExam
{
[Key]
public int CompletedExamId { get; set; }
[Required]
[Display(Name = "Date Completed")]
public DateTime DateCompleted { get; set; }
[Required]
[Display(Name = "Final Score")]
public decimal FinalScore { get; set; }
[Required]
[Display(Name = "Exam Name")]
public string ExamName { get; set; }
[Required]
[Display(Name = "User")]
public string UserId { get; set; }
[ForeignKey("UserId")]
public ApplicationUser User { get; set; }
public virtual Exam Exam { get; set; }
}
Another example of the tables I'm using: (there are more but mainly just ints and strings)
public class Exam
{
[Key]
public int ExamId { get; set; }
[Required]
[StringLength(100, ErrorMessage = "Exam name cannot be longer than 100 characters.")]
[Display(Name = "Exam Name")]
public string ExamName { get; set; }
[Display(Name = "Exam Description")]
public string ExamDescription { get; set; }
public virtual ICollection<Module> Modules { get; set; }
}
public class Question
{
[Key]
public int QuestionId { get; set; }
[Required]
[Display(Name = "Question Text")]
public string QuestionText { get; set; }
[Display(Name = "Question Order Index")]
[Range(0, int.MaxValue, ErrorMessage = "Index can not be negative")]
public int? QuestionOrderIndex { get; set; }
[Required]
[Display(Name = "Question Type")]
public int QuestionTypeId { get; set; }
[Required]
[Display(Name = "Module")]
public int ModuleId { get; set; }
public virtual QuestionType QuestionType { get; set; }
public virtual Module Module { get; set; }
public virtual ICollection<Answer> Answers { get; set; }
}
public class QuestionType
{
[Key]
public int QuestionTypeId { get; set; }
[Required]
[StringLength(60, ErrorMessage = "QuestionType Name cannot be longer than 60 characters.")]
[Display(Name = "QuestionType Name")]
public string QuestionTypeName { get; set; }
[Display(Name = "QuestionType Description")]
public string QuestionTypeDescription { get; set; }
public virtual ICollection<Question> Questions { get; set; }
}
The error you're getting is due to having added the IdentityUsers property to your context. Your context is inheriting from IdentityDbContext<ApplicationUser>, and that class already has the following property:
public IDbSet<TUser> Users { get; set; }
Where TUser is the type passed in the generic, ApplicationUser. Adding your own DbSet for ApplicationUser creates two methods of access, and you get that error. Remove your property and you should be good.
Related
I have a simple problem with my site.
Inside my site, I'm using two different models, with some identical named fields.
Because of collision, I have to give them unique names and for not loosing the modelbinding, I decided to use viewModels.
So I have a Model like this:
namespace MySite.Models
{
public class Function : BaseEntity
{
//Beziehung zur Funktionsgruppe
[Required]
[Display(Name = "Übergeordnete Funktion")]
public int FunctionGroupId { get; set; }
public virtual FunctionGroup FunctionGroup { get; set; }
[Required]
[StringLength(200)]
[Display(Name = "Bezeichnung")]
public string Name { get; set; }
}
}
And I have a new ViewModel like this:
namespace MySite.ViewModels
{
public class FunctionViewModel
{
//Properties of BaseEntity
public int F_Id { get; set; }
[Display(Name = "Erstellt")]
public string F_Created { get; set; }
[Display(Name = "Bearbeitet")]
public string F_LastChange { get; set; }
[Display(Name = "Bearbeiter")]
public string F_ByUser { get; set; }
//Beziehung zur Funktionsgruppe
[Required]
[Display(Name = "Übergeordnete Funktion")]
public int F_FunctionGroupId { get; set; }
public virtual FunctionGroup F_FunctionGroup { get; set; }
[Required]
[StringLength(200)]
[Display(Name = "Bezeichnung")]
public string F_Name { get; set; }
}
}
Now my Question is, is there a way to automatically fetch the data of the corresponding model, while loading the ViewModel?
Something like a kind of function directly inside the { get; set; }?
Actually I load field by field form the model into the ViewModel.
Hope that I could have described Right, what I'd like to do.
Carsten
You can use Automapper for mapping or getting your data from Model to ViewModel or vice-versa. It will be hard for you to map/configure if your name of your property is different so have the same name in view model as well. (If possible)
Ok so I'm adding on to the Simplemembership.
Model UsersProfiles
namespace OilNGasWeb.Models
{
[Table("Users")]
public class UserProfiles
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string MiddleName { get; set; }
public string Initials { get; set; }
public string Company { get; set; }
public string Department { get; set; }
public string Title { get; set; }
public string Team { get; set; }
public string TeamSub { get; set; }
public string Phone { get; set; }
public string ImageLocation { get; set; }
public string CurrentlyAuthorized { get; set; }
public string Note { get; set; }
public string Comment { get; set; }
//public virtual dbClient Client { get; set; }
public virtual ICollection<Roles> Roles { get; set; } //many to many
public virtual ICollection<dbClient> Clients { get; set; } // many to many
}
}
Roles
namespace OilNGasWeb.Models
{
[Table("webpages_Roles")]
public class Roles
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
[Required(ErrorMessage = "Required")]
public int RoleID { get; set; }
[Required(ErrorMessage = "Required")]
public string RoleName { get; set; }
public virtual ICollection<UserProfiles> UserProfiles { get; set; } //many to many
}
}
My issue now that i have it creating the many to many tables like i saw it creat before modifications my question is how to get those tables Renamed
webpages_UsersInRoles
I would prefer not to go into SSMS and change them physically rather tell MVC to use a different instance
From the code above EF produced RolesUserProfiles instead of webpages_UsersInRoles
The error shows when the program is trying to #if (User.IsInRole("Administrator")) validade user.
Naturally I hit F12 on IsInRole to bring me to the definition....
it does but there all empty
Now what ? how can i recode if its hidden from me ? where is the code at , and how can i Modify this?
What i would like out of all this is
either renaming the tables ManytoMany as they are being created
being able to modify the code that looks for webpages_UsersInRoles
Thanks in advance.
You cannot rename the tables. The table names are hard coded in SimpleMembership. You can see the source code here:
http://aspnetwebstack.codeplex.com/SourceControl/latest#src/WebMatrix.WebData/SimpleMembershipProvider.cs
Don't use the EF navigational properties. You should be accessing this information via the Membership or WebSecurity API's.
If you really want to do this, then you will need to configure EF to use the tablenames required by simple membership, which means utilizing the fluent mapping syntax.. which is not exactly intuitive.
I have the following model for an article.
public class Article
{
[Key]
public int ArticleId { get; set; }
[Required(ErrorMessage = "Title is required."), MaxLength(80)]
public string Title { get; set; }
[Required(ErrorMessage = "Body is required.")]
public string Body { get; set; }
public DateTime Time { get; set; }
public int AuthorId { get; set; }
// Navigation properties
public virtual UserProfile Author { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
}
The UserProfile is an extendend version of the default in the MVC4 standard project.
Now, in my scaffolded controller/view, there is no way to enter the Author.
My database (MySQL) contains a field with named Author_UserId of type int.
What is wrong?
Also, is it really necessary for the author to be referenced both via the navigation property and the AuthorId
It's not common to use 2 the same Foreign keys of a specific table at any table. Such this interpreted as data redundancy. To solve the problem without any redundancy, i suggest bellow model to you:
public class Article
{
[Key]
public int ArticleId { get; set; }
[Required(ErrorMessage = "Title is required."), MaxLength(80)]
public string Title { get; set; }
[Required(ErrorMessage = "Body is required.")]
public string Body { get; set; }
public DateTime Time { get; set; }
public int AuthorId { get; set; }//determine name of foreign key here of type primary key of navigatable table
// Navigation properties
[ForeignKey("AuthorId")]
public virtual UserProfile Author { get; set; }//column with name 'AuthorId'
public virtual ICollection<Tag> Tags { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
}
Solution above, used to self naming navigation property foreign key.
I wish be useful.
I am wondering what is the correct way to approach this. I currently have one model - (shown below), which contains all the fields required for my record.
My issue is that when the record is created I only need to pass data for
CustomerID, EmployeeID, Date and ArrivalTime.
The remainder of the fields in the model will be populated when the record is updated at a later stage.
As some of my fields are required this will obviously cause validation errors if I don't post data for those fields.
I am wondering what is the best practice to achieve this?
Should I split the model into two?, or can I do partial validation?
public class CustomerSupportRecord
{
public int CustomerSupportRecordID { get; set; }
[Required]
public int CustomerID { get; set; }
[Required]
public string EmployeeID { get; set; }
[Required(ErrorMessage = "Please enter a Date")]
[DataType(DataType.Date)]
[Display(Name = "Date")]
public DateTime Date { get; set; }
[Required(ErrorMessage = "Please select an Arrival Time")]
[DataType(DataType.Time)]
[Display(Name = "Arrival")]
public DateTime ArrivalTime { get; set; }
[Required(ErrorMessage = "Please select a Departure Time")]
[DataType(DataType.Time)]
[Display(Name = "Departure")]
public DateTime DepartureTime { get; set; }
[Required(ErrorMessage = "Please select a Type")]
[Display(Name = "Type")]
public int CustomerSupportTypeID { get; set; }
[Display(Name = "Setting")]
public string ReflectionSetting { get; set; }
[Display(Name = "Advisor")]
public string ReflectionAdvisor { get; set; }
[Display(Name = "Notes")]
public string Notes { get; set; }
[Display(Name = "Comments")]
public string Comments { get; set; }
// Navigation Properties
public virtual Customer Customer { get; set; }
public virtual CustomerSupportType CustomerSupportType { get; set; }
public virtual Employee Employee { get; set; }
}
The correct approach would be to use different viewmodel classes for the different views and only include the properties you need on that view.
So your viewmodel for the first view look just like this:
public class CustomerSupportRecordForCreation
{
public int CustomerSupportRecordID { get; set; }
[Required]
public int CustomerID { get; set; }
[Required]
public string EmployeeID { get; set; }
[Required(ErrorMessage = "Please enter a Date")]
[DataType(DataType.Date)]
[Display(Name = "Date")]
public DateTime Date { get; set; }
[Required(ErrorMessage = "Please select an Arrival Time")]
[DataType(DataType.Time)]
[Display(Name = "Arrival")]
public DateTime ArrivalTime { get; set; }
}
You will have to map between that viewmodel classes and your domain/dal classes. This is where tools like AutoMapper comes in handy.
Edit Automapper:
Using Automapper is really simple.
You have to configure your mappings (i.e. in Application_Start). When the properties of the classes you want to map are named identically, its simple as this:
Mapper.CreateMap<CustomerSupportRecord,
CustomerSupportRecordForCreation>();
Then you can use the mapped in your app. When you have a CustomerSupportRecord and want to return the CustomerSupportRecordForCreation for your view write:
CustomerSupportRecord record = getRecordFromDb...
return View(Mapper.Map<CustomerSupportRecordForCreation>(record));
There is a good tutorial article on Codeproject: http://www.codeproject.com/Articles/61629/AutoMapper or just google
I am working with the EF Code First library trying to work on an appointment scheduling app.
The model's I have are going to be a Client, Appointment, and AppointmentType...
Basically each Client can have a set of Appointments, and each Appointment can have a single AppointmentType...
The code is as follows:
public class Client
{
[ScaffoldColumn(false)]
public int ClientID { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
[EmailAddress]
[Required]
public string Email { get; set; }
[DataType("DateTime")]
public DateTime Birthday { get; set; }
[Required]
public string CellPhone { get; set; }
public string HomePhone { get; set; }
public string Notes { get; set; }
public virtual ICollection<Appointment> Appointments{ get; set; }
public string Name {
get{
return FirstName + " " + LastName;
}
}
public class Appointment
{
[ScaffoldColumn(false)]
public int AppointmentID { get; set; }
[ScaffoldColumn(false)]
public int ClientID { get; set; }
[ScaffoldColumn(false)]
public int AppointmentTypeID { get; set; }
[Required]
public DateTime AppointmentDate { get; set; }
public string Notes { get; set; }
public virtual AppointmentType AppointmentType { get; set; }
public virtual Client Client { get; set; }
}
public class AppointmentType
{
[ScaffoldColumn(false)]
public int AppointmentTypeID { get; set; }
[Required]
public string Name { get; set; }
public string Description { get; set; }
public virtual Appointment Appointment { get; set; }
}
Everything works well when I create an appointment type, and a client, but when I create an appointment I get the following error...
InnerException {"The INSERT statement conflicted with the FOREIGN KEY constraint \"Appointment_Client\". The conflict occurred in database \"Salon.Models.SalonContext\", table \"dbo.Clients\", column 'ClientID'.\r\nThe statement has been terminated."} System.Exception {System.Data.SqlClient.SqlException}
If more details are needed, please let me know...I am just trying to figure out if I am missing anything in the setup.
This is what happens when I debug on the post to create the Appointment...All the ID's are as 0 which is correct, but should the other fields not be null?? Or does it matter...Just not very familiar with how things should look this being my first EF Code First project...
According to your setup, one AppointmentType can only have one Appointment. This is a one-to-one mapping. In this case, you better move the AppointmentType into the Appointment entity. Otherwise, what I believe is more logical, an AppoitmentType can have many Appointments but one Appointment can have only one AppointmentType. Accordingly, you should have a virtual ICollection inside your AppointmentType entity.
public class AppointmentType
{
[ScaffoldColumn(false)]
public int AppointmentTypeID { get; set; }
[Required]
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<Appointment> Appointments { get; set; }
}
I am not sure this is what's causing the problem but it could be. Sometimes mapping faults cause some weird exceptions to be thrown. Give it a try and let me know if your problem gets resolved.
By your constraints AppointmentType and Client cannot be null in Appointment. You can delete constraints or set correct objects in object properties. For example create Client and AppointmentType and then create Appointment for created Client with created AppointmentType