I'm trying to create an ApplicationUser which has a User as a child object, this is what the models look like:
ApplicationUser:
public class ApplicationUser : IdentityUser
{
public User User { get; set; }
}
User:
public class User
{
public int Id { get; set; }
[ForeignKey("AspNetUser")]
public string AspNetUserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual ApplicationUser AspNetUser { get; set; }
}
Within my DbContext I have:
public class IdentityDbContext : IdentityDbContext<ApplicationUser>
{
public IdentityDbContext(DbContextOptions<IdentityDbContext> options)
: base(options)
{
}
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.ApplyConfiguration(new AdminConfiguration());
}
}
AdminConfiguration:
public class AdminConfiguration : IEntityTypeConfiguration<ApplicationUser>
{
public void Configure(EntityTypeBuilder<ApplicationUser> builder)
{
var id = "bc62cdff-77ca-4473-a467-210eb36fdd5d";
var admin = new ApplicationUser
{
Id = id,
UserName = "admin",
NormalizedUserName = "ADMIN",
Email = "admin#dotvvm.com",
NormalizedEmail = "ADMIN#DOTVVM.COM",
EmailConfirmed = true,
SecurityStamp = new Guid().ToString("D")
};
admin.PasswordHash = GeneratePassword(admin, "Admin12345!");
builder.HasData(admin);
builder.OwnsOne(a => a.User).HasData(new User
{
Id = 1,
AspNetUserId = id,
FirstName = "Test",
LastName = "Test"
});
}
private string GeneratePassword(ApplicationUser user, string password)
{
var passHash = new PasswordHasher<ApplicationUser>();
return passHash.HashPassword(user, password);
}
}
With this code, I create a migration and try to execute Update-Database but I get this error:
To change the IDENTITY property of a column, the column needs to be dropped and recreated
I can't figure out what I'm doing wrong, does anyone know?
I'm almost sure that you're using .OwnsOne wrong (but i doubt it is root cause, i speak about it later)
Owned types are Value objects. Value objects have no identity on their own and exist only as a part of their owner like
//this is entity, it has identity
public class Person
{
public Guid Id { get; set; }
public Name Name { get; set; }
}
//and this is value object and could be owned type
public class Name
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
If you want both ApplicationUser and User to be entities (make sense) you could consider One-to-One relationship betwen them, like this
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ApplicationUser>()
.HasOne(a => a.User)
.WithOne(b => b.ApplicationUser)
.HasForeignKey<ApplicationUser>(b => b.AspNetUserId);
}
and then your
builder.HasData(new User
{
Id = 1,
AspNetUserId = id,
FirstName = "Test",
LastName = "Test"
});
might be valid and ... might not
because another possible source of you problem could be Autoincrement Id field (is it autoincrement in your User class?)
If so -
builder.OwnsOne(a => a.User).HasData(new User
{
Id = 1, //<<---- try removing this
AspNetUserId = id,
FirstName = "Test",
LastName = "Test"
});
this could solve your issue
Related
I have the following architecture:
Standard ASP.NET Identity tables, user can have one and only one role
There are 2 roles : Driver and Manager
User has driver or manager role. Every driver has record in Driver table, Every manager has record in Manager table.
Both, Manager and Driver tables, has FirstName and LastName
My entities and map among them (0..1:1):
public class AspNetUser
{
//.....
public virtual Manager Manager { get; set; }
public virtual Driver Driver { get; set; }
}
public partial class Driver
{
public int Id { get; set; }
//....
public virtual AspNetUser AspNetUser { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Manager
{
public int Id { get; set; }
//....
public virtual AspNetUser AspNetUser { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Nickname { get; set; }
}
public class AspNetUserMap : EntityTypeConfiguration<AspNetUser>
{
public AspNetUserMap()
{
this.HasKey(dp => dp.Id);
this.Property(p => p.UserName).IsRequired().HasMaxLength(256);
this.HasIndex(p => p.UserName).IsClustered(false).IsUnique();
this.HasOptional(c => c.Driver)
.WithOptionalPrincipal(a => a.AspNetUser)
.Map(m => m.MapKey("AspNetUserId"));
this.HasOptional(c => c.Manager)
.WithOptionalPrincipal(a => a.AspNetUser)
.Map(m => m.MapKey("AspNetUserId"));
Now I need to get all users from AspNetUser, but with their FN/LN. To get FN/LN I should check role of user and then call Driver or Manager.
I have the following code:
List<ChatUserDomain> returnedUsers = new List<ChatUserDomain>();
var users = (from i in db.AspNetUsers
.Include(path => path.Driver)
.Include(path => path.Manager)
.Include(path => path.AspNetRoles)
where i.CompanyId == db.AspNetUsers.Where(p => p.UserName.Equals(username, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault().CompanyId
select new
{
AspNetUser = i,
Username = i.UserName,
....
}
).ToList();
foreach (var user in users)
{
returnedUsers.Add(new ChatUserDomain()
{
UserId = user.AspNetUser.Id,
Name = GetNameByUser(user.AspNetUser),
...
});
}
return returnedUsers;
and GetNameByUser method:
private string GetNameByUser(AspNetUser aspNetUser)
{
IProfile profile = GetProfileByUser(aspNetUser);
if (!String.IsNullOrWhiteSpace(profile.FirstName) && !String.IsNullOrEmpty(profile.LastName))
{
return (GetFirstLettersOfFullName(profile.FirstName, profile.LastName));
}
else
{
return (aspNetUser.UserName.Substring(0, 2));
}
}
private IProfile GetProfileByUser(AspNetUser aspNetUser)
{
IProfile profile = null;
var role = aspNetUser.AspNetRoles.FirstOrDefault().Name;
if (role.Equals(Domain.StaticStrings.RoleStaticStrings.ROLE_DRIVER))
profile = aspNetUser.Driver;
else
profile = aspNetUser.Manager;
return profile;
}
It works, but it creates additional user.Count calls to DB. How to say to EF "load Manager and Driver data in the parent request"?
I am trying to create and associate the entities in my project. The User can have many Roles, Claims and Logins. On the other hand, A claim or login can have only one user, while a role can also be with many user. I have the relationship defined with fluent API:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
modelBuilder.Entity<User>().HasMany(u => u.Claims).WithRequired(cl => cl.User).Map(m => m.MapKey("User"));
modelBuilder.Entity<Claim>().HasRequired(cl => cl.User).WithMany(u => u.Claims).Map(m => m.MapKey("User"));
modelBuilder.Entity<User>().HasMany(u => u.Logins).WithRequired(lg => lg.User).Map(m => m.MapKey("User"));
modelBuilder.Entity<Login>().HasRequired(lg => lg.User).WithMany(u => u.Logins).Map(m => m.MapKey("User"));
modelBuilder.Entity<User>().HasMany(u => u.Roles).WithMany(ro => ro.Users).Map(userRoles =>
{
userRoles.ToTable("Users_Roles");
userRoles.MapLeftKey("User");
userRoles.MapRightKey("Role");
});
modelBuilder.Entity<Role>().HasMany(ro => ro.Users).WithMany(u => u.Roles).Map(userRoles =>
{
userRoles.ToTable("Users_Roles");
userRoles.MapLeftKey("User");
userRoles.MapRightKey("Role");
});
}
As you see, my database tables do not follow Entity Framework's convention, instead of using 'User_Id' as foreign key, the foreign key is simply named 'User'. However, I keep getting this 'Invalid Column Name: User_Id' exception message.
I tried to define the foreign key column name with the above code by calling the methods Map() and MapKey(), but with no prevail. Why is this happening? Am I writing the mapping code wrong? Can someone help?
PS: The exception error message is very unhelpful that I do not know which table this column name error is associated with. Does anyone know how to make the exception message show what the table name is, not just the column name? Thanks.
Also I've added code I used for Claim, Login and Role entities(with unrelated methods removed).
public class Claim
{
public string Id { get; protected set; }
public string Type { get; protected set; }
public string Value { get; protected set; }
public virtual User User { get; protected set; }
public Claim() { }
public Claim(string id, User user, string type, string value)
{
Id = id;
User = user;
Type = type;
Value = value;
}
}
public class Login
{
public string Id { get; protected set; }
public string Provider { get; protected set; }
public string Key { get; protected set; }
public DateTime? DateLoggedin { get; protected set; }
public virtual User User { get; protected set; }
public Login() { }
public Login(string id, string provider, string key, DateTime? dateLoggedIn = null)
{
Id = id;
Provider = provider;
Key = key;
DateLoggedin = dateLoggedIn;
}
}
public class Role
{
public string Id { get; protected set; }
public string Title { get; protected set; }
public string Description { get; protected set; }
public bool IsAdmin { get; protected set; }
public bool IsBanned { get; protected set; }
public IList<User> Users { get; protected set; }
public Role() { }
public Role(string id, string title, string description, bool isAdmin, bool isBanned)
{
Id = id;
Title = title;
Description = description;
IsAdmin = isAdmin;
IsBanned = isBanned;
}
}
The User_Id column is being created by EF convention because you map your key to a property by the same name as your navigation proeprty.
You could either remove the .Map() part and let EF handle the key, or define a key property on your Claim and Login classes, and map that in the fluent api. I would personally do the latter.
E.g:
public class Claim
{
public string Id { get; protected set; }
public string Type { get; protected set; }
public string Value { get; protected set; }
public virtual User User { get; protected set; }
public int UserId{get;set;}
public Claim() { }
}
And in your ModelCreating
modelBuilder.Entity<User>().HasMany(a=>a.Claims).WithRequired(a=>a.User).HasForeignKey(a=>a.UserId);
If you create a key field of 'EntityNameId' (UserId, ClaimId, etc) then EF convention will automatically map that as your Foreign Key, without the fluent mapping. However, if you name is something else (UsernameId, or whatever), you have to explicitly provide the mapping. Some reading on that.
I am new to entity framework code first. I am trying to create a many-to-many relationship between User and Role. The link table will be UserRole. The following is my code:
public class User
{
public int UserId { get; set; }
[Required]
[MaxLength(100)]
public string Username { get; set; }
[Required]
[MaxLength(100)]
public string Email { get; set; }
public string HashedPassword { get; set; }
public string Salt { get; set; }
public bool IsLocked { get; set; }
public DateTime DateCreated { get; set; }
public virtual ICollection<Role> Roles { get; set; }
}
public class Role
{
public int RoleId { get; set; }
public string RoleName { get; set; }
public virtual ICollection<User> Users { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
{
modelBuilder.Entity<User>()
.HasMany(r => r.Roles)
.WithMany(u => u.Users)
.Map(ur =>
{
ur.MapLeftKey("UserId");
ur.MapRightKey("RoleId");
ur.ToTable("UserRole");
}
);
}
protected override void Seed(LabRating.Data.LabRatingContext context)
{
context.UserSet.AddOrUpdate(u => u.Email, new User[]{
new User()
{
Email = "abc.blog#gmail.com",
Username = "abc123",
HashedPassword = "XwAQoiq84p1RUzhAyPfaMDKVgSwnn80NCtsE8dNv3XI=",
Salt = "mNKLRbEFCH8y1xIyTXP4qA==",
IsLocked = false,
DateCreated = DateTime.Now
}
});
context.RoleSet.AddOrUpdate(new Role[] {
new Role()
{
RoleName= "Admin"
}
});
}
As you can see first I am creating the two tables -User and Role - and then I am establishing the many-to-many relationship using Fluent API in the OnModelCreating method. When I checked the database, I see all three tables - User, Role and UserRole - being created correctly. When the Seed method is run, only the User and Role tables are populated with the data shown above but not the UserRole table. Please let me know how to resolve this issue.
protected override void Seed(DatabaseContext context)
{
Role role = new Role
{
RoleName = "Admin"
};
Role role2 = new Role
{
RoleName = "HR"
};
Role role3 = new Role
{
RoleName = "Marketing"
};
var roles= new List<Role> { role, role2,role3 };
roles.ForEach(i => context.Roles.AddOrUpdate(i));
context.SaveChanges();
User user= new User
{
Email = "abc.blog#gmail.com",
Username = "abc123",
HashedPassword = "XwAQoiq84p1RUzhAyPfaMDKVgSwnn80NCtsE8dNv3XI=",
Salt = "mNKLRbEFCH8y1xIyTXP4qA==",
IsLocked = false,
DateCreated = DateTime.Now,
Roles= new List<Role> { role,role2 }
};
User user2 = new User
{
Email = "def.blog#gmail.com",
Username = "def123",
HashedPassword = "XwAQoiq84p1RUzhAyPfaMDKVgSwnn80NCtsE8dNv3XI=",
Salt = "mNKLRbEFCH8y1xIyTXP4qA==",
IsLocked = false,
DateCreated = DateTime.Now,
Roles= new List<Role> { role3 }
};
var users= new List<User> { user, user2};
users.ForEach(a => context.Users.AddOrUpdate(a));
context.SaveChanges();
}
I have problem in which i would like to create N, two in the example, user objects (e.g. Customer & Supplier) which all inherent from the asp.net IdentityUser object. These object have very different additional data besides the the data from the IdentityUser. I would like to use the IdentityUser user as this gives me a flexible way of taking care of authentication and authorization.
This example has been very stripped down but should supply sufficient information concerning the not being able to create a concrete user (e.g. Customer of Supplier). It seems i need to use the UserManager object as this also takes care of creating for example the password hash and additional security information.
I get presented the following error:
{"Attaching an entity of type 'Supplier' failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate."}
Classes which inherent from IdentityUser
public class Customer : IdentityUser
{
public string CustomerProperty { get; set; }
}
public class Supplier : IdentityUser
{
public string SupplierProperty { get; set; }
}
Database context class
public class ApplicationDbContext : IdentityDbContext {
public ApplicationDbContext() : base("ApplicationDbContext")
{
Database.SetInitializer(new ApplicationDbInitializer());
}
public DbSet<Customer> CustomerCollection { get; set; }
public DbSet<Supplier> SupplierCollection { get; set; }
}
Seeding class which throws the exception
public class ApplicationDbInitializer : DropCreateDatabaseAlways<ApplicationDbContext>
{
protected override void Seed(ApplicationDbContext context)
{
var userStore = new UserStore(context);
var userManager = new UserManager(userStore);
// Seed customer user which inherents from asp.net IdentityUser
var user = userManager.FindByEmail("customer#customer.com");
if (user == null)
{
user = new User()
{
UserName = "customer#customer.com",
Email = "customer#customer.com"
};
userManager.Create(user, userPassword);
var customerUser = new Customer()
{
Id = user.Id,
CustomerProperty = "Additional Info"
};
context.Entry(customerUser).State = EntityState.Modified;
context.SaveChanges();
}
// Seed supplier user which inherents from asp.net IdentityUser
var user = userManager.FindByEmail("supplier#supplier.com");
if (user == null)
{
user = new User()
{
UserName = "supplier#supplier.com",
Email = "supplier#supplier.com"
};
userManager.Create(user, userPassword);
var supplierUser = new Supplier()
{
Id = user.Id,
IBAN = "212323424342234",
Relationship = "OK"
};
context.Entry(supplierUser).State = EntityState.Modified;
context.SaveChanges();
}
}
}
**** UPDATE ****
The solution below works but i am still struggling with two issues:
I would always like to have one user type (e.g. Customer of Supplier) associated with the IdentityUser. I though about using an interface but this doesn't work.
If i also add the virtual reference towards the IdentityUser on the user types i get an 'Unable to determine the principal end of an association between the types 'ApplicaitonUser' and 'Supplier'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.' exception.
Classes
public class Customer
{
[Key]
public int CustomerId { get;set; }
public string CustomerProperty { get; set; }
*public virtual User User { get; set; }*
}
public class Supplier
{
[Key]
public int SupplierId { get;set; }
public string SupplierProperty { get; set; }
*public virtual User User { get; set; }*
}
**Class IdentityUser (which works) **
public class User : IdentityUser
{
public virtual Supplier Supplier { get; set; }
public virtual Customer Customer { get; set; }
}
**Class IdentityUser (what i would like) **
public class User : IdentityUser
{
public virtual IConcreteUser ConcreteUser{ get; set; }
}
Database context class
public class ApplicationDbContext : IdentityDbContext {
public ApplicationDbContext() : base("ApplicationDbContext")
{
Database.SetInitializer(new ApplicationDbInitializer());
}
public DbSet<Customer> CustomerCollection { get; set; }
public DbSet<Supplier> SupplierCollection { get; set; }
}
**Seeding class **
public class ApplicationDbInitializer : DropCreateDatabaseAlways<ApplicationDbContext>
{
protected override void Seed(ApplicationDbContext context)
{
var userStore = new UserStore(context);
var userManager = new UserManager(userStore);
var roleManager = new RoleManager(roleStore);
var user = userManager.FindByEmail("customer#customer.com");
if (user == null)
{
user = new ApplicationUser()
{
UserName = "customer#customer.com",
Email = "customer#customer.com"
Customer = new Customer()
{
CustomerProperty = "Additional Info"
}
};
userManager.Create(user, userPassword);
roleManager.AddUserToRole("Customer");
}
user = userManager.FindByEmail("supplier#supplier.com");
if (user == null)
{
user = new ApplicationUser()
{
UserName = "supplier#supplier.com",
Email = "supplier#supplier.com",
Supplier = new Supplier()
{
IBAN = "212323424342234",
Relationship = "OK"
}
};
userManager.Create(user, userPassword);
roleManager.AddUserToRole("Supplier");
}
}
}
As others do too I think this is a design problem. There are some alternative approaches like:
use roles to define the "user-type" (a user can be supplier AND customer)
make the Supplier and Customer entities a relation not extension of the user
e.g.:
public class ApplicationUser : IdentityUser
{
public virtual Customer Customer { get; set; }
public virtual Supplier Supplier { get; set; }
}
public class Customer
{
[Key]
public int Id { get; set; }
public virtual ApplicationUser User { get; set; }
public string CustomerProperty { get; set; }
}
public class Supplier
{
[Key]
public int Id { get; set; }
public virtual ApplicationUser User { get; set; }
public string SupplierProperty { get; set; }
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public DbSet<Customer> Customers { get; set; }
public DbSet<Supplier> Suppliers { get; set; }
}
public class ApplicationDbInitializer
: DropCreateDatabaseAlways<ApplicationDbContext>
{
protected override void Seed(ApplicationDbContext context)
{
var userStore = new UserStore(context);
var userManager = new UserManager(userStore);
var roleManager = new RoleManager(roleStore);
var user = userManager.FindByEmail("customer#customer.com");
if (user == null)
{
user = new ApplicationUser()
{
UserName = "customer#customer.com",
Email = "customer#customer.com"
Customer = new Customer()
{
CustomerProperty = "Additional Info"
}
};
userManager.Create(user, userPassword);
roleManager.AddUserToRole("Customer");
}
user = userManager.FindByEmail("supplier#supplier.com");
if (user == null)
{
user = new ApplicationUser()
{
UserName = "supplier#supplier.com",
Email = "supplier#supplier.com",
Supplier = new Supplier()
{
IBAN = "212323424342234",
Relationship = "OK"
}
};
userManager.Create(user, userPassword);
roleManager.AddUserToRole("Supplier");
}
}
}
and in your logic you can do something like:
if (User.IsInRole("Customer"))
{
// do something
}
DISCLAIMER: This is not a "copy&paste" example and should just give you an idea of a different approach.
I just resolved a similar problem. I created a navigation property of abstract type DomainUser in my AppUser (that inherits from Identity User)
public class AppUser : IdentityUser
{
public DomainUser DomainUser { get; set; }
}
DomainUser looks like this:
public abstract class DomainUser : IAggregateRoot
{
public Guid Id { get; set; }
public AppUser IdentityUser { get; set; }
}
I inherit from DomainUser in all concrete domain user types:
public class AdministrationUser : DomainUser
{
public string SomeAdministrationProperty { get; set; }
}
public class SupplierUser : DomainUser
{
public string SomeSupplierProperty { get; set; }
}
public class Customer : DomainUser
{
public string SomeCustomerProperty { get; set; }
}
And in DbContext in OnModelCreating method I configured Entity Framework to store all entities inherited from DomainUser in separate tables (it's called Table per Concrete Type). And configured one to one relationship between IdentityUser and DomainUser:
modelBuilder.Entity<DomainUser>()
.Map<AdministrationUser>(m =>
{
m.MapInheritedProperties();
m.ToTable("AdministrationUsers");
})
.Map<SupplierUser>(m =>
{
m.MapInheritedProperties();
m.ToTable("SupplierUsers");
})
.Map<Customer>(m =>
{
m.MapInheritedProperties();
m.ToTable("Customers");
});
modelBuilder.Entity<DomainUser>()
.HasRequired(domainUser => domainUser.IdentityUser)
.WithRequiredPrincipal(groomUser => groomUser.DomainUser);
This code added column "DomainUser_Id" to table AspNetUsers and now I'm able to access IdentityUser navigation property in each domain user and DomainUser navigation property in AppUser.
I have the fallowing Models
namespace Prometheus.Models
{
[Table("People")]
public class Person : IPerson
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Name
{
get
{
return FirstName + " " + LastName;
}
set{}
}
public string Email { get; set; }
public DateTime? LastModified { get; set; }
}
}
And one that inherits it
namespace Prometheus.Models
{
[Table("UserProfile")]
public class UserProfile : Person
{
public string UserName { get; set; }
public string CNP { get; set; }
public virtual Faculty Faculty { get; set; }
public bool? IsUSAMV { get; set; }
public virtual ICollection<Result> Results { get; set; }
public virtual ICollection<Project> Projects { get; set; }
}
}
And the seed method
private void AddUser(string user, string password,
SimpleMembershipProvider membership)
{
if (membership.GetUser(user, false) == null)
{
WebSecurity.CreateUserAndAccount(user, password, new
{
CNP = "1890531111111",
IsUSAMV = true,
});
}
}
When i try to run the seed method without UserProfile extending Person everything is ok, but when it extends it i keep getting the fallowing error.
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_dbo.UserProfile_dbo.People_Id". The conflict occurred in database "PrometheusDb", table "dbo.People", column 'Id'.
The statement has been terminated.
Any help would be greatly appreciated thanks.
I tried updateing my function to
private void AddUser(string firstName,string lastName, string password,
SimpleMembershipProvider membership)
{
var user = firstName + "." + lastName;
if (membership.GetUser(user, false) == null)
{
var profile = new UserProfile()
{
Email = "test#email.com",
FirstName = firstName,
LastName = lastName
};
_context.Users.Add(profile);
_context.SaveChanges();
WebSecurity.CreateAccount(user, password);
}
}
But now i get:
- An error occurred while updating the entries. See the inner exception for details.
System.Data.SqlClient.SqlException: Cannot insert explicit value for identity column in table 'UserProfile' when IDENTITY_INSERT is set to OFF.
You are having a problem in the order that items are created and the integrity checks on the db. I have done something similar and it has involved saving the user first then the account. My code looks like:
var user = new User { UserName = model.UserName, Organisation = userOrg };
this.repository.SaveUser(user);
string token = WebSecurity.CreateAccount(model.UserName, model.Password, true);
Notice the use of the CreateAccount rather than the CreateUserAndAccount method