I am very confused by the way Entity Framework is able to pick up on relationships 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.
Related
I'm studying EF6 and think I know quite a bit already, but couldn't find a good solution (yet) for this:
Suppose I have the following model classes:
class LivingRoom {
public int Id { get; set; }
public string Name { get; set; }
public PersonTypeId { get; set; }
public IList<Person> Persons { get; set; }
}
class Person {
public int Id { get; set; }
public int PersonTypeId { get; set; }
public string Name { get; set; }
}
With these model classes I'm able to save and load via DbContext without any problem. Thanks to the navigation property in the "parent" LivingRoom class, the Persons collection will be included in this process. I don't have to load/save them separately.
UPDATE: Forgot the logical PersonTypeId field which will be used for determining which Persons should be in the collection property.
So far so good.
But EF6 is creating a FK in the Persons table, pointing to the LivingRooms table, which seems logical.
But what if I'm going to use the Persons table for a lot more other parent entities, like eg. "Bus" and "Plane", and therefore don't want to have a dependency (= FK field in LivingRooms table) in the Persons table?
Can I achieve this (don't create the FK field) without breaking the "include child list" load/save process as described?
If yes, how? And if no, why not?
NB: Please understand that I want to learn the best techniques. So good advice, to not doing this, is also welcome.
First , it's better to handle FK in Person Table ourself to do that web have this :
class LivingRoom
{
public int Id { get; set; }
public string Name { get; set; }
public IList<Person> Persons { get; set; }
}
class Person
{
public int Id { get; set; }
public string Name { get; set; }
public LivingRoom LivingRoom { get; set; }
public int LivingRoomId { get; set; }
}
now If you have others Entities Like Bus and ... so we have
public class Bus
{
public int Id { get; set; }
public ICollection<Person> People { get; set; }
}
and Updated Person class is :
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public LivingRoom LivingRoom { get; set; }
public int LivingRoomId { get; set; }
public Bus Bus{ get; set; }
public int BusId { get; set; }
}
you can set FK in Person Table as Nullable to do this :
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public LivingRoom LivingRoom { get; set; }
public int? LivingRoomId { get; set; }
public Bus Bus{ get; set; }
public int? BusId { get; set; }
}
As you can see We set BusId and LivingRoomId as nullable or you can just set one of them that you want
Note : You need to add some mapper to tell EF which field id FK and something like this ,...
Very simple question but it looks like I'm trying to implement a simple one-to-many relationship between two models.
So far, what I have is this :
A product class :
public class Products
{
public long Id { get; set; }
public long Code { get; set; }
public DateTime DateCreated { get; set; }
public DateTime DateModified { get; set; }
public Boolean Reviewed { get; set; }
[ForeignKey("BundleId")]
public int BundleId { get; set; }
public virtual Bundles Bundle { get; set; }
}
And the Defects class looks like this:
public class Defects
{
public long Id { get; set; }
public String Description { get; set; }
public String Picture { get; set; }
public DateTime DateCreated { get; set; }
[ForeignKey("ProductId")]
public int ProductId { get; set; }
public Products Product { get; set; }
[ForeignKey("UserId")]
public int UserId { get; set; }
public virtual Users User { get; set; }
}
I thought that I did not need to add an ICollection of Defects to the Products class because it's a "simple" one-to-many relationship and this code would be enought to be able to get the ID of a Product in the Defects class (I don't need more).
But, of course I get an exception :
The property 'ProductId' cannot be configured as a navigation property. The property must be a valid entity type and the property should have a non-abstract getter and setter
How may I solve that issue ?
I might be doing someting wrong with my two foreign keys but since I declared the name of the foreign keys, I assumed it would have been enought.
Thanks for your attention.
This is what your relationship can be distilled to.
Please note that ForeignKey annotation is applied to navigation property with the name of the key property.
If you build one-to-many relationship - then ICollection is absolutely necessary. Otherwise where's the "many"
public class Products
{
public int Id { get; set; }
public virtual List<Defects> Bundle { get; set; }
}
public class Defects
{
public long Id { get; set; }
public int ProductId { get; set; }
[ForeignKey("ProductId")]
public Products Product { get; set; }
}
FK can also be applied to the key property. But in that case you have to put the name of the instance of related class there
public class Defects
{
public long Id { get; set; }
[ForeignKey("Product")]
public int ProductId { get; set; }
public Products Product { get; set; }
}
So, I'm writing an e-commerce application and I'm trying to create a many to many relationship between the Product and Size classes. Entities looks like that:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
public class Size
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ProductSize
{
public int Id { get; set; }
public int ProductId { get; set; }
public int SizeId { get; set; }
public string Amount { get; set; }
public virtual Size Size { get; set; }
public virtual Product Product { get; set; }
}
Normally I wouldn't explicitly create this ProductSize table, because EF would create it for me. However, I need the Amount column to sit there. Now, for getting info about products with sizes and amounts I have to create a very explicit query (2 joins). If I hadn't manually created the relationship class and just give the Product class a virtual ICollection of Size, getting the complete info would be just a simple matter. So if I had selected a Product instance, then all sizes would be loaded into it's virtual ICollection<Size>.
The question is - can I achieve the same level of simplicity with the entities structure given above? Instead of writing a 2 join query where I explicitly mention every column I want, and then pack it into some ViewModel I'd like to use the simpler syntax for getting a Product instance and its related data (so also the data that's sitting in the intermediate table).
Add an Inverse Navigation Property to the ProductSize from Product and Size like this:
public class Product {
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public virtual ICollection<ProductSize> ProductSizes { get; set; }
}
public class Size {
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<ProductSize> ProductSizes { get; set; }
}
public class ProductSize {
public int Id { get; set; }
public int ProductId { get; set; }
public int SizeId { get; set; }
public string Amount { get; set; }
public virtual Size Size { get; set; }
public virtual Product Product { get; set; }
}
Doing so, you should be able to do like in the following example:
from p in Context.Products
where p.ProductSizes.Where(ps => ps.Amount > 0 && ps.Size.Name.Equals("Big")).Any()
select p;
All examples in tutorials I have read through only ever show 2 tables so I am confused on what to do in the following scenario.
Lets say we have 3 classes:
public class Product
{
public int id { get; set; }
public string name { get; set; }
public string skuA { get; set; }
public string skuB { get; set; }
public virtual SupplierA supplierA { get; set; }
public virtual SupplierB supplierB { get; set; }
}
public class Supplier A
{
public int id { get; set; }
public string name { get; set; }
public string sku { get; set; }
public int price { get; set; }
}
public class Supplier B
{
public int id { get; set; }
public string name { get; set; }
public string sku { get; set; }
public int price { get; set; }
}
If we need to join the appropriate product sku field then using the data annotation I have seen in tutorial it ends up looking like this:
[ForeignKey("sku")]
public string skuA { get; set; }
[ForeignKey("sku")]
public string skuB { get; set; }
How does entity framework know which foreign "sku" field we are annotating for? Does each foreign key have to be named differently? I imagine this would become awkward if we had a hundred suppliers.
Are there some additional parameters or markup to handle this? Any advice appreciated as I am finding this confusing.
I misunderstood the purpose of the foreign key attribute. It defines key on current class if it does not follow naming convention.
I was getting stuck as the error message I was getting stated I needed to use annotation or the fluent api but what it really meant was I had to use the fluent api as the relationship I wanted could not be specified with annotations.
I'm trying to define a relationship between 2 tables with the ForeignKeyAttribute.
I came a cross a few sites that described an interesting method of doing this with the ForeignKeyAttribute.
Here are the two code samples:
The first one:
public class Customer
{
public int ID { get; set; }
public int OrderID { get; set; }
// Some other properties
[ForeignKey("OrderID")]
public virtual Order Order { get; set; }
}
public class Order
{
public int ID { get; set; }
public int CustomerID { get; set; }
// Some other properties
[ForeignKey("CustomerID")]
public virtual Customer Customer { get; set; }
}
The second one:
public class Customer
{
public int ID { get; set; }
[ForeignKey("Order")]
public int OrderID { get; set; }
// Some other properties
public virtual Order Order { get; set; }
}
public class Order
{
public int ID { get; set; }
[ForeignKey("Customer")]
public int CustomerID { get; set; }
// Some other properties
public virtual Customer Customer { get; set; }
}
In the first code sample, the ForeignKeyAttribute is placed on the public virtual Customer Customer { get; set; }.
And in the second code sample on the public int CustomerID { get; set; } (Order- and CustomerID).
My question is, how do I know which method to use in which situation?
I know this could be done too using Fluent API, but that's irrelevant at the moment for this question.
First, just want to say that you do not need to place the ForeignKey attribute on any property. Simply doing the following should be enough:
public class Customer
{
public int ID { get; set; }
//EF will create the relationship since the property is named class+id
//the following is not necessary, is just good practice
//if this is omitted EF will create a Order_Id on its own
public int OrderID { get; set; }
// Some other properties
public virtual Order Order { get; set; }
}
public class Order
{
public int ID { get; set; }
// no need to include the id property
// Some other properties
public virtual Customer Customer { get; set; }
}
Having said that, the answer is that the ForeignKey construct takes in a string parameter. If you place it on the foreign key property it should have the name of the navigation property, if you place it on the navigation property it should have the name of the foreign key. Where you place it is completely up to you, as long as you keep in mind what string value to use.