Entity Framework Code First associations/FK issues and assumptions/defaults
I am very confused by the way Entity Framework is able to pick up on rela开发者_如何学Gotionships between entities. I have a few questions on this.
In a simple test application, I have a table of people, and a table of notes, and a table of picture assets.
- There are many pictures, each is owned by a person (a person can own more than one).
- There are many notes, each is owned by a person (a person can own more than one).
- and finally a person has a logo which is a picture.
.
public class Person
{
public int ID { get; set; }
public string name { get; set; }
public Picture logo { get; set; }
}
public class Note
{
public int ID { get; set; }
public string Text { get; set; }
public Person Owner { get; set; }
}
public class Picture
{
public int ID { get; set; }
public string Path { get; set; }
public Person Owner { get; set; }
}
When I try to run, I get the error "Unable to determine the principal end of an association between the types ....".
(If I drop the Person.logo field, compile/run, then manually add it in to SQL, along with the FK relationship, it works 100% as expected... I just can't seem to work out how to set this from EF itself).
Can you help with the error? I have read quite a few answers here, but, I just can't seem to adapt it to fix my error.
However, now I have a one to many and a many to one (I don't think this is classed as many to many?), I just don't understand how to create people objects, where a FK is non nullable and the FK doesn't exist yet.
A solution I found, which I really don't like is to make the person.picture column nullable, then create a person, followed by creating a picture, then assign a picture to the person object... but, ideally I don't want it nullable. There should always be a picture.
Old answer assuming your 1:1 relation:
Scroll down for an answer on your clarification.
Here are two ways to achieve this, one way is to remove the cross reference navigation by applying 1:N.
public class Person
{
public int ID { get; set; }
public string Name { get; set; }
public virtual Picture Logo { get; set; }
}
public class Note
{
public int ID { get; set; }
public string Text { get; set; }
}
public class Picture
{
public int ID { get; set; }
public string Path { get; set; }
}
This is a really cheap solution, you probably don't want more notes or pictures than persons...
So, use Data Annotations to instruct what the foreign key is, this will keep navigation and 1:1.
public class Person
{
public int ID { get; set; }
public string Name { get; set; }
public virtual Picture Logo { get; set; }
}
public class Note
{
[Key, ForeignKey("Person")]
public int OwnerID { get; set; }
public string Text { get; set; }
public virtual Person Owner { get; set; }
}
public class Picture
{
[Key, ForeignKey("Person")]
public virtual int OwnerId { get; set; }
public string Path { get; set; }
public virtual Person Owner { get; set; }
}
As you can see, because of the 1:1 relationship the picture and note will use the same ID. If your key is not named ID, you need to add a KeyAttribute, in this case we also add the ForeignKeyAttribute.
Please note that you should use virtual
so that things load only when you request them, you most likely don't want the database to query the Picture information if you only want the name of the Person.
Association properties that are marked as
virtual
will by default be lazy-loaded. What this means is that if you retrieve a Product entity, its Category information will not be retrieved from the database until you access its Category property (or unless you explicitly indicate that the Category data should be retrieved when you write your LINQ query to retrieve the Product object).— Scott Gu - Using EF Code First with an existing database
New answer regarding your clarification:
Bulding further on the Foreign Keys by going back to a 1:N structure with ICollection<T>
; once you obtain your person like var boss = Person.Find(BossID)
you can then access boss.Pictures
which will have the various pictures. You can also assign boss.Logo
to be one of those pictures.
public class Person
{
public int ID { get; set; }
public string Name { get; set; }
public virtual Picture Logo { get; set; }
public ICollection<Picture> Pictures { get; set; }
public ICollection<Note> Notes { get; set; }
}
public class Note
{
public int ID { get; set; }
[ForeignKey("Person")]
public int OwnerID { get; set; }
public string Text { get; set; }
public virtual Person Owner { get; set; }
}
public class Picture
{
public int ID { get; set; }
[ForeignKey("Person")]
public virtual int OwnerId { get; set; }
public string Path { get; set; }
public virtual Person Owner { get; set; }
}
You might be interested in hinting your data members using DataAnnotations as well as taking a look to the current Conventions for Code First.
public class Person
{
public int ID { get; set; }
public string Name { get; set; }
public virtual Picture Logo { get; set; }
public ICollection<Picture> Pictures { get; set; }
public ICollection<Note> Notes { get; set; }
}
public class Note
{
public int ID { get; set; }
public int OwnerID { get; set; }
public string Text { get; set; }
[ForeignKey("OwnerId")]
public virtual Person Owner { get; set; }
}
public class Picture
{
public int ID { get; set; }
public virtual int OwnerId { get; set; }
public string Path { get; set; }
[ForeignKey("OwnerId")]
public virtual Person Owner { get; set; }
}
The above code is right, after trying the wrong code several times, I got the right code.
Hope it is usefull for you.
精彩评论