简体   繁体   中英

Determining if a model should have foreign keys / navigation properties

I'm building a fairly simple MVC project and still getting my head around where to use navigation properties and foreign keys with code first.

This is the main model class:

public class GroceryItem
{
    public int ID { get; set; }
    public string Name { get; set; }
    public GroceryCategory Category { get; set; }
    public QualityProfile Quality { get; set; }
    public GroceryStore BestStore { get; set; }
    public double BestPrice { get; set; }
    public double LastSeenPrice { get; set; }

    //Navigation Properties
    public virtual ICollection<GroceryItem> SimilarItems { get; set; }
}

and these are the relating classes:

public class GroceryStore
{
    public int ID { get; set; }
    public string Name { get; set; }
    public string Address { get; set; }
    public Uri Website { get; set; }
}

public class QualityProfile
{
    public int ID { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    /// <summary>
    /// Rank out of 1-10, 10 being the best
    /// </summary>
    public byte Ranking { get; set; }
}

public class GroceryCategory
{
    public int ID { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
}

Which brings me to my question, is the navigation property of SimilarItems I have in the GroceryItem class sufficient to represent a list of multiple grocery items or does this not work as it is referring to itself?

Additionally...do the Category , Quality and BestStore properties require ID properties to represent a foreign key inside of the GroceryItem class (eg CategoryID), or is the way I have this represented OK?

----EDIT----

--Refactored Code--

I've re-factored my model based on the suggestions below, which I think better accommodates the suggestions you've made (yes a 2nd time), realised my model was a little flawed and extracted out the price component into a separate purchases Model.

public class GroceryItem
{
    public int ID { get; set; }
    public string Name { get; set; }

    [ForeignKey("Category")]
    public int CategoryID { get; set; }

    [ForeignKey("Quality")]
    public int QualityID { get; set; }

    //Navigation Properties
    public virtual QualityProfile Quality { get; set; }
    public virtual GroceryCategory Category { get; set; }
}

However the last thing I'm uncertain about which is on topic to this post, is if I have a collection as a part of the model (one that does not reference itself like in the first example), can I just represent that with a navigation property or does an extra step need to be taken?

Ie. If I was to allow multiple different categories on a GroceryItem , instead of looking like this:

[ForeignKey("Category")]
public int CategoryID { get; set; }
public virtual GroceryCategory Category { get; set; }

it would look like this:

public virtual ICollection<GroceryCategory> Categories { get; set; }

The best answer to your question(s) is, "It depends". Navigation properties are one way of informing Entity Framework that there's a relationship between entities. By convention, if you have a navigation property such as:

public Category Category { get; set; }

Entity Framework will create a column on the table named in the form of [RelatedPropertyName]_[RelatedPK] . Given your classes, the property above would cause a column named Category_ID . There's nothing more you need to do make it work. The relationship will automatically be handled by EF.

However, doing it this way, you won't have access to this foreign key property. It's not exposed in the public API of your entity. Often, especially when selecting related items from a select list and similar such scenarios, this becomes problematic, as you must store the selected value some place else, usually a property on a view model, and then use this to query the related thing from the database before setting it on the entity it belongs to and finally saving the entity. Whereas, with an actual foreign key property, you can simply post directly back to this and Entity Framework will automatically wire up the related entity. As a result, I tend to always follow the following pattern with my navigation properties:

public int FooId { get; set; }
public virtual Foo Foo { get; set; }

In most scenarios, Entity Framework will automatically connect those two, such that FooId will hold the foreign key relationship for the Foo navigation property. However, occasionally, EF will trip up and try to create the implicit foreign key behind the scenes, still, but you can correct that behavior by explicitly telling EF that this is the foreign key:

[ForeignKey("Foo")]
public int FooId { get; set; }

Roughly the same applies with collection navigation properties. EF will see this as an indication that there's a one-to-many relationship in play and add the implicit foreign key on the opposite entity. Given your collection:

public virtual ICollection<GroceryItem> SimilarItems { get; set; }

The opposite entity is actually the same entity, which presents an interesting use case. Typically, EF would handle this by assuming there's a one-to-many relationship. You'd end up with a column named GroceryItem_ID on your dbo.GroceryItems table. Here, though, you would not only have no access to the foreign key directly, but you also have no public API for accessing the parent GroceryItem either. That may not be a problem, but it's something to be aware of. The only way you'd be able to manage the relationship is through the collection on the parent, not through a child item in that collection.

However, since this is self-referential and you have not specify a foreign key or instance navigation property, all EF will see is a collection on both sides of the relationship, so my guess is that you'll actually end up with an M2M with an intermediary table. I can't test that theory out myself at the moment, and I haven't tried this particular scenario myself previously.

To create a true one-to-many, you would need to create another navigation property similar to:

public virtual GroceryItem ParentGroceryItem { get; set; }

And, even, then, I don't think EF will get the point without a little Fluent configuration:

HasMany(m => m.SimilarItems).WithOptional(m => m.ParentGroceryItem);

You could also use WithRequired in other scenarios instead of WithOptional , which would obviously make the relationship a required one, but since this is self-referential, it's impossible to have it required, because there will have to be at least one root node with no parent.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM