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.
Related
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<...>
I am using EF6 but...
I can not change the database.
So, if I'm not wrong, I need to create a model that suits the database.
I have to models in relationship one to many:
[Table("ReceCli")]
public class ReceCli
{
[Key]
public int Indice { get; set; }
[Required, StringLength(12)]
[Display(Name = "NÂș Documento")]
public string NDOC { get; set; }
[Display(Name = "Banco do boleto")]
[Column("CodBancoBoleto")]
public int CodBancoBoleto { get; set; }
public Banco Banco { get; set; }
}
and
[Table("Bancos")]
public class Banco
{
[Key]
public int CodBanco { get; set; }
[Column("Banco")]
[Required, StringLength(50)]
[Display(Name = "Banco")]
public string Nome { get; set; }
}
In the database this relations are expressing like:
ALTER TABLE [dbo].[ReceCli] WITH NOCHECK ADD CONSTRAINT [ReceCli_CodBancoBoleto] FOREIGN KEY([CodBancoBoleto])
REFERENCES [dbo].[Bancos] ([CodBanco])
GO
ALTER TABLE [dbo].[ReceCli] CHECK CONSTRAINT [ReceCli_CodBancoBoleto]
When executing return an error:
Invalid column name 'Banco_CodBanco'.
I can not change the database.
How can I change the model to EF use ReceCli_CodBancoBoleto name of column instead of Banco_CodBanco ?
You can do model an existing db by hand but you can also tell EF to generate the model from an existing database.
As for your example, a couple of things:
The relationship you have modeled is not one to many but one to one.
Public Banco Banco {get; set;}
Change To:
Public ICollection<Banco> Bancos {get;set;}
There are several ways you can model relationships with EF. Here's a sample of Modeling 1 to many relationships in EF.
The Column attribute is used to match to names in the DB. Make sure your EF CF properties that don't match the database have a Column Attribute. For Your RecCli it should look something like:
[Column("CodBanco")]
public int CodBancoBoleto { get; set; }
or
public int CodBanco { get; set; }
However, you are mapping a 1 to many relationship so having the CodBancoBoleto is not needed. Just use the navigation property of Public ICollection<Banco> Bancos {get;set;}. This should suffice except you might have to put a ForeignKey attribute for it telling it to use CodBanco as the key for the navigation.
[ForeignKey("CodBanco")]
Public ICollection<Banco> Bancos {get;set;}
You might have to do this for all your keys as the default code first convention for keys end with Id. I say might as your Banco Class's key is named properly CodBanco and marked with the Key. So you might be fine.
A final note is that you appear to be trying to use the constraints name for the mapping. You don't use the constraint name, rather the actual column names, aka the references part of the constraint.
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 have an issue with an Entity Framework from DB model.
My issue is down to the fact that one of my models has a multiple references to one table.
public partial class Customer
{
public int Id { get; set; }
public Nullable<int> PrimaryEngId { get; set; }
public Nullable<int> AssignedDevloperId { get; set; }
public virtual Engineer Engineer { get; set; }
public virtual Engineer Engineer1 { get; set; }
}
In my model the columns are mapped respectively, however when a colleague builds the model from the same database the two are reversed.
I believe the issue is that the first mapping to in was the primaryEngId
and the Db constraint is called FK_Customer_Engineer.
And the assigned developer id was added subsequently and the DB constraint is called FK_Customer_Devloper
So alphabetically Developer come before Engineer and Entity Framework now maps them the other way round.
My code references the Engineer in quite a lot of places which now won't work
Is there any way out of this?
Many thanks
Ian
You have to add missing ForeignKey attributes on foreign keys for those two navigation properties:
[ForeignKey("Primary")]
public int? PrimaryEngId { get; set; }
[ForeignKey("Assigned")]
public int? AssignedDevloperId { get; set; }
public virtual Engineer Primary { get; set; }
public virtual Engineer Assigned { get; set; }
NOTE: Also don't use generic names for navigation properties with EF. In the nutshell one of the best things EF gives you is that you can say:
#myCustomer.Assigned.Name
etc in the view, and you are totally screwing it up with names like Engineer and Engineer1.
NOTE2: Keep Nullable<int> to code generation. int? is a lot more readable.
NOTE3: Use VS refactoring to rename properties Engineer and Engineer1 to what they should be ( PrimaryEngineer and AssignedEningeer etc). After that add ForeignKey attributes to your model. That should be enough. However, any future changes that you are doing has to be done in the Code and not in db.
IF on the other hand you are constantly regenerating entities and context code from database, make sure that all your foreign keys has meaningful names, as EF will use them to generate name.(ie it is not named Engineer1 out of blue) Rename those foreign keys to reflect what logical relationship is. Ie you most likely have the following foreign keys in db:
FK_Customer_Engineer
FK_Customer_Engineer1
You need to rename them to
FK_Customer_PrimaryEngineer
FK_Customer_AssignedEngineer
Update: you can have different column name and property name like so:
[Column("PrimaryEngId")]
[ForeignKey("Primary")]
public int? PrimaryID { get; set; }
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.