Entity Framework 6 - inheritance and navigation properties on base class

c# entity-framework entity-framework-6 inheritance orm

Question

I have a problem with navigation properties and inheritance.

This is my problem: I have a base Person class and classes User and Worker which inherit from Person. On the DB level I'm using single table inheritance or table per hierarchy (TPH) inheritance. So there a single table with a discriminator column.

Both User and Worker need to have a Company relation, so I would like to define it on the Person class.

I define my model like this:

[Table("mydb.person")]
public abstract partial class Person
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public long ID { get; set; }

    public long? CompanyID { get; set; }
    [ForeignKey("CompanyID")]
    public virtual Company Company { get; set; }
    ...
}

public partial class User : Person
{
    ...
}

public partial class Worker : Person
{
    ....
}

[Table("mydb.company")]
public partial class Company
{
    public Company()
    {
        this.People = new HashSet<Person>();
        this.Users = new HashSet<User>();
        this.Workers = new HashSet<Worker>();
    }

    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public long ID { get; set; }

    public virtual ICollection<Person> People { get; set; }

    public virtual ICollection<User> Users { get; set; }

    public virtual ICollection<Worker> Workers { get; set; }

    ...
}

Now, when I try to do a query to get the user and related company, for example:

dbSet.Where(u => u.Username == username).Include(x => x.Company).FirstOrDefault();

The query fails with this exception:

Unknown column 'Extent1.Company_ID' in 'field list

If I examine the result SQL it looks something like this:

SELECT
1 AS `C1`, 
@gp2 AS `C2`, 
`Extent1`.`ID`, 
`Extent1`.`CompanyID`, 
`Extent1`.`Username`,
...
`Extent1`.`Company_ID`
FROM `person` AS `Extent1`
 WHERE `Extent1`.`Discriminator` = @gp1 

It includes the extra Company_ID column, which doesn't exist.

I tried a few thing, nothing worked out:

  • renaming the column from CompanyID to Company_ID -> it generates a Column_ID1 in SQL and throws the same exception
  • removing the Users and Workers relations from Company -> it throws an exception saying it doesn't know how to map User and Company entities:

Unable to determine the principal end of an association between the types 'Models.User' and 'Models.Company'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.

  • If I remove all 3 navigation properties from Company it throws the same mapping exception as above

I'm out of "clean" ideas at the moment. The only thing that could work is to do some dirty hack, define all the relations on child classes, and do separate queries and merging in the base class if both users and workers are required.

Do you have any suggestions?

1
1
6/20/2018 11:27:16 AM

Accepted Answer

Remove the Users and Workers collection properties.

public virtual ICollection<User> Users { get; set; }

public virtual ICollection<Worker> Workers { get; set; }

As your Company navigation property is defined on Person the associated back navigation property has to be an ICollection of Person.

The People collection will contain all the associated workers and users. The two extra properties Users and Workers are interpreted as completely new relationships and because you do not have corresponding properties and foreign keys on User or Worker EF generates it virtually.

5
6/30/2014 11:53:14 AM

Popular Answer

Answer to the comment. Just for the sake of formatting as a second answer ;-)

With eager loading if you start with the Company

var companies = db.Company.Include(p => p.People);

It will always get the Users and the Workers.

If you use eager loading starting at the people.

var users = db.People.OfType<User>().Include(p => p.Company).ToList();
var companies = users.Select(p => p.Company).Distinct().ToList();

the People navigation property of your companies has just the Users.

Also you could execute two separate statements and the fixup of the database context will automatically fill the Navigation properties.

var company = db.Company.Where(p => p.ID > 100).ToList();
var copanyUsers = db.Company.Where(p => p.ID > 100)
                      .SelectMany(p => p.People).OfType<User>().ToList();


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow