1

I have two entities User and Profile. Each of those entities inherits from an Entity class which contains an Id property. The relation between the User and the Profile are One-To-One relation. I am trying to use the same Id created for the User entity to be the Id for the Profile entity, i.e. User.Id == Profile.Id.

I have been trying to do this with FluentAPI but i am unable to. I have read this question and also read this post, but i am still unable to find a solution to my problem.

Here is the code for my entities:

public class Entity<TKey> : IEntity
{
    /// <summary>
    /// Unique identifier for this entity.
    /// </summary>
    public virtual int Id { get; set; }
}

public class User : Entity<int>
{
    public virtual Profile ProfileItem { get; set; }
}

public class Profile : Entity<int> 
{

}

Here are my trials so far, in which all failed:

  • This mapping generated a ProfileItemId in the User table:

    modelBuilder.Entity<User>().HasOne(u => u.ProfileItem)
        .WithOne()
        .HasForeignKey<Profile>(p => p.Id)
        .IsRequired()
        .OnDelete(DeleteBehavior.Cascade);
    
  • I tried adding an inverse navigation property in the Profile entity for User. The User table was mapped correctly, but a UserId column was added to the Profile table:

    modeBuilder.Entity<User>().HasOne(u => u.ProfileItem)
        .WithOne(u => u.User)
        .HasForeignKey<Profile>(p => p.Id)
        .IsRequired()
        .OnDelete(DeleteBehavior.Cascade);
    

The only solution for this problem as reported in the question that i mentioned earlier, is that you have to add an inverse property and then use Data Annotations. I tried the following and it worked:

public class Entity<TKey> : IEntity
{
    /// <summary>
    /// Unique identifier for this entity.
    /// </summary>
    public virtual int Id { get; set; }
}

public class User : Entity<int>
{
    [InverseProperty("User")]
    public virtual Profile ProfileItem { get; set; }
}

public class Profile : Entity<int> 
{
    [ForeignKey("Id")]
    public virtual User User { get; set; }
}

How to get the same to work with FluentAPI?

ykh
  • 1,775
  • 3
  • 31
  • 57
  • 1
    All your attempts except the last (double mapping) are correct and should work. Just make sure when you use `.WithOne()`, there is no reference navigation property `User` in `Profile`. Try reproducing the issue in a clean project (because I can't - everything works as expected) and probably you'll find the cause. – Ivan Stoev Nov 20 '17 at 09:08
  • When i remove the User property from the Profile, a column ProfileItemId is created in the User table. – ykh Nov 20 '17 at 09:14
  • I removed the third trial from my question... – ykh Nov 21 '17 at 08:30

1 Answers1

0

What @Ivan said was correct. In the example i posted, i did the mappings on the OnModelCreating event, but in my actual code, i used a base class for mapping, and that was the problem.

Here is my current code which produced the problem:

  • A base class for entity mapping

    public class EntityTypeConfigurationBase<T> : IEntityTypeConfiguration<T> where T : class, IEntity
    {
        private bool BIgnoreBaseProps = true;
    
        public EntityTypeConfigurationBase(bool bIgnoreBaseProps = true) : base()
        {
            BIgnoreBaseProps = bIgnoreBaseProps;
        }
    
        public void Configure(EntityTypeBuilder<T> builder)
        {
            if (BIgnoreBaseProps)
            {
                builder.Ignore(x => x.State);
                builder.Ignore(x => x.AssociationState);
            }
        }
    }
    
  • My mapping class

    public class UserMap : EntityTypeConfigurationBase<User>
    {
    
       new public void Configure(EntityTypeBuilder<User> builder)
       {
    
        builder.HasKey(u => u.Id);
        builder
            .HasOne(u => u.ProfileItem)
            .WithOne()
            .HasForeignKey<Profile>(p => p.Id)
            .IsRequired()
            .OnDelete(DeleteBehavior.Cascade);
        builder.ToTable("User");
    
        base.Configure(builder);
        }
    }
    

The issue was in the mapping class, in the Configure method. In the base class, i needed to ignore some shared properties, and in the mapping classes, i needed to do my custom mappings. The method used is named Configure which is required to be implemented for the mapping. Since the same method name is used for both, the base and the derived classes, this resulted in a duplicate call. The first call was for the base type method, and the second was to the mapping classes. The mapping was done twice, and the foreign key (my issue) was generated on the first mapping.

The solution to this issue was to mark the base type method as virtual, and then overriding the method on the base type. The base type method call will take the same ModelBuilder object and will not duplicate the mapping again.

ykh
  • 1,775
  • 3
  • 31
  • 57