Entity Framework User Roles Many to Many Relationship - entity-framework

Hi I'm trying to set up my entity framework for a many to many relationship between User and Role.
The picture below shows what's in the database:
The Model for User is:
public class User : IEntity
{
public virtual int UserId { get; set; }
[Column(TypeName = "varchar")]
[StringLength(100)]
public virtual string UserName { get; set; }
[Column(TypeName = "varchar")]
[StringLength(100)]
public virtual string FirstName { get; set; }
[Column(TypeName = "varchar")]
[StringLength(100)]
public virtual string LastName { get; set; }
[Column(TypeName = "varchar")]
[StringLength(200)]
public virtual string EmailAddress { get; set; }
public int AreaId { get; set; }
[Column(TypeName = "varchar")]
[StringLength(64)]
public string CreatedByUserName { get; set; }
public DateTime CreatedDateTime { get; set; }
[Column(TypeName = "varchar")]
[StringLength(64)]
public string LastModifiedByUserName { get; set; }
public DateTime? LastModifiedDateTime { get; set; }
//Navigation properties
//public virtual Role Role { get; set; }
public virtual Area Area { get; set; }
public virtual ICollection<Role> Roles { get; set; }
}
Model for Role is:
public class Role : IEntity
{
public int RoleId { get; set; }
[Column(TypeName = "varchar")]
[StringLength(100)]
public string Name { get; set; }
[Column(TypeName = "varchar")]
[StringLength(1000)]
public string Description { get; set; }
[Column(TypeName = "varchar")]
[StringLength(64)]
public string CreatedByUserName { get; set; }
public DateTime CreatedDateTime { get; set; }
[Column(TypeName = "varchar")]
[StringLength(64)]
public string LastModifiedByUserName { get; set; }
public DateTime? LastModifiedDateTime { get; set; }
//Navigation Properties
public ICollection<User> Users { get; set; }
}
UserRole is:
public class UserRole
{
public int UserId { get; set; }
public int RoleId { get; set; }
//Navigation properties
public virtual User User { get; set; }
public virtual Role Role { get; set; }
}
So I thought I had this set up fine but in my code I go something like:
var roles = from r in user.Roles
select r.Name;
and it shoots itself giving errors of:
Server Error in '/' Application.
Invalid object name 'dbo.RoleUser'.
so I added the following to the context:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasMany(i => i.Roles)
.WithMany(u => u.Users);
}
However now I'm getting errors of:
Server Error in '/' Application.
Invalid column name 'Role_RoleId'.
Invalid column name 'User_UserId'.
So surely I don't have something set up here correctly. Can andybody point me in the right direction?

You don't need to model the link table UserRole as a class since it has only the primary keys of the tables participate in the relationship. So remove the UserRole class.
If you are modeling an existing database, EF may infer the link table name to be RoleUser. To avoid this you can configure the link table as follows.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasMany(i => i.Roles)
.WithMany(u => u.Users)
.Map(m =>
{
m.ToTable("UserRole");
m.MapLeftKey("UserId");
m.MapRightKey("RoleId");
});
}

Related

One to Many relashionship with Entity Framework

in my project (Asp.net Core Web Api) I have the tables "Truck" and "UserAccount with a one to many relashionship.
[Table("UserAccount")]
public class UserAccount : BaseClass
{
// Foreign Keys
[ForeignKey(nameof(UserAccountType))]
public int UserAccountTypeId { get; set; }
[ForeignKey(nameof(Gender))]
public int GenderId { get; set; }
[ForeignKey(nameof(Truck))]
public int TruckId { get; set; }
// Properties
public string LastName { get; set; }
public string FirstName { get; set; }
public string UserName { get; set; }
[DataType(DataType.EmailAddress)]
public string Mail { get; set; }
public string Login { get; set; }
[DataType(DataType.Password)]
public string Password { get; set; }
// Navigation Properties
[IgnoreDataMember]
public virtual Gender Gender { get; set; }
//public virtual Truck Truck { get; set; }
[IgnoreDataMember]
public virtual UserAccountType UserAccountType { get; set; }
public Truck Truck { get; set; }
}
[Table("Truck")]
public class Truck : BaseClass
{
// Foreign Keys
// Properties
[Column(Order = 3)]
public string Name { get; set; }
[DataType(DataType.EmailAddress)]
[Column(Order = 4)]
public string Mail { get; set; }
[Column(Order = 5)]
public string Phone { get; set; }
[Column(Order = 6)]
public string VATNumber { get; set; }
// Navigation Properties
public virtual ICollection<TruckFoodType> TruckFoodTypes { get; set; }
public virtual ICollection<TruckOption> TruckOptions { get; set; }
public ICollection<UserAccount> UserAccounts { get; set; }
}
In the method OnModelCreation into my ApplicationDbContex file I have this to create the one to many relashionship:
modelBuilder.Entity<UserAccount>()
.HasOne<Truck>(u => u.Truck)
.WithMany(t => t.UserAccounts)
.HasForeignKey(u => u.TruckId);
But when I try to populate the UserAccount table I have this error message :
"Merge instruction is in conflict with "FK_User_Account_TruckId". This conflict occurse in the database xxx table dbo.Truck column Id" (Sorry, Comes from a french translation)
I don't hunderstand why.
Can somebody help me?
Thanks
OK, stupid mistake. In some cases the TruckId field from the User Account table can be null. So I added a "?" to this fields like this : public int? TruckId { get; set; } Sorry for inconvenience

Migration failed while trying to create a many to many relationship

I am trying to connect two tables with a code first migration. I thought EF would create many to many relationship table itself but I get error "build failed". While building whole project everything works fine. It's just the migration.
Following are my models -
Task:
[Key]
public int Id { get; set; }
public DateTime? CreatedAt { get; set; }
public DateTime? EndedAt { get; set; }
[Column(TypeName = "text")]
public string CreatedBy { get; set; }
[Required]
[Column(TypeName = "text")]
public string Title { get; set; }
[Required]
[Column(TypeName = "text")]
public string Description { get; set; }
public virtual TaskGroups TaskGroup { get; set; }
public string Status { get; set; }
[Column(TypeName = "text")]
public string WantedUser { get; set; }
TaskGroup:
[Required]
public int Id { get; set; }
[Required]
public string GroupName { get; set; }
public virtual Tasks Tasks { get; set; }
At first I've tried with ICollection<> but I got the same error.
My project is .Net Core 3.
Any ideas?
Edit
Tasks
[Key]
public int Id { get; set; }
public DateTime? CreatedAt { get; set; }
public DateTime? EndedAt { get; set; }
[Column(TypeName = "text")]
public string CreatedBy { get; set; }
[Required]
[Column(TypeName = "text")]
public string Title { get; set; }
[Required]
[Column(TypeName = "text")]
public string Description { get; set; }
public string Status { get; set; }
[Column(TypeName = "text")]
public string WantedUser { get; set; }
public IList<TaskGroupTask> TaskGroupTask { get; set; }
TaskGroups
[Key]
public int Id { get; set; }
[Required]
public string GroupName { get; set; }
public IList<TaskGroupTask> { get; set; }
TaskGroupTask
public int TaskId { get; set; }
public int TaskGroupId { get; set; }
public Tasks Tasks { get; set; }
public TaskGroups TaskGroups { get; set; }
DbContext
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<TaskGroupTask>(e =>
{
e.HasKey(p => new { p.TaskId, p.TaskGroupId });
e.HasOne(p => p.Tasks).WithMany(t =>
t.TaskGroupTask).HasForeignKey(p => p.TaskId);
e.HasOne(p => p.TaskGroups).WithMany(tg =>
tg.TaskGroupTask).HasForeignKey(p => p.TaskGroupId);
});
}
public DbSet<TaskGroupTask> TaskGroupTask { get; set; }
You will need to create a joining entity, like MyJoiningEntity or TaskGroupTask, whose sole purpose is to create a link between Task and TaskGroup. Following models should give you the idea -
public class Task
{
public int Id { get; set; }
public string Description { get; set; }
public IList<JoiningEntity> JoiningEntities { get; set; }
}
public class TaskGroup
{
public int Id { get; set; }
public string GroupName { get; set; }
public IList<JoiningEntity> JoiningEntities { get; set; }
}
// this is the Joining Entity that you need to create
public class JoiningEntity
{
public int TaskId { get; set; }
public int TaskGroupId { get; set; }
public Task Task { get; set; }
public TaskGroup TaskGroup { get; set; }
}
Then you can configure the relation in the OnModelCreating method of your DbContext class, like -
modelBuilder.Entity<JoiningEntity>(e =>
{
e.HasKey(p => new { p.TaskId, p.TaskGroupId });
e.HasOne(p => p.Task).WithMany(t => t.JoiningEntities).HasForeignKey(p => p.TaskId);
e.HasOne(p => p.TaskGroup).WithMany(tg => tg.JoiningEntities).HasForeignKey(p => p.TaskGroupId);
});
This will define a composite primary key on JoiningEntity table based on the TaskId and TaskGroupId properties. Since this table's sole purpose is to link two other tables, it doesn't actually need it's very own Id field for primary key.
Note: This approach is for EF versions less than 5.0. From EF 5.0 you can create a many-to-many relationship in a more transparent way.
Since I have some time, I've decided to pull all pices of the code in one place. I think it would be very usefull sample how to create code-first many-to-many relations for database tables. This code was tested in Visual Studio and a new database was created without any warnings:
public class Task
{
public Task()
{
TaskTaskGroups = new HashSet<TaskTaskGroup>();
}
[Key]
public int Id { get; set; }
public DateTime? CreatedAt { get; set; }
public DateTime? EndedAt { get; set; }
[Column(TypeName = "text")]
public string CreatedBy { get; set; }
[Required]
[Column(TypeName = "text")]
public string Title { get; set; }
[Required]
[Column(TypeName = "text")]
public string Description { get; set; }
public string Status { get; set; }
[Column(TypeName = "text")]
public string WantedUser { get; set; }
[InverseProperty(nameof(TaskTaskGroup.Task))]
public virtual ICollection<TaskTaskGroup> TaskTaskGroups { get; set; }
}
public class TaskGroup
{
public TaskGroup()
{
TaskTaskGroups = new HashSet<TaskTaskGroup>();
}
[Required]
public int Id { get; set; }
[Required]
public string GroupName { get; set; }
[InverseProperty(nameof(TaskTaskGroup.TaskGroup))]
public virtual ICollection<TaskTaskGroup> TaskTaskGroups { get; set; }
}
public class TaskTaskGroup
{
[Key]
public int Id { get; set; }
public int TaskId { get; set; }
[ForeignKey(nameof(TaskId))]
[InverseProperty(nameof(TaskTaskGroup.Task.TaskTaskGroups))]
public virtual Task Task { get; set; }
public int TaskGroupId { get; set; }
[ForeignKey(nameof(TaskGroupId))]
[InverseProperty(nameof(TaskTaskGroup.Task.TaskTaskGroups))]
public virtual TaskGroup TaskGroup { get; set; }
}
public class TaskDbContext : DbContext
{
public TaskDbContext()
{
}
public TaskDbContext(DbContextOptions<TaskDbContext> options)
: base(options)
{
}
public DbSet<Task> Tasks { get; set; }
public DbSet<TaskGroup> TaskGroups { get; set; }
public DbSet<TaskTaskGroup> TaskTaskGroups { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(#"Server=localhost;Database=Task;Trusted_Connection=True;");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TaskTaskGroup>(entity =>
{
entity.HasOne(d => d.Task)
.WithMany(p => p.TaskTaskGroups)
.HasForeignKey(d => d.TaskId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_TaskTaskGroup_Task");
entity.HasOne(d => d.TaskGroup)
.WithMany(p => p.TaskTaskGroups)
.HasForeignKey(d => d.TaskGroupId)
.HasConstraintName("FK_TaskTaskGroup_TaskCroup");
});
}
}

Entity Framework - How to configure the User Roles Many to Many Relationship

Below is the definition of the User entity, there is a navigation property Roles
public class User
{
public User()
{
Roles = new List<Role>();
}
public string Id { get; set; }
public string Username { get; set; }
public virtual ICollection<Role> Roles { get; set; }
Below is definition of the Role entity
public class Role
{
public string Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
What i want is to define the many to many relationship and generate a relationship table UserRole which use UserId as the left key and RoleId as the right key, so how to write the configuration code?
User:
public class User
{
public User()
{
Roles = new List<Role>();
}
public string Id { get; set; }
public string Username { get; set; }
public virtual ICollection<UserRole> Roles { get; set; }
}
Role:
public class Role
{
public string Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
UserRole:
public class UserRole
{
public string Id { get; set; }
public string UserId { get; set; }
public string RoleId{ get; set; }
public virtual User User { get; set; }
public virtual Role Role { get; set; }
}
Override the OnModelCreating method in your dbcontext:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<User>()
.HasMany(c => c.Roles )
.WithMany()
.Map(x =>
{
x.MapLeftKey("UserId");
x.MapRightKey("RoleId");
x.ToTable("UserRoles");
});
}

How to properly map entities using Fluent API?

I have two entities, a User and a UserProfile. The PK of User is UserId, the PK of UserProfile is UserProfileId. Every time a new user is created in my app, I create a new UserProfile whose PK is the same as the PK in User. When I then try to go update properties on the UserProfile I end up getting multiplicity errors or schema invalid errors. Here are my two entities:
public class User
{
public Guid UserId { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int? PhoneExtension { get; set; }
public string Comment { get; set; }
public Boolean IsApproved { get; set; }
public int PasswordFailuresSinceLastSuccess { get; set; }
public DateTime? LastPasswordFailureDate { get; set; }
public DateTime? LastActivityDate { get; set; }
public DateTime? LastLockoutDate { get; set; }
public DateTime? LastLoginDate { get; set; }
public string ConfirmationToken { get; set; }
public DateTime? CreateDate { get; set; }
public Boolean IsLockedOut { get; set; }
public DateTime? LastPasswordChangedDate { get; set; }
public string PasswordVerificationToken { get; set; }
public DateTime? PasswordVerificationTokenExpirationDate { get; set; }
public virtual ICollection<Role> Roles { get; set; }
public virtual UserProfile UserProfile { get; set; }
}
public class UserProfile
{
public Guid UserProfileId { get; set; }
public virtual User ProfileOwner { get; set; }
public Int64? HomePhone { get; set; }
public Int64? MobilePhone { get; set; }
public virtual User Manager { get; set; }
}
..and here are my only defined relationships using Fluent API.
modelBuilder.Entity<UserProfile>()
.HasKey(e => e.UserProfileId);
modelBuilder.Entity<UserProfile>()
.Property(e => e.UserProfileId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
modelBuilder.Entity<UserProfile>()
.HasRequired(e => e.ProfileOwner)
.WithRequiredDependent(r => r.UserProfile);
Finally, my UserService creates a new user and at the same time creates a new UserProfile whose Guid UserProfileId is the same as the User's Guid UserId. Right after the user and profile are created, I try to update the manager in the UserProfile with my UserProfileService using this:
public void UpdateUserProfile(UserProfile updatedUserProfile)
{
UserProfile oldUserProfile = GetUserProfileByID(updatedUserProfile.UserProfileId);
oldUserProfile.Manager = updatedUserProfile.Manager;
oldUserProfile.HomePhone = updatedUserProfile.HomePhone;
oldUserProfile.MobilePhone = updatedUserProfile.MobilePhone;
this.SetEntityState(oldUserProfile, EntityState.Modified);
this.UnitOfWork.SaveChanges();
}
The this.SetEntityState line throws this error:
Multiplicity constraint violated. The role 'UserProfile_ProfileOwner_Source' of the relationship 'WhelenPortal.Data.Context.UserProfile_ProfileOwner' has multiplicity 1 or 0..1.
I've been trying to get this working for TWO DAYS now, PLEASE HELP!!! Thanks in advance.
As requested, here is some additional information. I'm using the repository pattern and unit of work here. My GetUserProfileById code is below. The service uses the repository so I show both.
public UserProfile GetUserProfileByID(Guid id)
{
if (id == null)
throw new BusinessServicesException(Resources.UnableToRetrieveUserProfileExceptionMessage, new ArgumentNullException("id"));
try
{
Model.UserProfile userProfile = _userProfileRepository.GetUserProfileByID(id);
if (userProfile != null)
return ToServicesUserProfile(userProfile);
return null;
}
catch (InvalidOperationException ex)
{
throw new BusinessServicesException(Resources.UnableToRetrieveUserProfileExceptionMessage, ex);
}
}
..and the repository:
public UserProfile GetUserProfileByID(Guid id)
{
return this.GetDbSet<UserProfile>().Find(id);
}
So after much playing around this is what ended up working for me, hopefully it can help someone else in some fashion. My User class stayed exactly the same but my UserProfile class changed to this:
public class UserProfile
{
public Guid UserProfileId { get; set; }
public virtual User ProfileOwner { get; set; }
public Guid? ManagerId { get; set; }
public virtual User Manager { get; set; }
public Int64? HomePhone { get; set; }
public Int64? MobilePhone { get; set; }
}
And here is the fluent mapping:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<User>()
.HasOptional(u => u.UserProfile)
.WithRequired(u => u.ProfileOwner);
modelBuilder.Entity<UserProfile>()
.HasOptional(u => u.Manager)
.WithMany()
.HasForeignKey(u => u.ManagerId);
}

MVC EntityFramework user roles are not coming through in context

I have the following models:
User:
public class User : IEntity, INamedType
{
public virtual int UserId { get; set; }
[Column(TypeName = "varchar")]
[StringLength(100)]
public virtual string UserName { get; set; }
[Column(TypeName = "varchar")]
[StringLength(100)]
public virtual string FirstName { get; set; }
[Column(TypeName = "varchar")]
[StringLength(100)]
public virtual string LastName { get; set; }
[Column(TypeName = "varchar")]
[StringLength(200)]
public virtual string EmailAddress { get; set; }
public int AreaId { get; set; }
[Column(TypeName = "varchar")]
[StringLength(64)]
public string CreatedByUserName { get; set; }
public DateTime CreatedDateTime { get; set; }
[Column(TypeName = "varchar")]
[StringLength(64)]
public string LastModifiedByUserName { get; set; }
public DateTime? LastModifiedDateTime { get; set; }
public bool Active { get; set; }
//Navigation properties
public virtual Area Area { get; set; }
public virtual ICollection<Role> Roles { get; set; }
}
Role:
public class Role : IEntity
{
public int RoleId { get; set; }
[Column(TypeName = "varchar")]
[StringLength(100)]
public string Name { get; set; }
[Column(TypeName = "varchar")]
[StringLength(1000)]
public string Description { get; set; }
[Column(TypeName = "varchar")]
[StringLength(64)]
public string CreatedByUserName { get; set; }
public DateTime CreatedDateTime { get; set; }
[Column(TypeName = "varchar")]
[StringLength(64)]
public string LastModifiedByUserName { get; set; }
public DateTime? LastModifiedDateTime { get; set; }
//Navigation Properties
public ICollection<User> Users { get; set; }
}
UserRole:
public class UserRole
{
public int UserId { get; set; }
public int RoleId { get; set; }
//Navigation properties
public virtual User User { get; set; }
public virtual Role Role { get; set; }
}
I also have a CustomRoleProvider with this method:
public override string[] GetRolesForUser(string username)
{
var user = _unitOfWork.UserRepository.GetUser(username);
var roles = from r in user.Roles
select r.Name;
if (roles != null)
return roles.ToArray();
else
return new string[] { };
}
so this all works fine until a an entry is added or removed from the UserRole table. For the User this record relates to the new Role when added by adding a UserRole row does not come through in the GetRolesForUser method. Likewise if a UserRole record is removed the Role keeps coming through for the record.
If however an IISReset occurs then all the correct records come through.
Anyone know why this would be happening and how to rectify?
The UserRole entity class conflicts with the join table created by EF for the many to many relationship.
You should either remove the UserRole entity or map the relationships in User and Role to the join entity
public class User : IEntity, INamedType
{
public virtual int UserId { get; set; }
public virtual ICollection<UserRole> Roles { get; set; }
}
public class Role : IEntity
{
public int RoleId { get; set; }
//Navigation Properties
public ICollection<UserRole> Users { get; set; }
}