When I run the below mentioned code EF saves the PersonAddress
with the correct PersonId
. I have NOT added the PersonAddress
entities to the Person
entity, even though I have not done this the records in my database are linked correctly.
My question is: Does EF automatically add the related entities even though I have not specified the entity that it belongs to? And if so can this not cause unwanted entity relations?
Update
It seems that the entities are saved correctly for the following reasons:
Person
the PersonId
field is 0PersonAddress.PersonId
is also 0 at the time of creation.By manually setting the Person.PersonId
to any value at the time of creation, and then setting PersonAddress.PersonId
to that same value of Person.PersonId
EF saves the data correctly as they share the same PersonId
.
So technically EF does not add the related entities automatically, they are related as they share the same PersonId
.
Please see code below as reference:
using (var context = new Models.TestEntities())
{
var person = context.People.Create();
var postalAddress = context.PersonAddresses.Create();
postalAddress.AddressLine1 = "PostalAddressLine1";
var residentialAddress = context.PersonAddresses.Create();
residentialAddress.AddressLine1 = "ResidentialAddressLine1";
context.People.Add(person);
context.PersonAddresses.Add(postalAddress);
context.PersonAddresses.Add(residentialAddress);
context.SaveChanges();
}
When I add an extra Person
to the code, I get the following error:
Code:
using (var context = new Models.TestEntities())
{
var person = context.People.Create();
var person2 = context.People.Create();
var postalAddress = context.PersonAddresses.Create();
postalAddress.AddressLine1 = "PostalAddressLine1";
var residentialAddress = context.PersonAddresses.Create();
residentialAddress.AddressLine1 = "ResidentialAddressLine1";
context.People.Add(person);
context.People.Add(person2);
context.PersonAddresses.Add(postalAddress);
context.PersonAddresses.Add(residentialAddress);
context.SaveChanges();
}
As the error specified Entityframework can now not determine to whom the PersonAddress
entities belong.
I can resolve this by modifying the code as below:
using (var context = new Models.TestEntities())
{
var person = context.People.Create();
var person2 = context.People.Create();
var postalAddress = context.PersonAddresses.Create();
postalAddress.AddressLine1 = "PostalAddressLine1";
var residentialAddress = context.PersonAddresses.Create();
residentialAddress.AddressLine1 = "ResidentialAddressLine1";
context.People.Add(person);
context.People.Add(person2);
person.PersonAddresses.Add(residentialAddress);
person.PersonAddresses.Add(postalAddress);
context.SaveChanges();
}
Please see the EDMX below:
Please see SQL script used to create the two tables:
CREATE TABLE Person
(
PersonId INT IDENTITY(1,1) NOT NULL CONSTRAINT [PK_Person] PRIMARY KEY,
FirstName VARCHAR(250)
)
CREATE TABLE PersonAddress
(
PersonAddressId INT IDENTITY(1,1) NOT NULL CONSTRAINT [PK_PersonAddress] PRIMARY KEY,
PersonId INT NOT NULL,
AddressLine1 VARCHAR(250)
)
ALTER TABLE PersonAddress
ADD CONSTRAINT [FK_PersonAddress_Person] FOREIGN KEY(PersonId)
REFERENCES [Person](PersonId)
Please see Id columns screenshots below:
Please see SQL Server Profiler trace below:
Person
:
PersonAddress
:
PersonAddress
:
Please see the inserted records in SQL:
Person
:
PersonAddress
:
Thanks.
Need function like the following. private string[] GetNaviProps(Type entityType)//eg typeof(Employee) { NorthwindEntities en = new NorthwindEntities(); //here I return all Properties only for example return entityType. GetProperties(). Select(p=>p.Name).
A navigation property is an optional property on an entity type that allows for navigation from one end of an association to the other end. Unlike other properties, navigation properties do not carry data.
Basically a scalar property is mapped to a column (int, string, ...) A navigation property is mapped to a relation. e.g Order. OrderDetails brings you to all ORderDetails of a specific order.
If you define your navigation property virtual , Entity Framework will at runtime create a new class (dynamic proxy) derived from your class and uses it instead of your original class. This new dynamically created class contains logic to load the navigation property when accessed for the first time.
var person = context.People.Create();
A Person
is created having PersonId
= 0.
var postalAddress = context.PersonAddresses.Create();
postalAddress.AddressLine1 = "PostalAddressLine1";
var residentialAddress = context.PersonAddresses.Create();
residentialAddress.AddressLine1 = "ResidentialAddressLine1";
Addresses are created, also having PersonId
= 0.
context.People.Add(person);
context.PersonAddresses.Add(postalAddress);
context.PersonAddresses.Add(residentialAddress);
EF has executed relationship fixup, i.e. it has matched the addresses' PersonId
s and the Person
's PersonId
(all 0) and established an association between them.
context.SaveChanges();
The database has assigned an identity value to Person.PersonId
. EF read it back from the database into the entities.
In the second snippet there are two Person
s having PersonId
= 0, so now EF doesn't know which person to associate the addresses with.
Clearly, this is unexpected behavior. The best thing is to associate the entities explicitly, if they are intended to be related, as in your third snippet.
Once you know about these automatic associations you may want to prevent them by assigning a different default value to Person.PersonId
other than 0, e.g. -1. Now EF won't match this id with any other foreign value having the default for integers, 0.
I have tested the scenario by creating a new project against a new database that I created with your script. I then generated a model against it leaving, all the defaults.
Here is the code I used to create the person and addresses.
var entities = new TestEntities();
var person = entities.People.Create();
person.FirstName = "xxx";
entities.People.Add(person);
var address1 = entities.PersonAddresses.Create();
address1.AddressLine1 = "Line1";
entities.PersonAddresses.Add(address1);
var address2 = entities.PersonAddresses.Create();
address2.AddressLine1 = "Line1";
entities.PersonAddresses.Add(address2);
entities.SaveChanges();
I can confirm that this code does indeed run and insert a person and two addresses linked to that person. It definitely looks like EF wires it up if there is only one entity that it could possibly link to. I'm not sure if that is supposed to be a feature, but it can make for some nasty surprises.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With