Farther extending ApplicationUser class in ASP.NET MVC5 Identity system - entity-framework

I'm creating a simple application for university where a student can make some type of request which is then processed by an employee of particular speciality.
I would like to use default MVC5 identity system and to extend the ApplicationUser class using TPH pattern. So I added the common properties to the ApplicationUser:
public class ApplicationUser : IdentityUser
{
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
[Required]
public string Email { get; set; }
}
then I created two classes which inherits the ApplicationUser:
public class Student : ApplicationUser
{
public string PersonalNumber { get; set; }
public bool Monitor { get; set; }
public virtual Group Group { get; set; }
public virtual ICollection<Request> Requests { get; set; }
}
public class Employee : ApplicationUser
{
public virtual EmployeeSpeciality EmployeeSpeciality { get; set; }
public virtual ICollection<Request> Requests { get; set; }
}
what I currently want is to make both types of users register as a base Identity and to keep both in a single table as in the inheritance example on asp.net
As I thought, it would be enough to initialize user var in AccountController which is then passes to the UserManager as a Student or as an Employee. But after trying to register as a Student i'm getting this exception:
Exception Details: System.Data.SqlClient.SqlException: Invalid column name 'PersonalNumber'.
Invalid column name 'Monitor'.
Invalid column name 'EmployeeSpeciality_Id'.
Invalid column name 'Group_Id'.
My context class:
public class EntityContext : IdentityDbContext
{
public EntityContext()
: base("DbConnection")
{
}
public DbSet<ApplicationUser> ApplicationUsers { get; set; }
public DbSet<Student> Students { get; set; }
public DbSet<Employee> Employees { get; set; }
...
}
and a part of controller's action:
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new Student()
{
UserName = model.UserName,
FirstName = model.FirstName,
LastName = model.LastName,
Email = model.Email
};
var result = await UserManager.CreateAsync(user, model.Password);
I tried setting ApplicationClass to an abstract class, but no luck. Any help would be appreciated.
UPDATE: The problem wasn't in the code itself. I simply haven't dropped (or updated) the database after making these changes to the model. After it everything works just fine.

#Dragonheart: I tried this repro and it would work fine if you remove the DBSet declarations in you context class. The IdentityDbContext would handle you TPH pattern and add a Discriminator column in the table to differentiate the child classes.

As ApplicationUser is inherited from IdentityUser, remove it from your DbContext class. On the other hand, there is no need to create an abstract class (you can create if you prevent from creating a user except from Student or Employee classes. For more information you might have a look at Table-per-Hierarchy.
For Register part, try something like that:
Student user = new Student
{
UserName = model.UserName,
FirstName = model.FirstName,
LastName = model.LastName,
Email = model.Email
};
var result = UserManager.Create(user, model.Password);
Hope this helps...

Related

Entity Framework one to many : SqlException

I have a little problem when I try to save an item in my DB using EntityFramework.
My classes looks like:
public partial class Site
{
public int Id { get; set; }
public string Name { get; set; }
public string LongName { get; set; }
public string Adress { get; set; }
public City City { get; set; }
public Country Country { get; set; }
public string VATNumber { get; set; }
}
public class Country
{
public int CountryId { get; set; }
public string Name { get; set; }
public string IsoCode { get; set; }
}
And when I try to create a new site in my controller it works, but when I try to add a link to an existing Country :
if (SiteProvider.GetSiteByName(Site.Name) == null)
{
Site.Country = CountryProvider.GetCountryById(1);//it's working, i see the link to the right country
SiteProvider.Create(Site);
}
public static void Create(Site Site)
{
using (MyDBContext Context = new MyDBContext())
{
Context.Site.Add(Site);
Context.SaveChanges(); //here is the problem
}
}
I got this error:
SqlException: Cannot insert explicit value for identity column in
table 'Country' when IDENTITY_INSERT is set to OFF
Thanks in advance for your help.
Add CountryId property to Site class and when adding a new Site set CountryId instead of Country property
public int CountryId { get; set; }
[ForeignKey("CountryId")]
public Country Country{ get; set; }
You have a slight issue with your use of contexts here. You have used one DBContext instance to load the country (where this country object will be tracked) and then a second DBContext to save the site (where the first country object is a property).
It is preferable to perform all your operations for a single unit of work by using one DB context (that would be shared between your classes) and the responsibility for disposing of it to be handled outside your repository layer.

Many to Many relationship in Asp.Net MVC 5 with Identity table and Custom table

I'm trying to make a relationship between the Users from the table generated by Asp.Net Identity with my own table. The relationship must be many to many, since many Users can work on the same Task (which is my table), and same time an User can work on multiple Tasks.
public class Task
{
public int ID { get; set; }
public string Name { get; set; }
public string UserID { get; set; }
public virtual ICollection<ApplicationUser> Users { get; set; }
}
public class ApplicationUser : IdentityUser
{
public int TaskID { get; set; }
public virtual ICollection<Task> Tasks{ get; set; }
// rest of the code
}
I try it this way but I get an error during migration (or run time)
"One or more validation errors were detected during model generation:"
Please help me solve this problem and archive what I need.
Try it like this:
public class Projects
{
public Projects()
{
ApplicationUser = new HashSet<ApplicationUser>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<ApplicationUser> ApplicationUser { get; set; }
}
Application User
public class ApplicationUser : IdentityUser
{
public ApplicationUser()
{
Projects = new HashSet<Projects>();
}
public async Task GenerateUserIdentityAsync(UserManager manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
public virtual ICollection <Projects > Projects { get; set; }
}
Application Context :
public class ApplicationDbContext : IdentityDbContext
{
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public virtual DbSet<Projects> Projects { get; set; }
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
now when I run this Mvc app and register, the db tables I get is like the following:
and the correct schema:
The things to be questioned are a lot, from my point of view important is to determine if you:
- can/should you mix application context and your model context ?
You can try it as shown below using Fluent API.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Task>()
.HasMany<ApplicationUser>(s => s.Users)
.WithMany(c => c.Tasks)
.Map(cs =>
{
cs.MapLeftKey("TaskRefId");
cs.MapRightKey("ApplicationUserRefId");
cs.ToTable("TaskApplicationUser");
});
}
Update : you can see this link too.
EntityType 'IdentityUserLogin' has no key defined. Define the key for this EntityType
Error text is not related to your many-to-many relationship. It tips that other built-in entities are not configured properly. So, It would be nice if you provided full definition of your custom DbContext-class and how it is configured.
UPDATE
As i understood u are working with two different contexts. You must work with the same context, cause of u are extending IdentityContext, creating relationships and adding custom types. So problem then will be resolved itself.
Hope, this will help.

Unable to determine the principal end of an association 1-1 relationship

I'm having problems trying to create relationships of 0..1 to 0..1 using code first between AspNetUsers table extending the applicationuser class and MyUserInfo class. The nature of the relationship is that a user can have one MyUserInfo or not.
My Models:
public class MyUserInfo
{
[Key]
public int MyUserInfoId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string UserId { get; set; }
[ForeignKey("UserId")]
public virtual ApplicationUser User { get; set; }
}
ApplicationUser class:
public class ApplicationUser : IdentityUser
{
public int? MyUserInfoId { get; set; }
[ForeignKey("MyUserInfoId")]
public virtual MyUserInfo MyUserInfo { get; set; }
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
Logically it should work. Got two foreign keys. One foreign key in ApplicationUser class (AspNetUser table) and one foreign key in MyUserInfo class.
Somehow I get this error on adding migration:
Unable to determine the principal end of an association between the
types 'IdentityDemoByThanh.Models.MyUserInfo' and
'IdentityDemoByThanh.Models.ApplicationUser'. The principal end of
this association must be explicitly configured using either the
relationship fluent API or data annotations.
Why do I get this kind of error?
Thanks in advance.

One-to-Many Relationship in Entity Framework 4.1: foreign Id is retrieved, but the object itself not

I'm using Entity Framework 4.1 and trying to create a one-to-many relationship. In my software I have UserEntity and RoleEntity classes. Each User has one Role but one Role can have many Users.
So far I've succeeded in getting the data from the DB to the UserEntity except for the related RoleEntity object. The RoleId property of my test user object has the correct RoleID but Role property is null.
Is there some kind of additional configuration that I would need to write before the Role property is populated with the correct RoleEntity?
[Table("Roles")]
public class RoleEntity
{
public long Id { get; set; }
public string Name { get; set; }
}
[Table("Users")]
public class UserEntity
{
public long Id { get; set; }
public string Password { get; set; }
public Int32 IsActive { get; set; }
public string Username { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string LocalService { get; set; }
[ForeignKey("RoleEntity")]
public long RoleId { get; set; }
public virtual RoleEntity Role { get; set; }
public virtual ClientOrganizationEntity ClientOrganization { get; set; }
}
public class UserContext : DbContext
{
#region Constructor
public UserContext(DbConfigurationProvider dbConfigurationProvider)
: base(dbConfigurationProvider.ConnectionString)
{
if (dbConfigurationProvider == null)
throw new ArgumentNullException("dbConfigurationProvider");
Configuration.ProxyCreationEnabled = false;
}
#endregion
#region DbSets
public DbSet<UserEntity> Users { get; set; }
public DbSet<RoleEntity> Roles { get; set; }
public DbSet<ClientOrganizationEntity> ClientOrganizations { get; set; }
#endregion
}
Data in Users table:
Id User Password FirstName LastName Email LocalService IsActive RoleId ClientOrganizationId
1 foo 62cdb7020ff920e5aa642c3d4066950dd1f01f4d FirstName LastName te#s.t foobar.com 1 1 1
Data in Roles table:
Id Name
1 Administrator
EDIT:
This how I get the data from the DbContext:
var user = _dbContext.Users.FirstOrDefault(x => x.Id == 1);
var role = user.Role;
I didn't see that you have disabled proxy creation in the constructor by
Configuration.ProxyCreationEnabled = false
Lazy loading will not work when you disable Proxy creation.
So either Enable proxy creation or use Include method to eager load the navigational properties
var user = _dbContext.Users.Include(u => u.Role).FirstOrDefault(x => x.Id == 1);
You need to enable lazy loading Configuration.LazyLoadingEnabled=true; in your UserContext constructor.
I ended up solving this myself. For some disabling of Configuration.ProxyCreationEnabled broke the relationships. The only change I made to fix it was remove this line:
Configuration.ProxyCreationEnabled = false;
Surely relationships must be possible to be used even with proxy creation disabled so if anyone has any additional information, I'd like to hear it. For the time being however, I'm just glad that the problem was solved.

Entity Framework Code First Mapping Foreign Key Using Fluent API

I have the situation where a User can have several addresses. Accordingly, I have an ICollection on my user class. But I also want the user to be able to choose a default address. So I've done the following:
public class User
{
public int Id { get; set; }
public int? DefaultAddressId { get; set; }
[ForeignKey("DefaultAddressId")]
public virtual Address DefaultAddress { get; set; }
public virtual ICollection<Address> Addresses { get; set; }
//properties were removed for purpose of this post
}
I would like to remove the public virtual Address DefaultAddress { get; set; } altogether, keep the DefaultAddressId and map it using the Fluent API instead because the current setup is causing a lot of trouble (in this and other classes where I have a similar setup). So can this be done using the fluent api?
UPDATE:
The address class currently doesn't have any reference to the User class, it's a uni-directional relationship. But yes, an address belongs to only ONE user, it's not a many to many relationship. Here's the address class:
public class Address
{
public int Id { get; set; }
public string Name { get; set; }
public string Details { get; set; }
public virtual Area Area { get; set; }
}
I would personally move the Foreign Key relation from User to Address, and add an IsDefaultAddress property on the address class.
public class Address
{
public int Id { get; set; }
// This property marks the FK relation
public virtual User User { get; set; }
public string Name { get; set; }
public string Details { get; set; }
public virtual Area Area { get; set; }
// This property signals whether this is the user's default address
public bool IsDefaultAddress { get; set; }
}
EF will know that it needs a Foreign Key relation between Address and User.
This would simplify your model a great deal. That is, of course, if an address can only belong to one user (as asked by Slauma in the comments).
Your original model in the question should work. You can test it quite easily:
Create new console application (VS 2010)
Name it "EFTestApp"
Add reference to "EntityFramework.dll"
Delete content of Program.cs and copy the following code into the file
Program.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;
namespace EFTestApp
{
public class User
{
public int Id { get; set; }
public int? DefaultAddressId { get; set; }
[ForeignKey("DefaultAddressId")]
public virtual Address DefaultAddress { get; set; }
public virtual ICollection<Address> Addresses { get; set; }
}
public class Address
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Context : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Address> Addresses { get; set; }
}
class Program
{
static void Main(string[] args)
{
using (var context = new Context())
{
try
{
User user = new User() { Addresses = new List<Address>() };
Address address1 = new Address() { Name = "Address1" };
Address address2 = new Address() { Name = "Address2" };
user.Addresses.Add(address1);
user.Addresses.Add(address2);
context.Users.Add(user);
context.SaveChanges();
// user has now 2 addresses in the DB and no DefaultAddress
user.DefaultAddress = address1;
context.SaveChanges();
// user has now address1 as DefaultAddress
user.DefaultAddress = address2;
context.SaveChanges();
// user has now address2 as DefaultAddress
user.DefaultAddress = null;
context.SaveChanges();
// user has now no DefaultAddress again
}
catch (Exception e)
{
throw;
}
}
}
}
}
In SQL Server Express it creates a new DB called "EFTestApp.Context". You can set breakpoints on every SaveChanges above, step over and watch the changes in the DB.
If you look at the relationships in the database then there are two, and in table Addresses in the DB is a foreign key column User_Id.
I think you could also remove public int? DefaultAddressId { get; set; } and [ForeignKey("DefaultAddressId")]. It creates the same database tables and relationships with an optional DefaultAddress.
Perhaps you want the relationship Address -> User as required (Addresses cannot live alone in the DB without a User). Then you can add this to the Context class:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasMany(u => u.Addresses)
.WithRequired();
}
It makes User_Id in the Addresses table non nullable and sets up cascading delete by default. So, when a user gets deleted all its addresses get deleted as well.
DefaultAddressId doesn't need any specific mapping because it will be just column in User table without any relation (FK) to Address table. There will be no relation created because navigation property doesn't exist on either side. Also it should be one-to-one relation which will not work because EF doesn't support unique keys.
I like solution provided by #Sergi Papaseit
You don't need to map it if you are removing the DefaultAddress property. You can just have the property there and EF should know how to map it provided DefaultAddressId is in the User table