How to properly map entities using Fluent API? - entity-framework

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);
}

Related

Entity Framework Core - 3 tier relationship

I have to apply a set of relationships with a system that incorporates a messaging system.
I have the two of my domain object with one mapping object (for the many-to-many relationship):
public class User
{
public User()
{
UserMails = new List<UserMail>();
}
public int Id { get; set; }
public ICollection<UserMail> UserMails { get; set; }
}
public class Mail
{
public Mail()
{
UserMails = new List<UserMail>();
}
public int Id { get; set; }
public string Title { get; set; }
public string Body { get; set; }
public ICollection<UserMail> UserMails { get; set; }
}
public class UserMail
{
public int Id { get; set; }
public int FromUserId { get; set; }
public User FromUser { get; set; }
public int ToUserId { get; set; }
public User ToUser { get; set; }
public int MailId { get; set; }
public Mail Mail { get; set; }
}
How would I configure this relationship using Fluent API such that there's a many to many relationship between User and Mail and Mail can have 2 foreign keys back to the UserFrom and UserTo?
Any help on this would be greatly appreciated.
If you are trying to model the relationship between a mail and its sender/recipient, then you don't need a many-to-many relation, or 2 foreign keys in your joining entity. Instead, you need 2 one-to-many relations like below -
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Mail> ReceivedMails { get; set; }
public ICollection<Mail> SentMails { get; set; }
}
public class Mail
{
public int Id { get; set; }
public string Title { get; set; }
public string Body { get; set; }
public int SenderId { get; set; }
public User Sender { get; set; }
public int RecipientId { get; set; }
public User Recipient { get; set; }
}
and you can configure them as -
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Mail>()
.HasOne(p => p.Sender)
.WithMany(p => p.SentMails)
.HasForeignKey(p => p.SenderId)
.OnDelete(DeleteBehavior.NoAction);
builder.Entity<Mail>()
.HasOne(p => p.Recipient)
.WithMany(p => p.ReceivedMails)
.HasForeignKey(p => p.RecipientId)
.OnDelete(DeleteBehavior.NoAction);
}

EFCore Generic Repository and UnitOfWork Design Pattern

when im trying to create new data and save it, im getting error at the
public int Complete()
{
return _context.SaveChanges();
}
and error is saying me that:
The value of 'Agency.ID' is unknown when attempting to save changes. This is because the property is also part of a foreign key for which the principal entity in the relationship is not known. .
i have a Base class like that:
public class Base
{
protected Base()
{
CreatedDate = DateTime.Now;
IsDeleted = false;
ModifiedDate = null;
}
public int ID { get; set; }
public int? CreatedUserId { get; set; }
public int? ModifiedUserId { get; set; }
public string CreatedUserType { get; set; }
public string ModifiedUserType { get; set; }
public DateTime? CreatedDate { get; set; }
public DateTime? ModifiedDate { get; set; }
public bool? IsActive { get; set; }
public bool? IsDeleted { get; set; }
}
i have a Agency class like that :
public class Agency : Base
{
public Agency()
{
AgencyIsComplated = false;
}
[Required, StringLength(255)]
public string AgencyName { get; set; }
[Required, StringLength(255)]
public string AgencyPhoto { get; set; }
[Required, StringLength(255)]
public string AgencyEMail { get; set; }
[Required, StringLength(13)]
public string AgencyPhone { get; set; }
[StringLength(13)]
public string AgencyBPhone { get; set; }
[StringLength(255)]
public string AgencyInfo { get; set; }
[Required, StringLength(255)]
public string AgencyTitle { get; set; }
[Required, StringLength(255)]
public string AgencyLink { get; set; }
public int AgencyExportArea { get; set; } // Join table ile yapılacak,ayrı bir tabloda tutulacak
[Required, StringLength(255)]
public string AgencyInstagram { get; set; }
public string AgencyTwitter { get; set; }
public string AgencyFacebook { get; set; }
public string AgencyLinkedin { get; set; }
public string AgencyYoutube { get; set; }
public bool AgencyIsComplated { get; set; }
[ForeignKey("CompanyID")]
public Company Company { get; set; }
[ForeignKey("LogID")]
public Log Log { get; set; }
public virtual ICollection<AgencyCompany> AgencyCompanies { get; set; }
public virtual ICollection<User> Users { get; set; }
public virtual ICollection<Log> Logs { get; set; }
}
public class AgencyConfiguration : IEntityTypeConfiguration<Agency>
{
public void Configure(EntityTypeBuilder<Agency> builder)
{
builder.HasKey(agency => agency.ID);
builder.HasMany(a => a.Logs)
.WithOne(a => a.Agency)
.HasForeignKey(a=>a.ID)
.OnDelete(DeleteBehavior.Restrict);
builder.HasMany(us => us.Users)
.WithOne(us => us.Agency)
.HasForeignKey(au=>au.ID)
.OnDelete(DeleteBehavior.Restrict);
builder.HasMany(ac => ac.AgencyCompanies)
.WithOne(ac => ac.Agency)
.OnDelete(DeleteBehavior.Restrict);
}
}
and i have got a UnitOfWork like that:
public class UnitOfWork : IUnitOfWork
{
private TradeTurkDBContext _context;
public UnitOfWork(TradeTurkDBContext context)
{
_context = context;
RepositoryAgency = new RepositoryAgency(_context);
}
public IRepository Repository { get; private set; }
public IRepositoryAgency RepositoryAgency { get; private set; }
public int Complete()
{
return _context.SaveChanges();
}
public void Dispose()
{
_context.Dispose();
}
}
im inheriting that ID on my Base Model.
the problem is getting solved when im not defining ID in the base model but i allready set up my mapping on it.
so how can i solve that error without using AgencyID in the Agency model ?
The foreign key is in the details (or child) table. Therefore, e.g. a user, should have an AgencyId as foreign key.
builder.Entity<User>()
.HasOne(u => u.Agency)
.WithMany(a => a.Users)
.HasForeignKey(u => u.AgencyId)
.OnDelete(DeleteBehavior.Restrict);
This key automatically points to the primary key of the master (or parent) table.
User.ID is a primary key. User.AgencyId is a foreign key which (automatically) relates to the primary key Agency.ID.
E.g. see: Configure One-to-Many Relationships using Fluent API in Entity Framework Core

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");
});
}

Entity Framework - 3 tables have relationships with each other

I have 3 tables as follows:
ApplicationUser:
public class ApplicationUser : IdentityUser
{
..some basic properties..
// navigation properties
public virtual ICollection<Post> Posts { get; set; }
public virtual ICollection<Album> Albums { get; set; }
}
Post:
public class Post
{
public long Id { get; set; }
public string Content { get; set; }
public int? AlbumId { get; set; }
public string UserId { get; set; }
public virtual ApplicationUser User { get; set; }
public virtual Album Album { get; set; }
}
Album:
public class Album
{
public int Id { get; set; }
public string Name { get; set; }
public string UserId { get; set; }
public virtual ApplicationUser User { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
and finally ApplicationDbContext:
modelBuilder.Entity<ApplicationUser>()
.HasMany(a=>a.Posts)
.WithRequired(a=>a.User)
.HasForeignKey(a=>a.UserId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Post>()
.HasKey(p => p.Id);
modelBuilder.Entity<Album>()
.HasKey(a => a.Id);
modelBuilder.Entity<ApplicationUser>()
.HasMany(u=>u.Albums)
.WithOptional()
.HasForeignKey(a=>a.UserId)
.WillCascadeOnDelete();
modelBuilder.Entity<Album>()
.HasMany(a=>a.Posts)
.WithRequired()
.HasForeignKey(p=>p.AlbumId)
.WillCascadeOnDelete();
When I run the migration and update database, I get an error:
The ALTER TABLE statement conflicted with the FOREIGN KEY constraint
"FK_dbo.Posts_dbo.Albums_AlbumId". The conflict occurred in database
"aspnet-Link-20161012104217", table "dbo.Albums", column 'Id'.
Could anyone tell my why they conflict? It seems pretty legit to me.
In your code you set AlbumId as nullable but in configuration defined WithRequeired():
public class Post
{
public long Id { get; set; }
public string Content { get; set; }
public int? AlbumId { get; set; } //<-- this one
public string UserId { get; set; }
public virtual ApplicationUser User { get; set; }
public virtual Album Album { get; set; }
}
modelBuilder.Entity<Album>()
.HasMany(a=>a.Posts)
.WithRequired() //<-- this one
.HasForeignKey(p=>p.AlbumId)
.WillCascadeOnDelete();
If AlbumId is nullable you should change the configuration:
//Ef by default conventions set the AlbumId as foreign key
modelBuilder.Entity<Album>()
.HasMany(a=>a.Posts)
.WithOptional(a=>a.Album);
and if AlbumId isn't nullable change the property:
public class Post
{
public long Id { get; set; }
public string Content { get; set; }
public int AlbumId { get; set; }
public string UserId { get; set; }
public virtual ApplicationUser User { get; set; }
public virtual Album Album { get; set; }
}
and use following configuration:
//Ef by default conventions set the AlbumId as foreign key
modelBuilder.Entity<Album>()
.HasMany(a=>a.Posts)
.WithRequired(a=>a.Album)
.WillCascadeOnDelete();

Entity Framework User Roles Many to Many Relationship

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");
});
}