Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Entity Framework CTP5 Code-First: Mapping a class with multiple collections of another class

With EF CTP5 Code-First I am trying to map a class model which contains multiple collections in one class pointing to another class. Here is an example of what I mean:

public class Company
{
    public int CompanyId { get; set; }
    public IList<Person> FemaleEmployees { get; set; }
    public IList<Person> MaleEmployees { get; set; }
}

public class Person
{
    public int PersonId { get; set; }
    public Company Company { get; set; }
}

If I let the database create from this model with a DbContext without further customization, like so:

public class MyContext : DbContext
{
    public DbSet<Company> Companies { get; set; }
    public DbSet<Person> People { get; set; }
}

... then I get two tables in SQL Server, a simple Companies table with only a CompanyId column and a People table with the following columns ("FKRN" means "Foreign Key Relationship Name", as created by EF in SQL Server):

PersonId            int     not nullable
CompanyCompanyId    int     nullable       FKRN: Company_FemaleEmployees
CompanyCompanyId1   int     nullable       FKRN: Company_MaleEmployees
CompanyCompanyId2   int     nullable       FKRN: Person_Company

The last three columns have all a foreign key relationship to the primary key CompanyId of the Companies table.

Now I have several questions:

  • 1) Why do I get three foreign key columns in the People table? I actually expected two. If I remove the property public Company Company { get; set; } from the Person the third column CompanyCompanyId2 disappears but I also lose the reference property in the class.

  • 2) Let's say I drop the Company property from the Person table (I don't need it really in my model). Is there a way to give the two remaining foreign key columns another name than the auto-created CompanyCompanyId and CompanyCompanyId1? (For instance FCompanyId and MCompanyId to indicate the relation to the FemaleEmployees and MaleEmployees collections.)

  • 3) Is there any way to define this model with only one foreign key CompanyId in the People table? Surely I would need a differentiating additional column in the Person class (like bool IsFemale). A Person is either part of the FemaleEmployees or the MaleEmployees collection, never in both (naturally in this example), so with SQL I could fetch those collections by something like WHERE IsFemale = true/false AND CompanyId = 1. I am wondering if I could give EntityFramework a hint to load the two collections this way. (Here I would like to avoid to extend the model by a FemalePerson and MalePerson class which both derive from Person as base class and then use for instance Table-Per-Hierarchy mapping, since these derived classes would be empty and artificial and had no other purpose except enabling the mapping to SQL Server.) Having only one foreign key CompanyId would allow me to make it non-nullable which isn't possible with two foreign keys (both can never be non-null in the same row).

Thank you for feedback and suggestions in advance!

like image 729
Slauma Avatar asked Feb 21 '11 15:02

Slauma


2 Answers

  • To question (1): EF cannot map the single reference property Company in class Person to two different collection endpoints FemaleEmployees and MaleEmployees in class Company at the same time. The mapping conventions assume that there is actually a third endpoint in Company which isn't exposed in the model. Therefore a third foreign key is created.

  • To question (2): With the EF 4.1 Release Candidate it is now possible to specify the database column name of foreign keys in the Fluent API (which wasn't possible with EF CTP5) by using the Map method of the ForeignKeyNavigationPropertyConfiguration class:

    modelBuilder.Entity<Company>()
                .HasMany(c => c.FemaleEmployees)
                .WithOptional()
                .Map(conf => conf.MapKey("FCompanyId"))
                .WillCascadeOnDelete(false);
    
    modelBuilder.Entity<Company>()
                .HasMany(c => c.MaleEmployees)
                .WithOptional()
                .Map(conf => conf.MapKey("MCompanyId"))
                .WillCascadeOnDelete(false);
    
  • To question (3): I still have no idea.

Edit

Just to close this old question now: (3) (relating two navigation properties in one entity to the same endpoint of another entity) is not possible, for example: Specific Entity Framework Code First Many to 2 Model Mapping ... (and I remember many other questions which were looking for a solution for such a scenario without success)

like image 180
Slauma Avatar answered Sep 29 '22 06:09

Slauma


I think you could do this:

public class Company
{
    public int CompanyId { get; set; }
    public ICollection<Person> Employees { get; set; }
    public IEnumerable<Person> MaleEmployees {
        get
        {
            Employees.Where(x=> !x.IsFemale);
        }
    }
}

public class Person
{
    public int PersonId { get; set; }
    public Company Company { get; set; }
}

You just have one CompanyID FK at People table.

You can load Male Employees using EF context:

context.Entry(companyInstance)
    .Collection(p => p.Employees)
    .Query()
    .Where(u => !u.IsFemale)
    .Load();

I think that your approach isn´t so good, because, what happens when I add a male to Company.FemaleEmployees? EF dont know this rules

like image 44
Felipe Pessoto Avatar answered Sep 29 '22 05:09

Felipe Pessoto