I started working with Entity Framework and I have one BIG problem. I want a many-to-many relation between entities User and Role.
So I created 3 tables User, Role, UserRoles
Next I created 3 entities:
public class User
{
public virtual int UserId { get; protected set; }
public virtual string UserName { get; set; }
public virtual string Password { get; set; }
public virtual DateTime CreateDate { get; set; }
public virtual ICollection<UserRole> Roles { get; set; }
public virtual ICollection<UserRole> CreatedRoles { get; set; }
}
public class Role
{
public virtual int RoleId { get; protected set; }
public virtual string Name { get; set; }
}
public class UserRole
{
public virtual int UserRoleId { get; protected set; }
public virtual int UserId { get; set; }
public virtual User User { get; set; }
public virtual int RoleId { get; set; }
public virtual Role Role { get; set; }
public virtual DateTime CreateDate { get; set; }
public virtual int CreateUserId { get; set; }
public virtual User CreateUser { get; set; }
}
I want to separate mapped user from created user. And In this step Entity Framework start to throw a bugs.
Next I want navigate from User object to his mapped roles and to his created mappings.
Can I configure Entity Framework to do these things?
You should apply InverseProperty attribute:
public class User
{
public virtual int UserId { get; protected set; }
public virtual string UserName { get; set; }
public virtual string Password { get; set; }
public virtual DateTime CreateDate { get; set; }
[InverseProperty("User")]
public virtual ICollection<UserRole> Roles { get; set; }
[InverseProperty("CreateUser")]
public virtual ICollection<UserRole> CreatedRoles { get; set; }
}
Related
I'm creating a freelance task management app. Here is the logic. These are the 3 POCOs involved
User (LoggedIn User)
A user can create any number of clients
A user can create any number of projects
A user is created, updated by another user
Client (Freelance Client)
A client can have any number of projects
A client is created, updated by a user
Project
A project should have only one client
A project is created, updated by a user
These are my Entities. I'm using Code-First approach and uses SQLServer. I removed many properties for posting here
User Entity
public class User
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime? CreatedOn { get; set; }
public DateTime? UpdatedOn { get; set; }
public virtual User CreatedBy { get; set; }
public virtual User UpdatedBy { get; set; }
[ForeignKey("UserId")]
public int? CreatedById { get; set; }
[ForeignKey("UserId")]
public int? UpdatedById { get; set; }
public virtual ICollection<Project> Projects { get; set; }
public virtual ICollection<Client> Clients { get; set; }
}
Client Entity
public class Client
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int Id { get; set; }
public string Company { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public string Address { get; set; }
public DateTime? CreatedOn { get; set; }
public DateTime? UpdatedOn { get; set; }
public virtual User CreatedBy { get; set; }
public virtual User UpdatedBy { get; set; }
[ForeignKey("UserId")]
public int? CreatedById { get; set; }
[ForeignKey("UserId")]
public int? UpdatedById { get; set; }
public ICollection<Project> Projects { get; set; }
}
Project Entity
public class Project
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string ProjectLogo { get; set; }
public DateTime? CreatedOn { get; set; }
public DateTime? UpdatedOn { get; set; }
public virtual User CreatedBy { get; set; }
public virtual User UpdatedBy { get; set; }
[ForeignKey("UserId")]
public int? CreatedById { get; set; }
[ForeignKey("UserId")]
public int? UpdatedById { get; set; }
public virtual Client Client { get; set; }
}
I'm now facing so many issues with ForginKeys and mappings. What is wrong with the entity mappings here?
I can't insert 2 rows in clients table with same CreatedById, EF is constraining it with unique values.
This is my fluent mappings
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
//User
modelBuilder.Entity<User>().HasOne(a => a.CreatedBy).WithOne().OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<User>().HasOne(a => a.UpdatedBy).WithOne().OnDelete(DeleteBehavior.NoAction);
//Project
modelBuilder.Entity<Project>().HasOne(a => a.CreatedBy).WithOne().OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<Project>().HasOne(a => a.UpdatedBy).WithOne().OnDelete(DeleteBehavior.NoAction);
//Client
modelBuilder.Entity<Client>().HasOne(a => a.CreatedBy).WithOne().OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<Client>().HasOne(a => a.UpdatedBy).WithOne().OnDelete(DeleteBehavior.NoAction);
}
How can I implement this requirement?
This was a relationship issue with my entities. I made my properties look like
public DateTime? CreatedOn { get; set; }
public DateTime? UpdatedOn { get; set; }
public virtual User CreatedBy { get; set; }
public virtual User UpdatedBy { get; set; }
public int? CreatedById { get; set; }
public int? UpdatedById { get; set; }
And added fluent mapping
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
//User
modelBuilder.Entity<User>().HasOne(a => a.CreatedBy).WithMany().OnDelete(DeleteBehavior.SetNull);
modelBuilder.Entity<User>().HasOne(a => a.UpdatedBy).WithMany().OnDelete(DeleteBehavior.SetNull);
//Project
modelBuilder.Entity<Project>().HasOne(a => a.CreatedBy).WithMany().OnDelete(DeleteBehavior.SetNull);
modelBuilder.Entity<Project>().HasOne(a => a.UpdatedBy).WithMany().OnDelete(DeleteBehavior.SetNull);
//Client
modelBuilder.Entity<Client>().HasOne(a => a.CreatedBy).WithMany().OnDelete(DeleteBehavior.SetNull);
modelBuilder.Entity<Client>().HasOne(a => a.UpdatedBy).WithMany().OnDelete(DeleteBehavior.SetNull);
}
Adding One-Many relationship resolved my issue. Thanks to #David
I have some problems to correctly use Entity Framework with one to many relationships.
here is a part of the model :
public class User
{
[Required]
public int UserID { get; set; }
[Required]
public string Login { get; set; }
//one to many relationship
public virtual Group Group { get; set; }
}
public class Group
[Required]
public int GroupID { get; set; }
[Required]
public string Name { get; set; }
public virtual Group ParentGroup { get; set; }
public virtual List<Group> ChildGroups { get; set; }
[Required]
public User CreatedBy { get; set; }
}
public class OtherClass
[Required]
public int OtherClassID { get; set; }
[Required]
public User CreatedBy { get; set; }
}
I have a question about the update an OtherClass entity.
When I get an entity to update, it doesn't have dependencies loaded, I have to add them with an Include like this :
using (var db = new DalContext())
{
var test = db.OtherClasses.Include(o => o.CreatedBy).Single....
}
But if I want to update an OtherClass entity, why all dependencies have to be loaded?
To resume, when I want to update an OtherClass entity, CreatedBy dependency must be completely loaded, with the group, and for the group, the parent group and child groups, with users...
Is there a possibility to add or update an entity with only ID attributes filled?
Thank you for your help.
I'm not an expert, but here is what I would try:
I believe you should have a UserId in your Group and OtherClass classes, like this:
public class User
{
[Required]
public int UserID { get; set; }
[Required]
public string Login { get; set; }
//one to many relationship
public virtual Group Group { get; set; }
}
public class Group
[Required]
public int GroupID { get; set; }
[Required]
public string Name { get; set; }
public virtual Group ParentGroup { get; set; }
public virtual List<Group> ChildGroups { get; set; }
[Required]
public int UserId { get; set; }
//If you want to lazy-load the user when you load the Group entity, use virtual
//Then you don't need the .Include(g=>g.User)
public virtual User CreatedBy { get; set; }
}
public class OtherClass
[Required]
public int OtherClassID { get; set; }
[Required]
public int UserId { get; set; }
public virtual User CreatedBy { get; set; }
}
To update the OtherClass, you update/set the Id properties, not the User property.
I am having issues with making the navigation properties correctly since I get the error
The Foreign key component ID is not a declared property on type Administrator.
My model basically consists of a base class User which has two derived classes Administrator and Common user. I believe this is a one to many relation since there can be multiple common and administrator users.
My classes look like this:
[Table("User")]
public class User
{
[Key]
public int ID { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public DateTime RegisterDate { get; set; }
public virtual ICollection<Common> CommonUsers { get; set; }
public virtual ICollection<Administrator> Administrators { get; set;}
}
public class Administrator : Usuario
{
[ForeignKey("User")]
public int ID { get; set; }
public virtual User User { get; set; }
}
public class Common : Usuario
{
[ForeignKey("User")]
public int ID { get; set; }
public virtual User User { get; set; }
}
I set the foiregn key on both ID attributes in the derived classes to reference the User ID which is the primary key for the table.
Any input would be appreciated.
Semantically your construction
public virtual ICollection<Common> CommonUsers { get; set; }
public virtual ICollection<Administrator> Administrators { get; set;}
mean that a single user can be multiple Administrators and each Administrator can be assigned to a single user.
public virtual User User { get; set; }
From the logic explained it should vise versa.
public class User
{
[Key]
public int ID { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public DateTime RegisterDate { get; set; }
//public virtual ICollection<Common> CommonUsers { get; set; }
public virtual Administrator Administrators { get; set;}
}
public class Administrator : Usuario
{
//Primary key is required but not defined
[ForeignKey("User")]
public int ID { get; set; }
//same admin role applies to many users
public virtual ICollection<User> User { get; set; }
}
Also note that class name User may shadow / conflict with System.Security.Principal.User
Most probably you mean
public class Usuario // User
{
[Key]
public int ID { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public DateTime RegisterDate { get; set; }
//public virtual ICollection<Common> CommonUsers { get; set; }
//public virtual ICollection<Administrator> Administrators { get; set;}
//define 1-to-1(0)
public virtual Common CommonUsers { get; set; }
public virtual Administrator Administrators { get; set;}
}
public class Administrator : Usuario
{
[ForeignKey("User")]
public int ID { get; set; }
public virtual User User { get; set; }
}
I have a detached object with many different properties. Some of these properties need to be "Attached" at some point, so that EF does not try to insert them into the database.
public partial class Load
{
public virtual int Id {get;set;}
[Required]
public virtual int CustomerId { get; set; }
[ForeignKey("CustomerId")]
public virtual Customer Customer { get; set; }
[Required]
public virtual long CreatedByApplicationUserId { get; set; }
[ForeignKey("CreatedByApplicationUserId")]
public virtual ApplicationUser CreatedByApplicationUser { get; set; }
public virtual long? ModifiedByApplicationUserId { get; set; }
[ForeignKey("ModifiedByApplicationUserId")]
public virtual ApplicationUser ModifiedByApplicationUser { get; set; }
public virtual long? CoveredByApplicationUserId { get; set; }
[ForeignKey("CoveredByApplicationUserId")]
public virtual ApplicationUser CoveredByApplicationUser { get; set; }
public virtual bool IsNetworkLoad { get; set; }
public virtual bool IsExport { get; set; }
public virtual bool CanTrackLoad { get; set; }
[Required]
public virtual DateTime CreatedDateTime { get; set; }
public virtual DateTime? ModifiedDateTime { get; set; }
public virtual string BillingReferenceNumber { get; set; }
[Required]
public virtual int LoadStatusId { get; set; }
[ForeignKey("LoadStatusId")]
public virtual LoadStatus LoadStatus { get; set; }
public virtual Freight Freight { get; set; }
public virtual ICollection<LoadOrigin> LoadOrigins { get; set; }
public virtual ICollection<LoadDestination> LoadDestinations { get; set; }
public virtual ICollection<LoadNote> LoadNotes { get; set; }
public virtual ICollection<LoadCarrier> LoadCarriers { get; set; }
}
Take my property LoadCarries for example. LoadCarries has a property Carrier that is populated from the db with no tracking.
public partial class LoadCarrier
{
public virtual int Id {get;set;}
public virtual bool IsDispatched { get; set; }
public virtual bool IsPrimary { get; set; }
[MaxLength(25)]
public virtual string CarrierProNumber { get; set; }
[MaxLength(1000)]
public virtual string RCNotes { get; set; }
[Required]
public virtual int CarrierId { get; set; }
[ForeignKey("CarrierId")]
public virtual Carrier Carrier { get; set; }
[MaxLength(50)]
[Required]
public virtual string Dispatcher { get; set; }
public virtual long LoadId { get; set; }
[ForeignKey("LoadId")]
public virtual Load Load { get; set; }
public virtual LoadDriver LoadDriver { get; set; }
}
Once I have my object populated properly, I try to save to the db. Since these object are all detached the save fails because I have a unique constraint on members of the Carriers object because EF incorrectly tries to insert an object that already exists in the db.
public virtual int Create(T entity, long userId)
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
dbSet.Add(entity);
return context.SaveChanges(userId);
}
I have tried several times to change the state of the Carriers object to "Unchanged", but the I get a duplicate primary key exception. I've also tried to add the Load object to the context and then set the Carrier object to an object I explicitly pull from the db. Is there a way by which I can attach this detached object properly?
Don't set the Carrier property of LoadCarrier, but instead only set the CarrierId.
This is enough for EF to know that it has to set the foreign key to an existing record.
There are other ways to do this, but since the Carrier is detached, it is most likely better to keep it attached and not to pull it in in the first place. I imagine you are preloading the carriers untracked because they are kind of fixed.
I would try to make a relation between two entities : A goup (with an owner and users) and user (which belongs to a unique group). Code :
public class User
{
public int Id { get; set; }
public int GroupId { get; set; }
public virtual Group group { get; set; }
}
public class Group
{
public int Id { get; set; }
public User Owner { get; set; }
public virtual ICollection<User> Users { get; set; }
}
I don't arrive with data annotation to make it corectly.
Thank you so much for your propositions
A solution that works for me :
public class User
{
public int Id { get; set; }
public int? GroupId { get; set; }
[InverseProperty("Users")]
public virtual Group group { get; set; }
}
public class Group
{
public int Id { get; set; }
public int? OwnerId { get; set; }
public User Owner { get; set; }
public virtual ICollection<User> Users { get; set; }
}