Piotr Gankiewicz
Piotr Gankiewicz

Reputation: 25

EF code first - inheritance and relationships

Please take a look at following code:

public class SomeEntity
{
    public int Id { get; set; }

    public int UserId { get; set; }
    public virtual User User { get; set; }
}

public class SomeEntityA : SomeEntity
{
    public int Number { get; set; }
}

public class SomeEntityB : SomeEntity
{
    public string Text { get; set; }
}

public class User
{
    public int Id { get; set; }
    public int Username { get; set; }

    public virtual ICollection<SomeEntityA> SomeEntitiesA { get; set; }
    public virtual ICollection<SomeEntityB> SomeEntitiesB { get; set; }
}

My question is - is there a way to set the FluentApi to make relationships shown above work properly? Currently when new SomeEntityA object is being added to User, EF creates a new record in SomeEntities table with properly set User_Id FK, however in SomeEntitesA which is an inherited table, there's also a FK property User_Id - set as null and when I try to get SomeEntitesA collection from User object - it's empty. I do realize why that happens, but I'm not sure whether there's a way to fix this? The only solution that comes to my mind at this moment is to replace following code:

    public virtual ICollection<SomeEntityA> SomeEntitiesA { get; set; }
    public virtual ICollection<SomeEntityB> SomeEntitiesB { get; set; }

with:

    public virtual ICollection<SomeEntity> SomeEntitiesA { get; set; }
    public virtual ICollection<SomeEntity> SomeEntitiesB { get; set; }

and configure FluentApi.

Any thoughts would be highly appreciated.

Upvotes: 1

Views: 213

Answers (2)

ntziolis
ntziolis

Reputation: 10221

Have a look at this question/answer, the classes have exactly the same structure as yours and there is a detailed explanation why things are not working as expected:

Is inheritance of navigation properties supported?

As Slauma pointed out, there is a rather simple solution to get around this by doing the following (copied from the linked answer and fit to your example):

public class User
{
    public int Id { get; set; }
    public int Username { get; set; }

    // this is necessary to have access to the related SomeEntityAs/SomeEntityBs
    // also it cant be private otherwise EF will not overload it properly
    public virtual ICollection<SomeEntity> SomeEntities { get; set; }

    public IEnumerable<SomeEntityA> SomeEntitiesA { get { return this.SomeEntities.OfType<SomeEntityA>(); } }
    public IEnumerable<SomeEntityB> SomeEntitiesB { get { return this.SomeEntities.OfType<SomeEntityA>(); } }
}

Upvotes: 1

Slauma
Slauma

Reputation: 177133

Your proposed solution also won't work because you cannot relate two navigation properties User.SomeEntitiesA and User.SomeEntitiesB to the same endpoint SomeEntity.User. You would actually need two users:

public class SomeEntity
{
    public int Id { get; set; }

    public int UserAId { get; set; }
    [InverseProperty("SomeEntitiesA")]
    public virtual User UserA { get; set; }

    public int UserBId { get; set; }
    [InverseProperty("SomeEntitiesB")]
    public virtual User UserB { get; set; }
}

But then you could also keep the collection types as they are and move the users to the derived entities:

public class SomeEntity
{
    public int Id { get; set; }
}

public class SomeEntityA : SomeEntity
{
    public int Number { get; set; }

    public int UserId { get; set; }
    public virtual User User { get; set; }
}

public class SomeEntityB : SomeEntity
{
    public string Text { get; set; }

    public int UserId { get; set; }
    public virtual User User { get; set; }
}

public class User
{
    public int Id { get; set; }
    public int Username { get; set; }

    public virtual ICollection<SomeEntityA> SomeEntitiesA { get; set; }
    public virtual ICollection<SomeEntityB> SomeEntitiesB { get; set; }
}

You don't need to specify the [InverseProperty] in this case because conventions will detect now the correct pairs of navigation properties.

Upvotes: 1

Related Questions