Referencing from the #Ogglas answer of this post,
I would like to ask if it is normal for EF to generate another table?
If the additional table should not be there, then what am I doing wrong here? Please enlighten me. TIA!
Sample code:
public class Aggregate
{
public Aggregate()
{
Episodes = new HashSet<Episode>();
}
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public Guid Id { get; set; }
...
public virtual ICollection<Episode> Episodes { get; set; }
}
public class Episode
{
public Episode()
{
Aggregates = new HashSet<Aggregate>();
}
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public Guid Id { get; set; }
...
public virtual ICollection<Aggregate> Aggregates { get; set; }
}
public class EpisodeAggregate
{
[Key]
[Column(Order = 1)]
[ForeignKey("Episode")]
public Guid EpisodeId { get; set; }
[Key]
[Column(Order = 2)]
[ForeignKey("Aggregate")]
public Guid AggregateId { get; set; }
public virtual Episode Episode { get; set; }
public virtual Aggregate Aggregate { get; set; }
public DateTime Timestamp { get; set; }
}
In my DbContext.cs:
public DbSet<EpisodeAggregate> EpisodeAggregates { get; set; }
You are right. In a relational database, a many-to-many relation is solved using a junction table.
For a standard many-to-many relationship, you don't need to mention this junction table; entity framework recognizes the many-to-many by your use of the virtual ICollection<...> on both sides of the many-to-many relation, and will automatically create the tables for you.
To test my database theories and entity framework, I quite often use a simple database with Schools, Students and Teachers. One-to-many for School-Student and School-Teacher and many-to-many for Teacher-Student. I always see that the Teacher-Student junction table is created automatically, without ever having to mention it.
However!
Your junction table is not standard. A standard junction table has only two columns: the EpisodeId and the AggregateId. It doesn't even have an extra primary key. The combination [EpisodeId, AggregateId] is already unique and can be used as a primary key.
You have in table EpisodeAggregate an extra column: TimeStamp. Apparently you want to know when an Episode and an Aggregate got related.
"Give me the TimeStamp when Episode[4] got related with Aggregate[7]"
This makes that this table is not a standard junction table. There is no many-to-many relation between Episodes and Aggregates. You made a one-to-many relation between Episode and its Relations with the Aggregates, and similarly a one-to-many relation between Aggregates and its Relations with the Episodes.
This makes that you have to change your many-to-many into one-to-many:
class Episode
{
public Guid Id { get; set; }
// every Episode has zero or more Relations with Aggregates (one-to-many)
public virtual ICollection<EpisodeAggregateRelation> EpisodeAggregateRelations { get; set; }
...
}
class Aggregate
{
public Guid Id { get; set; }
// every Episode has zero or more Relations with Episodes(one-to-many)
public virtual ICollection<EpisodeAggregateRelation> EpisodeAggregateRelations { get; set; }
...
}
class EpisodeAggregateRelation
{
// Every Relation is the Relation between one Episode and one Aggregate
// using foreign key:
public Guid EpisodeId { get; set; }
public Guid AggregateId { get; set; }
public virtual Episode Episode { get; set; }
public virtual Aggregate Aggregate { get; set; }
public DateTime Timestamp { get; set; }
}
If you are certain the there will always be at utmost one relation between an Episode and an Aggregate, you can use the combination [EpisodeId, AggregateId] as a primary key. If you think these two might have several relations, you need to add a separate primary key.
I often use my classes in different databases, hence I don't like attributes, I solve it in fluent API in OnModelCreating:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Episode>()
.HasKey(episode => episode.Id)
// define the one-to-many with EpisodeAggregateRelations:
.HasMany(episode => episode.EpisodeAggregateRelations)
.WithRequired(relation => relation.Episode)
.HasForeignKey(relation => relation.EpisodeId);
modelBuilder.Entity<Aggregate>()
.HasKey(aggregate => aggregate.Id)
// define the one-to-many with EpisodeAggregateRelations:
.HasMany(aggregate => aggregate .EpisodeAggregateRelations)
.WithRequired(relation => relation.Aggregate)
.HasForeignKey(relation => relation.aggregateId);
The above is not needed!
Because you followed the entity framework code first conventions, you can omit these two statements. Entity framework will recognize the primary key and the one-to-many relation. Only if you want to deviate from the conventions, like a non-standard table name, or if you want to define the column order:
modelBuilder.Entity<Episode>()
.ToTable("MySpecialTableName")
.Property(episode => episode.Date)
.HasColumnName("FirstBroadcastDate")
.HasColumnOrder(3)
.HasColumnType("datetime2");
But again: you followed the conventions, all those attributes like Key, ForeignKey, DatabaseGenerated are not needed. And the column order: who cares? Let your database management system decide about the most optimum column order.
My advice would be: try to experiment: leave out this fluent API and check whether your unit tests still pass. Checked in five minutes.
The EpisodeAggregateRelation has something non-standard: it has a composite primary key. Hence you need to define this. See Configuring a composite primary key
modelBuilder.Entity<EpisodeAggregateRelation>()
.HasKey(relation => new
{
relation.EpisodId,
relation.AggregateId
});
If you already defined the one-to-many in Episodes and Aggregates, or if that was not needed because of the conventions, you don't have to mention this relation here again.
If you want, you can put the one-to-many in the fluent API part of EpisodeAggregateRelation, instead of in the fluent API part of Episode / Aggregate:
// every EpisodeAggregateRelation has one Episode, using foreign key
modelBuilder.Entity<EpisodeAggregateRelation>()
.HasRequired(relation => relation.Episode(
.WithMany(episode => episode.EpisodeAggregateRelations)
.HasForeignKey(relation => relation.EpisodeId);
// similar for Aggregate
One final tip
Don't create a HashSet in the constructor. It is a waste of processing power if you fetch data: you create the HashSet, and it is immediately replaced by the ICollection<...> that entity framework creates.
If you don't believe me: just try it out, and see that your unit tests pass, with the possible exception of the unit test that checks for an existing ICollection<...>
Related
Consider the relationship between the following entities:
class Post
{
public int Id { get; set; }
public string Title { get; set; }
public string Text { get; set; }
public int AuthorId { get; set; }
public Author Author { get; set; }
}
class Author
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int PostId { get; set; }
public Post Post { get; set; }
}
Okay, nobody in their right minds would have a one-to-one relationship in this context; that's not the issue at play here...
You'll notice that for each navigation property (Author and Post) there are explicit Id columns defined (AuthorId and PostId) respectively.
Personally I don't like this approach (though I can see some potential benefit). I'd prefer EF to manage the Id columns internally for me, and just let me expose a relationship between Post and Author.
What I want to know is, is there any official recommendation for or against explicit Id columns?
NOTE: I do know of one place where explicit Id mapping is valuable, and that is when you're implementing a many-to-many join table. You can use the Ids to create a unique constraint which prevents record duplication for the same many-to-many relationship.
What I want to know is, is there any official recommendation for or against explicit Id columns?
Yes:
It is recommended to include properties in the model that map to
foreign keys in the database. With foreign key properties included,
you can create or change a relationship by modifying the foreign key
value on a dependent object. This kind of association is called a
foreign key association. Using foreign keys is even more essential
when working with N-Tier applications.
Entity Framework Relationships and Navigation Properties
This is not an "official recommendation". But here is how I see it.
You want the navigational properties to be virtual for lazy loading, and you will need the two Id columns for the mapping into the database.
But your code never uses those foreign keys. Here is an example that links a Post with an Author.
var p = new Post {Title="Foo"};
p.Author = _db.Authors.First(a => a.Id == 5);
_db.Posts.Add(p);
_db.SaveChanges();
You also need to map those fields up into your domain layer to keep track of relations.
I'm learning EF Core and put wrong code in the Fluent API, which resolved to very strange One-to-One Relationship in the created database. Let me give you some code and more specific information and I really hope someone can explain how this happen.
I have coded 3 Models in C# and 1 Mapping table. The problem occurred between 2 of the models.
public class Album
{
[Key]
public int AlbumId { get; set; }
public string BackgroundColor { get; set; }
public Boolean IsPublic { get; set; }
public int PhotographerId { get; set; }
public Photographer Photographer { get; set; }
public IList<PictureAlbum> AlbumPictures { get; set; } = new List<PictureAlbum>();
}
public class Photographer
{
[Key]
public int PhotographerId { get; set; }
[Required]
public string Username { get; set; }
[Required]
[MinLength(6)]
public string Password { get; set; }
[Required]
public string Email { get; set; }
[Required]
public DateTime RegisteredDate { get; set; }
[Required]
public DateTime BirthDate { get; set; }
public IList<Album> Albums { get; set; } = new List<Album>();
}
So everything looks right. 'Album' have Photographer and Foreign Key. Photographer have Collection of 'Albums'.
Here is part of the table relationship in the Fluent API (included only the relationship for the "strange" relationship between Album and Photographer):
builder.Entity<Album>()
.HasOne(p => p.Photographer)
.WithMany(a => a.Albums)
.HasForeignKey(p => p.AlbumId);
As you can see, instead of putting "PhotographerId" for the ForeignKey, I put "AlbumId" which should leads to "Self-Referencing" Table, right?
But this looks not true, because when I review the Diagram of the Database I see the following:
Diagram (Relation between Album and Photographer)
More of that, if you look in the Key's they have, they looks like they are coded in C#:
Tables Keys
Now I do not understand how this is possible. I created the Database using Migrations.
I did not put the right Foreign Key in FluentApi, but the Diagram shows One-to-One Relationship?
In the Keys of the table, we can see that they don't have Relationship.
More than that I have IList Albums in "Photographer" Model, which by my understanding should lead to may be something different, but not One-to-One.
I know I made a mistake with the FluentApi, but I want to learn from my mistake and to understand how this result happened.
This is my first post here and I hope I can get some support/help.
Thank you.
It's a weird situation. You effectively configured a one-to-one association between Photographer (principal) and Album (dependent). How this happened becomes clear by using the correct range variables (p and a) in the mapping:
builder.Entity<Album>()
.HasOne(a => a.Photographer)
.WithMany(p => p.Albums)
.HasForeignKey(a => a.AlbumId); // i.e. this is Album.AlbumId
Album.AlbumId now is Albums primary key and its foreign key to Photographer. The primary key is not an identity field, because it "borrows" its value from the owning Photographer. This is a common way of modeling 1:1 associations in a relational database. But for EF, the association is 1-n and it's a bit surprising that it doesn't give a warning about this anomaly.
The funny thing is that is still works. You can establish the 1:1 association between a Photographer and an Album by adding the album to Photographer.Albums, and EF saves everything alright. However, if you try to add a second Album, EF will run into a duplicate primary key exception.
I need to implement Entity-Attribute-Value functionality on multiple data tables using Entity Framework. Let's say I have an attribute value EF class that looks like this:
public class EntityAttributeValue
{
// Not important to my question.
public virtual Entity ParentEntity { get; set; }
public virtual EntityAttribute ParentEntityAttribute { get; set; }
// Field in question.
public Guid ParentSurrogateKey { get; set; }
public string Value { get; set; }
...
}
Then I have multiple entities that have supplementary EAV values associated with them:
public class Entity1
{
// Key. EntityAttributeBalue.ParentSurrogateKey maps to this.
[Key]
public Guid SurrogateKey { get; set; }
// Standard properties.
public string Property1 { get; set; }
public string Property2 { get; set; }
// Collection of EAV values associated with this entity/table.
[ForeignKey("ParentSurrogateKey")]
public virtual IList<EntityAttributeValue> EntityAttributeValues { get; set; }
}
public class Entity2
{
// Key. EntityAttributeBalue.ParentSurrogateKey maps to this.
[Key]
public Guid SurrogateKey { get; set; }
// Standard properties.
public string OtherProperty1 { get; set; }
public string OtherProperty2 { get; set; }
// Collection of EAV values associated with this entity/table.
[ForeignKey("ParentSurrogateKey")]
public virtual IList<EntityAttributeValue> EntityAttributeValues { get; set; }
}
My problem is that both Entity1 and Entity2 have EntityAttributeValue objects associated with them. Code first migrations tries to create a foreign key from EntityAttributeValue back to Entity1 and another one back to Entity2 on ParentSurrogateKey. The surrogate key for any single given EntityAttributeValue is only associated with either one Entity1 or one Entity2 (or, expanding out, one EntityN...), not both/all.
I have a many to many relationship here, but one side not only maps to multiple rows, but multiple entities/tables over a shared GUID column.
How should I be approaching this? Should I just remove the EntityAttributeValue foreign keys back to Entity1 and Entity2 from the automatic migration (which would be a long term pain)? Should I be manually retrieving the list of EntityAttributeValues for a given EAV entity instead of relying on EF to do it for me?
Well, the answer turned out to be obvious and simple. I needed to define a many-to-many relationship with FluentAPI. In OnModelCreating, I just added:
modelBuilder.Entity<Entity1>()
.HasMany(m => m.EntityAttributeValues)
.WithMany();
modelBuilder.Entity<Entity2>()
.HasMany(m => m.EntityAttributeValues)
.WithMany();
I thought I had tried this, but I guess I hadn't. Because the many-to-many relationship creates an intermediate table for each entity and the foreign keys are on that intermediate table (and there is only a row in the intermediate table when a given EntityAttributeValue applies to a given Entity), no foreign key issues.
I use Fluent Nhibernate and have 2 entities:
public class Document
{
public virtual int Id { get; protected set; }
public virtual string Name { get; set; }
public virtual User Author { get; set; }
public virtual DateTime Date { get; set; }
}
and
public class User
{
public virtual int Id { get; protected set; }
public virtual string Name { get; set; }
public virtual IList<Document> Docs { get; set; }
public User()
{
Docs = new List<Document>();
}
}
and I don't understand why fnh creates that wrong schema on this simpliest entities. That's what fnh creates in my db:
I can't understand why fnh creates 2 references (Author_id and User_id) to User table instead of a single reference (only Author_id).
I found an workaround here Fluent Nhibernate AutoMapping -- 2 foreign keys to same table? and here Fluent NHibernate Automappings generating 2 foreign keys for 1 relationship but I don't want to use it because I don't understand why I should set up every thing by my hands if I use automappings that should do all work for me (at-least that simpliest and obvious mappings as in my entities).
You have a Document entity referring a User entity (0-1 relationship) through a property named Author, but in the same time, in User entity you refer Document in a one-to-many relationship.
Fluent NHibernate automapping works with conventions, and the specific HasManyConvention maps the relationship creating a foreign key name based on the NAME (and not the type) of the referring entity (in this case USER)
So NHibernate, when creating the relationship between User and Document, creates a User_Id key in the Document table. This is a correct convention behavior.
I'm just a beginner in EF code first model. Given two POCO classes mapped to current legacy MS SQL database. They are associated with a composite foreign key setting up one to many relation. Since it's actually one-to-one relation I'd like to have corresponding navigation properties in my POCO objects and do mapping in fluent API. Here is my example:
public partial class Answer
{
//primary key
public int id { get; set; }
//foreign keys
public int question { get; set; }
public int assignedForm { get; set; }
//simple fields
public short state { get; set; }
public int author { get; set; }
//navigation property
public virtual AssignedQuestion AssignedQuestion { get; set; }
}
public partial class AssignedQuestion
{
// primary keys
public int id { get; set; }
public int assignedForm { get; set; }
//simple field
public string content { get; set; }
//navigation property
//public virtual ICollection<Answer> Answers { get; set; }
public virtual Answer Answer { get; set; }
}
If I wanted to do one-to-many relation I would simply uncomment "Answers" collection and have Fluent API mapping:
modelBuilder.Entity<AssignedQuestion>()
.HasKey(q => new { q.id, q.assignedForm });
modelBuilder.Entity<Answer>()
.HasRequired(a => a.AssignedQuestion)
.WithMany(aq=>aq.Answers)
.HasForeignKey(a => new { a.question,a.assignedForm});
My goal is to go with one-to-one relation and use "Answer" property in AssignedQuestion with such Fluent API as:
modelBuilder.Entity<AssignedQuestion>()
.HasKey(q => new { q.id, q.assignedForm });
modelBuilder.Entity<Answer>()
.HasRequired(a => a.AssignedQuestion)
.WithOptional(aq => aq.Answer);
//.HasForeignKey(a => new { a.question, a.assignedForm });
The problem is I can't specify exactly foreign key fields (as in previous example) and uncomment HasForeignKey call. In this case EF tries to join tables using conventional field names "AssignedQuestion_ID" and "AssignedQuestion_AssignedForm" instead of "question" and "assignedForm" in Answer table. Is there a walkaround in Fluent API other than changing field names?
It is not one-to-one relationship so your first mapping is correct. The reason why it is one-to-many is that EF understands one-to-one only when build on PKs on both sides. If AssignedQuestion has PK id and assignedForm your Answer will need to have FK and PK on its id and assignedForm otherwise EF doesn't see it as one-to-one relation. Even if you mark your question and assignedForm with unique constaint in database (to make it one-to-one in the database) EF will still not be able to handle it as one-to-one because it doesn't support unique constraints yet (except PK).