One to many relationship: why does ID field go in other entity? - entity-framework

In Entity Framework when I want to specify that an entity has many of another type of entity it seems to do things backwards to me.
For instance let's say I have a keyword entity that is used in several places throughout my app. All keywords are unique but on my other entities I want to have multiple keywords attached them so to me this would make sense:
class Page
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Keyword> Keywords { get; set; }
}
class Search
{
public int ID { get; set; }
public DateTime Date { get; set; }
public virtual ICollection<Keyword> Keywords { get; set; }
}
class Keyword
{
public int ID { get; set; }
public string Name { get; set; }
}
However when I do this the foreign key is added to the Keyword table whereas I want it to be on the actual entity so I can look at it in database and see small list of keywords instead of looking at keyword and seeing a ridiculous large number of page results.
So instead to get Entity Framework to put the Keyword_IDs on Page and Search entities I am doing this:
class Page
{
public int ID { get; set; }
public string Name { get; set; }
}
class Search
{
public int ID { get; set; }
public DateTime Date { get; set; }
}
class Keyword
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Page> Pages { get; set; }
public virtual ICollection<Search> Searches { get; set; }
}
This feels backwards as I am specifying the relationship on the entity that doesn't get the foreign ID field in the database table.
I feel like I am doing something wrong as I should be able to see the relationship by looking at my search & page class.
I am sorry for the basic question but for some reason I have read documentation and I am not fully understanding it.

In a one-to-many association it's always the many side that refers to the one side. How else would you implement it? If a Page would have a KeywordId as FK, it could only have one keyword, ever.
Also, even when a Keyword would belong to a myriad of pages, that doesn't mean you always have to access all of these pages through one keyword. You'd only do that if you'd do a search for pages in which specific keywords are used.
But now back to your model. You can't have one-to-many associations here. It would mean that any keyword can only belong to one Page or one Search. And if you invert the relationship, as you proposed, a Page or Search can only ever have one keyword (the one that Keyword_ID refers to).
In reality, you're dealing with many-to-many associations and the good news is, it leaves your Keyword intact.
Modelling it as many-to-many doesn't change the way your model looks (the first version), but the mapping is different:
modelBuilder.Entity<Page>().HasMany(p => p.Keywords)
.WithMany()
.Map(m =>
{
m.ToTable("PageKeyword");
m.MapLeftKey("PageID");
m.MapRightKey("KeywordID");
});
modelBuilder.Entity<Search>().HasMany(s => s.Keywords)
.WithMany()
.Map(m =>
{
m.ToTable("SearchKeyword");
m.MapLeftKey("SearchID");
m.MapRightKey("KeywordID");
});
This will generate two junction tables in your database, PageKeyword and SearchKeyword that record the many-to-many associations.

Related

Entity framework one foreign key toward two tables - code first

All,
Is it possible to use the same FK for two tables.
Probably it is not a good practice, but I have a two different classes that can be both booked:
public class Course {
public Course() {
BookingRefs = new HashSet<BookingRef>();
}
public long Id { get; set; }
public string Title { get; set; }
// other props ...
[InverseProperty(nameof(BookingRef.Course))]
public virtual ICollection<BookingRef> BookingRefs { get; set; }
}
public class GiftCard {
public GiftCard() {
BookingRefs = new HashSet<BookingRef>();
}
public long Id { get; set; }
public string Prop1 { get; set; }
public int Prop2 { get; set; }
// other props ...
[InverseProperty(nameof(BookingRef.Course))]
public virtual ICollection<BookingRef> BookingRefs { get; set; }
}
// this is the bookin reference for a Course or an GiftCard
public class BookingRef {
public BookingRef() {
}
public long Id { get; set; }
// other props ...
/// <summary>The item (usually the course but theoretically anything with a long id)</summary>
public long? ItemId { get; set; }
// maybe a generic Object?
[ForeignKey(nameof(ItemId))]
public Object GiftCard { get; set; }
// maybe 2 items possibly null?
[ForeignKey(nameof(ItemId))]
public Course Course { get; set; }
// maybe 2 items possibly null?
[ForeignKey(nameof(ItemId))]
public GiftCard GiftCard { get; set; }
}
Is it possible to use the same FK for two tables
No. The relational model doesn't allow that. You can introduce a superclass of all your bookable things and have a FK to that, but you shouldn't do that just get a single collection rather than multiple.
Think of it from the relational data perspective. How would the database know what table an "Item ID" pointed at? How would it index it?
This would be a case for using a null-able FK to each related table on the booking. These FKs do not need to reside in the entity, just the navigation properties. You can leverage .Map(x => x.MapKey) in EF6 or .HasForeignKey("") in EF Core to leverage a shadow property.
This does not enforce if you want a booking to only be associated to a course or a gift card but not both. That would need to be catered for at the application level, and I would recommend using a scheduled maintenance task to evaluate the data for violations to that rule. (Look for bookings holding both a course ID and a gift card ID for example)
You can alternatively keep the joins "loose" and evaluated by the application based on a discriminator similar to an inheritance model. (ItemId + ItemType) However you have to resolve the relationship load separately in your application based on the ItemType and lose out on any FK, indexing, and data integrity checks in the database. This could be a significant performance & maintenance cost to save adding a couple FKs.

One-to-one mapping in multiple tables

I'm trying to solve one puzzle, but with no luck so far.
I have an article (or blog post) and comment entities, they both have content. In order to support lazy loading for content (there is no need to load the content when I need to display a list of articles or comments) I decided to move content to separate table and organize one-to-one mapping. Here is an example of what I think:
public class Content {
[Key]
public int ID { get; set; }
public string RawContent { get; set; }
// a bunch of scalar properties, like content type and so on
}
public class BlogArticle {
[Key]
public int ID { get; set; }
public int ContentID { get; set; }
[ForeignKey(nameof(ContentID)]
public virtual Content Text { get; set; }
// other properties related to BlogArticle
}
public class Comment {
[Key]
public int ID { get; set; }
public int ContentID { get; set; }
[ForeignKey(nameof(ContentID)]
public virtual Content Text { get; set; }
// other properties related to comment
}
<...>
From first look it seems ok: I can create blog articles, comments and attach content (at first I insert content, obviously). Update works as well. However, deletion doesn't work: when I delete blog article or comment, content is not deleted (but I want to delete it when blog article or comment are deleted, not opposite).
From what I understand my biggest issue because of relationship direction: in my case, Content entity is principal end and BlogArticle and Comment are dependent ends. In order to solve the puzzle, I need to change principal/dependent relationship. Again, from what I understand in order to change relationship direction I need to have a foreign key in Content entity and use fluent API to describe who is parent (principal) and who is child (dependent) in one-to-one relationship. Since many tables (there might be other entities with content property) are pointing to Content table, it doesn't seem very easy. Am I correct in my understanding?
One possible solution I could imagine is to create multiple foreign keys in Content table and point to each related table:
public class Content {
[Key]
public int ID { get; set; }
public string RawContent { get; set; }
// foreign keys
public int BlogArticleID { get; set; }
public int CommentID { get; set; }
public int WebWidgetID { get; set; }
// other foreign keys if necessary
}
probably, foreign keys must be nullable (because only single foreign key is used at once). Then use Entity Framework fluent API to describe relationship directions and organize cascade delete. For me it looks ugly, but I have no other ideas.
My question: is my proposed solution good/reliable? Are there other options I can look at?
Thanks in advance!
All your thoughts are correct. And your proposed solution is the only way with traditional relational design. The drawback of course is the need of multiple mutually exclusive nullable FKs.
The other options I see are as follows:
(1) Using EF inheritance for the entities holding Content. e.g.
public abstract class EntityWithContent
{
[Key]
public int ID { get; set; }
public virtual Content Text { get; set; }
}
public class BlogArticle : EntityWithContent
{
// other specific properties
}
public class Comment : EntityWithContent
{
// other specific properties
}
and configured one-to-one relationship between Content (dependent) and EntityWithContent (principal) using either shared PK association or FK association.
But since EF Core currently supports only TPH strategy (i.e. all the derived entities share one and the same table with union of all fields), I won't recommend it.
(2) Making Content owned type.
This is closer to the intent, but unfortunately EF Core currently always loads the owned entity data along with the owner data (even if they are configured to be provided by different database tables), which is against your original goal, so I won't suggest that either.
(3) Using table splitting feature.
If the main goal is simple to support controlled (lazy/eager/explicit) loading and the Content is always required, then this might be the best solution so far.
It would require a bit more configuration, but at the end it will give you the original table design (single table per entity) with the desired loading behavior:
Model:
public abstract class Content
{
[Key]
public int ID { get; set; }
public string RawContent { get; set; }
// a bunch of scalar properties, like content type and so on
}
public class BlogArticle
{
[Key]
public int ID { get; set; }
public virtual BlogArticleContent Text { get; set; }
// other properties related to BlogArticle
}
public class BlogArticleContent : Content
{
}
public class Comment
{
[Key]
public int ID { get; set; }
public virtual CommentContent Text { get; set; }
// other properties related to comment
}
public class CommentContent : Content
{
}
Note that here Content class is not part of EF inheritance hierarchy, but simple base class with the common properties (abstract modifier is not strongly necessary). The actual derived classes might or might not define their own properties.
Configuration:
modelBuilder.Entity<BlogArticle>().ToTable("BlogArticles");
modelBuilder.Entity<BlogArticle>()
.HasOne(e => e.Text)
.WithOne()
.HasForeignKey<BlogArticleContent>(e => e.ID);
modelBuilder.Entity<BlogArticleContent>().ToTable("BlogArticles");
modelBuilder.Entity<Comment>().ToTable("Comments");
modelBuilder.Entity<Comment>()
.HasOne(e => e.Text)
.WithOne()
.HasForeignKey<CommentContent>(e => e.ID);
modelBuilder.Entity<CommentContent>().ToTable("Comments");

EF creating an unwanted field in database

I've hit a snag while building a .net mvc site. I have 2 related objects and am struggling with properly linking them. Specifically:
public class Address
{
public int AddressId { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public string PostCode { get; set; }
[ForeignKey("AddressCategory")] // <-- EF adds field to below object's table
public int AddressCategoryId { get; set; }
public virtual AddressCategory AddressCategory { get; set; }
}
public class AddressCategory
{
public int AddressCategoryId { get; set; }
public string Description { get; set; }
}
Adding the [ForeignKey] data annotation to the Address object results in EF adding an Address_AddressId column to the AddressCategory table, which I don't want (or need) to happen.
I've tried to omit the ForeignKey attribute, but then I run into other errors because .net can't link the tables (e.g. Unknown column 'Extent1.AddressId' in 'field list'). Additionally, I wouldn't be able to use:
var addresses = db.Addresses.Include(l => l.AddressCategory);
Is there any way to link the 2 tables without EF adding an additional column to the AddressCategory table?
Thank you to #cloudikka for responding. After much trial-and-error I seem to have gotten it to work simply by omitting any ForeignKey reference from either object. I let EF rebuild the database and perform all scaffolding (CRUD forms) and they have been created perfectly.
My take-away is that foreign key attributes should be used for parent-child relationships, but not for look-up tables. I clearly have much to learn about asp.net mvc!

Cascade delete in one to one relationship

I want to have cascade delete in 1:1 relationship, where i reference multiple entities to one. Problem is throws me an error on database update
Introducing FOREIGN KEY constraint 'FK_dbo.CategoryArticles_dbo.Articles_Article_Id' on table 'CategoryArticles' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
RoutingSeo entity is for storing seo friendly url in database for later usage. My problem is clearly M:N relationship between Article and Category. Is there something how can I deal with this problem?
Here are my entities of my model
public class Article : IEntity<int>
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Category> Categories { get; set; }
[Required]
public virtual RoutingSeo RoutingSeo { get; set; }
public int RoutingSeoId { get; set; }
}
public class Category : IEntity<int>
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Article> Articles { get; set; }
[Required]
public virtual RoutingSeo RoutingSeo { get; set; }
public int RoutingSeoId { get; set; }
}
public class SpecificProduct : IEntity<int>
{
public int Id { get; set; }
public string Name { get; set; }
[Required]
public RoutingSeo RoutingSeo { get; set; }
public int RoutingSeoId { get; set; }
}
public class RoutingSeo : IEntity<int>
{
public int Id { get; set; }
public string SeoRoute { get; set; }
public Article Article { get; set; }
public SpecificProduct SpecificProduct { get; set; }
public Category Category { get; set; }
}
Here is my fluent api code where i specify cascade delete
modelBuilder.Entity<Article>()
.HasRequired(x => x.RoutingSeo)
.WithOptional(x=>x.Article)
.WillCascadeOnDelete();
modelBuilder.Entity<Category>()
.HasRequired(x => x.RoutingSeo)
.WithOptional(x=>x.Category)
.WillCascadeOnDelete();
modelBuilder.Entity<SpecificProduct>()
.HasRequired(x => x.RoutingSeo)
.WithOptional(x=>x.SpecificProduct)
.WillCascadeOnDelete();
You are right, it is your many-to-many relation ship between Article and Category: one Article has zero or more Categories and every Category may be used by zero or more Articles.
If you delete an Article, its Categories can't be deleted automatically, because the Category might be used by other Articles, and even if it isn't used right now, entity framework doesn't know whether you want to use it tomorrow. After all, you specified that every Category might be used by zero or more Articles.
Similarly, if you remove a Category, entity framework can't automatically remove the Articles belonging to this category.
This differs from a one-to-many relationship. For example, if you have a one-to-many relationship of a Book and its Pages, then every Book has zero or more Pages and every Page belongs to exactly one Book.
If you remove the Book, then entity framework knows that it should automatically remove all Pages of the Book, which are all Pages with a foreign key BookId. If Entity Framework would only remove the Book, then we would have a bunch of Pages with foreign key value pointing to a non-existing Book. So in one-to-many relations, entity framework can cascade on delete.
Alas, in many-to-many this is not possible.
On the bright side, you have the advantage that you can delete the last Article of a Category, and keep the Category intact. Tomorrow you can add a new Article that uses this Category.
So if you want to remove an article, you manually have to remove it from the 'Categories` it uses:
many-to-many following the standard naming conventions:
class Article
{
public int Id {get; set;}
// an Article belongs to zero or more Categories:
public virtual ICollection<Category> Categories {get; set;}
...
}
class Category
{
public int Id {get; set;}
// a Category is used by zero or more Articles:
public virtual ICollection<Article> Articles{get; set;}
...
}
Don't forget to declare your ICollections virtual!
class MyDbContext : DbContext
{
public class DbSet<Article> Articles {get; set;}
public class DbSet<Category> Categories {get; set;}
}
You don't have to mention the junction-table, entity framework will make it automatically for you, but you won't have to use it for joins if you want Articles with their Categories, or Categories with their Articles, just use the ICollections
Note: As Categories is not the expected plural of Category, you'll have to tell entity framework the proper table name. Out of scope of this question.
Delete an Article, but keep all Categories it belongs to alive:
using (var dbContext = new MyDbContext(...))
{
Article articleToRemove = ...
dbContext.Articles.Remove(articleToRemove);
dbContext.SaveChanges();
}
Entity framework will automatically perform the proper joins, and remove the articleToRemove from every Category. However, the Categories won't be removed.
In fact, internally the Categories table doesn't change at all. All records with Article.Id will be removed from the junction table.

Can I promote a many-to-many relationship to an entity without changing all references to the existing navigation property?

I have a many-to-many relationship between Questions and Answers. But now I want to add a cost to a valid question and answer pair. I was trying to think of a way of doing it that would avoid having to change all references to the original property. Is it possible?
public class Question
{
public int ID { get; set:}
public string Text { get; set; }
//The original many-to-many
//public virtual ICollection<Answer> Answers { get; set; }
//but now I need a QuestionAnswerPair as an entity
//problem is that Adding or Removing does not affect the QuestionAnswerPairs collection
[NotMapped]
public ICollection<Answer> Answers
{
get
{
return QuestionAnswerPairs.Select(x => x.Answer).ToList();
}
}
public virtual ICollection<QuestionAnswerPair> QuestionAnswerPairs { get; set; }
}
public class Answer
{
public int ID {get; set;}
public string Text { get; set; }
//The original many-to-many
//public virtual ICollection<Question> Questions { get; set; }
}
//UnitCosts should only be added to valid Question-Answer pairs
//so I want to have a cost linked to the many-to-many relationship
public class QuestionAnswerPair
{
public int ID {get; set;}
public int AnswerID { get; set; }
public virtual Answer Answer { get; set; }
public int QuestionID { get; set; }
public virtual Question Question { get; set; }
public decimal? Amount { get; set; }
}
You will soon find out that this is not possible when you want to use the navigation property in LINQ-to-entities queries.
If you'd do something like
context.Questions.SelectMany(q => q.Answers)
EF will throw an exception that Answers is not supported (Only initializers, entity members, and entity navigation properties are supported).
If you want to work around this by adding AsEnumerable:
context.Questions.AsEnumerable().SelectMany(q => q.Answers)
you will find out that for each question queries are executed to load their QuestionAnswerPairs collections and the Answers. (If lazy loading is enabled). And if you want to prevent that, you've got to fetch questions with Incude statements.
You really can't do anything better but include the QuestionAnswerPairs in your LINQ queries.
That's why it's always a big decision to implement many-to-many associations with transparent junction tables (i.e. no junction classes). Sooner or later users will want to add descriptive data to junction records. Pure junction tables are very rare in real-life applications.