I'm defining a many-to-many relationship as follows:
modelBuilder.Entity<GameSessionEntry>().
HasMany(c => c.Users).
WithMany(p => p.GameSessionEntries).
Map(
m =>
{
m.MapLeftKey("SessionId");
m.MapRightKey("UserId");
m.ToTable("UserSessions");
});
However, I keep getting:
The Foreign Key on table 'UserSessions' with columns 'UserId' could
not be created because the principal key columns could not be
determined. Use the AddForeignKey fluent API to fully specify the
Foreign Key.
I'm new to database work and the EntityFramework in general - what is it asking me to do?
It's the recurring confusion with left and right, see this explanation by Slauma. So you just have to turn around the key names:
m.MapLeftKey("UserId"); // Property in the HasMany call
m.MapRightKey("SessionId"); // Property in the WithMany call
This is how I usually go about creating a many to many table (note this requires no fluent api configuration)
public class User
{
public int Id { get; set; }
public virtual ICollection<UserSession> UserSessions { get; set; }
}
public class Session
{
public int Id { get; set; }
public virtual ICollection<UserSession> UserSessions { get; set; }
}
public class UserSession
{
[Key]
[Column(Order = 1)]
public int UserId { get; set; }
[Key]
[Column(Order = 2)]
public int SessionId{ get; set; }
public virtual User User { get; set; }
public virtual Session Session { get; set; }
}
Instead of fiddling around with a many-many relationship you should rewrite it to a weak entity set.
If you have for instance this relationship:
You can redesign it to a weak entity set:
By doing this you get rid of the many-many relationship and don't have to store the same data in multiple tables.
For more information: http://fileadmin.cs.lth.se/cs/Education/EDA216/lectures/dbtoh4.pdf
Read the lecture slides about "The Relational Data Model" starting on slide 87/360.
Related
I have a model which is used for managing friend relationships. It looks as follows:
public class Relationship
{
[Required]
public User User { get; set; }
[Required]
public User Friend { get; set; }
[Required]
public DateTimeOffset RelationshipInitializationDate { get; set; }
}
Users will have multiple records for their ID and there will be multiple records with the same FriendID so defining either of these as a key is a no-go. I would like the key to be a composite between User and Friend but when I define it like this:
modelBuilder.Entity<Relationship>().HasKey(r => new { r.User, r.Friend });
I get an error that states:
The property 'Relationship.User' is of type 'User' which is not supported by current database provider. Either change the property CLR type or ignore the property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
How should I go about this to create the primary key that will link with a user and friend object. I didn't have any issues with my other objects having typed properties and I don't have an issue if I add an arbitrary key to the Relationship model. Thanks in advance
The basic idea here is that your adding properties to the model that EF can use to make a relationship. Right you're trying to create a relationship of type User and that is creating an error. To assign a composite key each key needs to be a type compatible with a Key field, not a navigation property. So we add UserId and FriendId of type int, string or GUID etc. and create a relationship off those properties.
public class Relationship
{
public User Friend { get; set; }
public User User { get; set; }
public int FriendId { get; set; }
public int UserId { get; set; }
public DateTimeOffset RelationshipInitializationDate { get; set; }
}
public class User
{
public int UserId { get; set; }
}
You can now define a composite key across UserId and FriendId. Something like this should do:
public class NorthwindContext : DbContext
{
public NorthwindContext(DbContextOptions<NorthwindContext> options):base(options) { }
public NorthwindContext() { }
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Relationship>().HasKey(table => new {
table.FriendId, table.UserId
});
}
public DbSet<Relationship> Relationships { get; set; }
public DbSet<User> Users { get; set; }
}
Source: Medium - How To: Entity Framework Core relationships, composite keys, foreign keys, data annotations, Code First and Fluent API
I'm struggling with composite keys and extra fields being generated by Entity Framework. I have a question about something which I think is odd.
Let's say I have a one to many relationships with these classes:
File (dossier)
[Table("Dossier")]
public class Dossier
{
[Key]
public string Dossiernummer { get; set; }
[Key]
public string Dossierversie { get; set; }
[Required]
public string Dossierreferentie { get; set; }
[Required]
public string Relatienr { get; set; }
public ICollection<Artikel> Artikels { get; set; } ();
}
And my artikel (article) class:
[Table("Artikel")]
public class Artikel
{
[Key]
public string Artnr { get; set; }
[Key]
public string ArtVersie { get; set; }
public string ArtOmschrijving { get; set; }
public Dossier Dossier { get; set; }
public string Dossiernummer { get; set; }
}
I'm using migrations and a code first approach. For some reason using migrations creates a dossiernummer1 column in the artikel table. I don't understand why and would like it gone. Does anyone know how?
Another thing which I prefer not to have is the second primary key in my artikel table. It puts both keys from the dossier table in the artikel table yet I only want to use Dossiernummer as a foreign key. Do you know how to change this?
When getting all the dossiers from the context I notice something odd as well. When I look into a dossier object the artikels list is empty, even though data exists in the database for that. Is it normal you have to initialize it yourself?
Thanks for any help and info in advance.
Kind regards,
you must use fluent API for set relations and add ColumnAttribute to order keys :
[Table("Artikel")]
public class Artikel
{
[Key]
[Column(Order = 1)]
public string Artnr { get; set; }
[Key]
[Column(Order = 2)]
public string ArtVersie { get; set; }
public string ArtOmschrijving { get; set; }
public Dossier Dossier { get; set; }
public string Dossiernummer { get; set; }
}
[Table("Dossier")]
public class Dossier
{
[Key]
[Column(Order = 1)]
public string Dossiernummer { get; set; }
[Key]
[Column(Order = 2)]
public string Dossierversie { get; set; }
[Required]
public string Dossierreferentie { get; set; }
[Required]
public string Relatienr { get; set; }
public ICollection<Artikel> Artikels { get; set; }
}
in your dbcontext override OnModelCreating method:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
builder.Entity<Dossier>()
.HasMany(x => x.Artikels)
.WithOne(a => a.Dossier)
.HasForeignKey(a => new { a.Dossiernummer, a.Artnr });
builder.Entity<Artikel>()
.HasKey(x => new {x.Artnr,x.ArtVersie});
builder.Entity<Dossier>()
.HasKey(x => new {x.Dossiernummer,x.Dossierversie});
}
Dossiernummer1:
Artikel has a Dossier. EF knows an K relationship must be set up. This requires that the PK of Dossier must be included in Artikel and wants to add it. It finds you've already put in a field with that name (what for it has no idea) and so it adds it as Dossiernummer1. You should not add Dossiernummer to Artikel - unless you you actually need one for something else - as the only reason it's there is to be an FK. EF will take care of that for you.
Adding Dossierversie to Artikel:
It thinks that the PK of Dossier is Dossiernummer + Dossierversie, and so to point to the correct Dossier it must have both of tem. I don't use code-first so I can't advise you on a) how to specify a PK and another, separate index (I assume that's what you want) versus a compound PK (which is what you appear to have).
Dossier.Artikels is empty: That's the way EF works, known as lazy loading. It gets the 'root' objects by not anything owned by them at first. Once your code accesses an Artikels collection it should load them (for that Dossier) at that point. This prevents EF pulling in what could be a large percentage of your database Imaging an ECommerce system. Getting a Customer list would pull in all Orders related to cutomers in that list; all order lines owned by those orders; all Product data related to the products on those order lines and so on. This would not be a good thing. Instead it just gets the things you've specifically mentioned and then pulls in related items as needed.
Incidentally, when looking as an unloaded collection as an attribute of the owner (e.g. looking at Artikels on a loaded Dossier), the debugger in VS tells me that examining the collection will result in it being loaded and gives me the option to continue or not.
I am using Entity Framework 6.1.3 and have the two models as shown below. However, when I run migration I get the below error:
Unable to determine the principal end of an association between the
types 'Example.Models.GiftVoucher' and
'Example.Models.Payment'. The principal end of this
association must be explicitly configured using either the
relationship fluent API or data annotations.
I have searched for it and found this question. One of the solution was to use the Required attribute.
However, I don't know how I can do the following in Entity Framework:
Rename GiftVoucherId and PaymentId in both models that are as foreign keys to Purchase_Id and Redemption_Idas shown in the image.
Then do something equivalent in Entity Framework like this CONSTRAINT fk-giftvoucher-table FOREIGN KEY (Purchase_Id) REFERENCES PAYMENT (PaymentId).
Payment Model
public class Payment {
public int Id { get; set; }
[Required]
public DateTime DateTime { get; set; }
public double Amount { get; set; }
[Required]
public int GiftVoucherId { get; set; }
public GiftVoucher GiftVoucher { get; set; }
}
Gift Voucher Model
public class GiftVoucher
{
public int Id { get; set; }
public double Amount { get; set; }
[Required]
public int PaymentId { get; set; }
public Payment Payment { get; set; }
}
I don't tend to use one-to-one relationships a lot... Unless i want to Vertically Partition for performance needs or there is an incessant OCD need to separate the concerns.
*Note: One-to-one relationship is technically not possible in SQL Server. It will always be one-to-zero-or-one. EF forms One-to-One relationships on entities not in DB.*
However, documentation is your friend
Configuring a Relationship Where Both Ends Are Required (One-to-One)
In most cases the Entity Framework can infer which type is the
dependent and which is the principal in a relationship. However, when
both ends of the relationship are required or both sides are optional
the Entity Framework cannot identify the dependent and principal. When
both ends of the relationship are required, use WithRequiredPrincipal
or WithRequiredDependent after the HasRequired method.
...
Given the following
public class GiftVoucher
{
// your primary key
public int GiftVoucherId { get; set; }
public virtual Payment Payment { get; set; }
// other properties
public double Amount { get; set; }
}
public class Payment
{
// We need to share the same key
public int GiftVoucherId { get; set; }
public virtual GiftVoucher GiftVoucher { get; set; }
// other properties
public DateTime DateTime { get; set; }
public double Amount { get; set; }
}
We can Fluent API like this
// Configure the primary key for the OfficeAssignment
modelBuilder.Entity<Payment>()
.HasKey(t => t.GiftVoucherId);
// we are essentially making GiftVoucher the principle in the DB
modelBuilder.Entity<GiftVoucher>()
.HasRequired(t => t.Payment)
.WithRequiredPrincipal(t => t.GiftVoucher);
So basically HasRequired is making GiftVoucher required and WithRequiredPrincipal is making Payment required.. In turn, EF will throw if the above is not satisfied
How do I store additional fields in the "link table" that is automagically created for me if I have two entities associated as having a many to many relationship?
I have tried going the "two 1 to many associations"-route, but I'm having a hard time with correctly configuring the cascading deletion.
Unless those extra columns are used by some functions or procedures at the database level, the extra columns in the link table will be useless since they are completely invisible at the Entity Framework level.
It sounds like you need to re-think your object model. If you absolutely need those columns, you can always add them later manually.
You will most likely need to expose the association in your domain model.
As an example, I needed to store an index (display order) against items in an many-to-many relationship (Project <> Images).
Here's the association class:
public class ProjectImage : Entity
{
public Guid ProjectId { get; set; }
public Guid ImageId { get; set; }
public virtual int DisplayIndex { get; set; }
public virtual Project Project { get; set; }
public virtual Image Image { get; set; }
}
Here's the mapping:
public class ProjectImageMap : EntityTypeConfiguration<ProjectImage>
{
public ProjectImageMap()
{
ToTable("ProjectImages");
HasKey(pi => pi.Id);
HasRequired(pi => pi.Project);
HasRequired(pi => pi.Image);
}
}
From Project Map:
HasMany(p => p.ProjectImages).WithRequired(pi => pi.Project);
Maps to the following property on project:
public virtual IList<ProjectImage> ProjectImages { get; set; }
Hope that helps
Ben
Suppose there is a many-to-many association between two types: User and Message, and the association class is defined as UserMessageLink with additional properties.
public class User {
public int Id {get;set;}
}
public class Message {
public int Id {get;set;}
}
//The many-to-many association class with additional properties
public class UserMessageLink {
[Key]
[Column("RecieverId", Order = 0)]
[ForeignKey("Reciever")]
public virtual int RecieverId { get; set; }
[Key]
[Column("MessageId", Order = 1)]
[ForeignKey("Message")]
public virtual int MessageId { get; set; }
public virtual User Reciever { get; set; }
public virtual Message Message { get; set; }
//This is an additional property
public bool IsRead { get; set; }
}
I've been trying to get EF4 CTP5 to play nice with an existing database, but struggling with some basic mapping issues.
I have two model classes so far:
public class Job
{
[Key, Column(Order = 0)]
public int JobNumber { get; set; }
[Key, Column(Order = 1)]
public int VersionNumber { get; set; }
public virtual User OwnedBy { get; set; }
}
and
[Table("Usernames")]
public class User
{
[Key]
public string Username { get; set; }
public string EmailAddress { get; set; }
public bool IsAdministrator { get; set; }
}
And I have my DbContext class exposing those as IDbSet
I can query my users, but as soon as I added the OwnedBy field to the Job class I began getting this error in all my tests for the Jobs:
Invalid column name 'UserUsername'.
I want this to behave like NHibernate's many-to-one, whereas I think EF4 is treating it as a complex type. How should this be done?
UserUsername is the default name that EF Code First chooses for the foreign key in the Jobs table. Obviously, it's not the same name that your existing database has for the FK column in Jobs table. You need to override this conventions and change the FK's default name so that it matches your database. Here is how it's done with fluent API:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Job>()
.HasOptional(j => j.OwnedBy)
.WithMany()
.IsIndependent()
.Map(m => m.MapKey(u => u.Username, "TheFKNameInYourDB"));
}
Try let it create new database from your schema and look what columname it expect.
I think its foreing key for OwnedBy. It tries to name it according to its internal convention, that is incompatible with how you named your foreing key column. And no, its really treating it as many-to-one.