4

I have the following classes, set for testing:

public class Company
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Employee
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    public string Name { get; set; }

    public int CompanyId { get; set; }
    public virtual Company Company { get; set; }
}

public class EFTestDbContext : DbContext
{
    public DbSet<Employee> Employees { get; set; }
    public DbSet<Company> Companies { get; set; }
}

For the sake of testing, I wanted to insert one company and one employee for that company with single SaveChanges call, like this:

Company company = new Company
{
    Name = "Sample company"
};

context.Companies.Add(company);

// ** UNCOMMENTED FOR TEST 2
//Company company2 = new Company
//{
//    Name = "Some other company"
//};
//context.Companies.Add(company2);

Employee employee = new Employee
{
    Name = "Hans",
    CompanyId = company.Id
};
context.Employees.Add(employee);

context.SaveChanges();

Even though I am not using navigational properties, but instead I've made relation over Id, this somehow mysteriously worked - employee was saved with proper foreign key to company which got updated from 0 to real value, which made me go ?!?! Some hidden C# feature?

Then I've decided to add more code, which is commented in the snippet above, making it to be inserting of 2 x Company entity and 1 x Employee entity, and then I got exception:

Unable to determine the principal end of the 'CodeLab.EFTest.Employee_Company' relationship. Multiple added entities may have the same primary key.

Does this mean that in cases where foreign key is 0, and there is a single matching entity being inserted in same SaveChanges transaction, Entity Framework will assume that foreign key should be for that matching entity?

In second test, when there are two entities matching the relation type, Entity Framework throws an exception as it is not able to figure out to which of the Companies Employee should be related to.

EDIT:

I've done one more test, and commented out one line. First test still runs properly (since default value for int is 0):

Employee employee = new Employee
{
    Name = "Hans",
    //CompanyId = company.Id // * no need for this at all
};
Admir Tuzović
  • 10,997
  • 7
  • 35
  • 71
  • 1
    Your last edit even better demonstrates that it's absolutely necessary to set references explicitly. This may cause unexpected behavior. One might even call it a bug. – Gert Arnold Jun 06 '14 at 22:17

2 Answers2

6

You're not really hitting a hidden C# feature, maybe an obscure Entity Framework feature.

When you assign the CompanyId, EF knows that the the company having Id = 0 (at that moment) is the employee's parent. On numerous occasions, EF executes DetectChanges, which also executes relationship fixup: matching foreign key values and references. This happens when you execute

context.Employees.Add(employee);

Now EF will use the reference, rather than the foreign key value, so it knows which FK value to store in the database.

When you create two companies, there are two instances having the same transient key value, so EF can't choose anymore.

So when you want to store new connected objects it's always recommended to set references in stead of FK values.

Community
  • 1
  • 1
Gert Arnold
  • 105,341
  • 31
  • 202
  • 291
2

You added the company to the companies table.

context.Companies.Add(company);

Then you set the companyId of the employee:

CompanyId = company.Id

This is all that is needed to create the relationship in sql. Likely the entity framework inserted your company, then inserted the employee with a companyId of the last identity generated.

Looking here we can see there are issues using id when there are duplicate enities. In your case both companies have an id of 0 until they are saved so entity framework can't uniquely identify a company.

Community
  • 1
  • 1
Vulcronos
  • 3,428
  • 3
  • 16
  • 24