From examples that I have seen online and in a Programming Entity Framework CodeFirst book, when you have a collection on both classes EF would create a mapping table such as MembersRecipes and the primary key from each class would link to this table.
However when I do the below, I instead get a new field in the Recipes table called Member_Id and a Recipe_Id in the Members table.
Which only creates two one-to-many relationships, but not a many-to-many so I could have Member 3 linked to Recipes (4,5,6) and Recipe 4 linked to Members (1,2,3) etc.
Is there a way to create this mapping table? and if so how do you name it something else such as "cookbooks" ?
Thanks
public abstract class Entity {
[Required]
public int Id { get; set; }
}
public class Member : Entity {
[Required]
public string Name { get; set; }
public virtual IList<Recipe> Recipes { get; set; }
}
public class Recipe : Entity {
[Required]
public string Name { get; set; }
[ForeignKey("Author")]
public int AuthorId { get; set; }
public virtual Member Author { get; set; }
....
public virtual IList<Member> Members { get; set; }
}
UPDATE:
Below is another approach I have tried which doesn't use the Fluent API and replaces the AuthorId & Author on Recipe with an owner flag, I have also renamed the below example from Cookbooks to MembersRecipes, this also fixes my issue similar to the answer but as mentioned has further implications.
public class MembersRecipes {
[Key, Column(Order = 0)]
[ForeignKey("Recipe")]
public int RecipeId { get; set; }
public virtual Recipe Recipe { get; set; }
[Key, Column(Order = 1)]
[ForeignKey("Member")]
public int MemberId { get; set; }
public virtual Member Member { get; set; }
public bool Owner { get; set; }
}
and in Recipe & Member classes I changed the collections to
public virtual IList<MembersRecipes> MembersRecipes { get; set; }
Do this on your DbContext OnModelCreating:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Recipe>()
.HasMany(x => x.Members)
.WithMany(x => x.Recipes)
.Map(x =>
{
x.ToTable("Cookbooks"); // third table is named Cookbooks
x.MapLeftKey("RecipeId");
x.MapRightKey("MemberId");
});
}
You can do it the other way around too, it's the same, just another side of the same coin:
modelBuilder.Entity<Member>()
.HasMany(x => x.Recipes)
.WithMany(x => x.Members)
.Map(x =>
{
x.ToTable("Cookbooks"); // third table is named Cookbooks
x.MapLeftKey("MemberId");
x.MapRightKey("RecipeId");
});
Further examples:
http://www.ienablemuch.com/2011/07/using-checkbox-list-on-aspnet-mvc-with_16.html
http://www.ienablemuch.com/2011/07/nhibernate-equivalent-of-entity.html
UPDATE
To prevent cyclical reference on your Author property, aside from above, you need to add this:
modelBuilder.Entity<Recipe>()
.HasRequired(x => x.Author)
.WithMany()
.WillCascadeOnDelete(false);
Idea sourced here: EF Code First with many to many self referencing relationship
The core thing is, you need to inform EF that the Author property(which is a Member instance) has no Recipe collections(denoted by WithMany()); that way, cyclical reference could be stopped on Author property.
These are the created tables from the Code First mappings above:
CREATE TABLE Members(
Id int IDENTITY(1,1) NOT NULL primary key,
Name nvarchar(128) NOT NULL
);
CREATE TABLE Recipes(
Id int IDENTITY(1,1) NOT NULL primary key,
Name nvarchar(128) NOT NULL,
AuthorId int NOT NULL references Members(Id)
);
CREATE TABLE Cookbooks(
RecipeId int NOT NULL,
MemberId int NOT NULL,
constraint pk_Cookbooks primary key(RecipeId,MemberId)
);
Related
I am trying to create relationships from the AspNetUsers table autogenerated by AspNet Identity to tables created by myself. Ex. Table A has a UserId column set to nvarchar(128) and I want it to relate to the UserId column the AspNet Users table. This is an Asp.Net MVC web app.
first I have some considerations you should not use nvarchar as FK much less as UserId because it is much more performant for a structural database like SQL to index but if you are using non-structural ignore this ...
good now for your solution see relationships between tables must be done through a ConfigMap where the entityFramework understands the relationships between objects and map them to the database I will give you an example:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Configure StudentId as FK for StudentAddress
modelBuilder.Entity<Student>()
.HasRequired(s => s.Address)
.WithRequiredPrincipal(ad => ad.Student);
}
consider these classes as relation objects
public class Student
{
public int StudentId { get; set; }
public string StudentName { get; set; }
public virtual StudentAddress Address { get; set; }
}
public class StudentAddress
{
[ForeignKey("Student")]
public int StudentAddressId { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public int Zipcode { get; set; }
public string State { get; set; }
public string Country { get; set; }
public virtual Student Student { get; set; }
}
see that the mapping on how the relationship should occur was done here OnModelCreating
there is another way to do it by making a configuration class for each object/table in the database that way it would be more organized
class CustomerConfiguration : IEntityTypeConfiguration<Customer>
{
public void Configure(EntityTypeBuilder<Customer> builder)
{
builder.HasKey(c => c.AlternateKey);
builder.Property(c => c.Name).HasMaxLength(200);
}
}
example:
EF Core Mapping EntityTypeConfiguration
I can also put the entityFramework documentation as a good guide for its development
1:1
https://www.entityframeworktutorial.net/code-first/configure-one-to-one-relationship-in-code-first.aspx
1:N
https://www.entityframeworktutorial.net/code-first/configure-one-to-many-relationship-in-code-first.aspx
N:N
https://www.entityframeworktutorial.net/code-first/configure-many-to-many-relationship-in-code-first.aspx
I really hope I helped with your question.
another point did not comment but you should study about migrations and use them in your project to version your changes in the database
https://www.entityframeworktutorial.net/efcore/entity-framework-core-migration.aspx#:~:text=Migration%20is%20a%20way%20to,on%20the%20EF%20Core%20model.
I have a schema Definitions which I would like to be able to reference itself. As I need meta data about the reference, there's a coupling schema named Associations. I'm using Entity Framework's fluent API in conjunction with data annotation attributes.
Definitions:
public class Definition
{
[Key]
public int Id { get; set; }
// ...
public virtual ICollection<Association> Associations { get; set; }
}
Associations:
public class Association
{
[Key]
public int Id { get; set; }
public int TypeId { get; set; }
public int AssociatedDefinitionId { get; set; }
public int RootDefinitionId { get; set; }
public virtual AssociationType Type { get; set; }
public virtual Definition AssociatedDefinition { get; set; }
public virtual Definition RootDefinition { get; set; }
}
OnModelCreating:
modelBuilder.Entity<Association>()
.HasRequired(p => p.AssociatedDefinition)
.WithRequiredPrincipal();
modelBuilder.Entity<Association>()
.HasRequired(p => p.RootDefinition)
.WithRequiredPrincipal();
I use MySQL as the database engine.
When I try to save a definition entity with an empty association collection, I get a constraint violation:
Cannot add or update a child row: a foreign key constraint fails
("u0228621_8"."Definitions", CONSTRAINT
"FK_Definitions_Associations_Id" FOREIGN KEY ("Id") REFERENCES
"Associations" ("Id"))
What am I doing wrong?
You have defined your association class with all relationships being "required:required" because of the WithRequiredPrincipal which doesn't seem to be what you want. Since the Associations collection appears (from the comments) to be the relation from the Root definitions, the mapping should come from definition, like so:
// Foreign key mappings included.
modelBuilder.Entity<Definition>().HasMany(d => d.Assocations)
.WithRequired(a => a.RootDefinition).HasForeignKey(a => a.RootDefinitionId);
modelBuilder.Entity<Association>().HasRequired(a => a.AssociatedDefinition)
.HasForeignKey(a => a.AssociatedDefinitionId);
So the Associations collection may be empty, but every Association requires a RootDefinition and AssociatedDefinition.
I am developing a sample application where people can place bets on sports events and earn points. It has the following Entity Framework Code-First models:
public class Person
{
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
}
public class Race
{
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
}
public class RaceBet
{
[Key]
public int Id { get; set; }
[Required]
public int RaceId { get; set; }
[Required]
public int PersonId { get; set; }
[Required]
public int CompetitorId { get; set; }
public virtual Race Race { get; set; }
public virtual Person Person { get; set; }
public virtual Person Competitor { get; set; }
}
A Person can place a bet for a Race and he can bet on any other Person (Competitor).
The models will produce the following error:
Introducing FOREIGN KEY constraint 'FK_dbo.RaceBets_dbo.People_PersonId' on table 'RaceBets' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints. Could not create constraint. See previous errors.
I tried removing the OneToManyCascadeDeleteConvention, adding fluent configurations to prevent cascade delete for RaceBet and all other variations of the api, but everything fails.
modelBuilder
.Entity<RaceBet>()
.HasRequired(x => x.Person)
.WithMany()
.HasForeignKey(x => x.PersonId)
.WillCascadeOnDelete(false);
How can I resolve this? Is the concept behind my models wrong?
Thanks!
Thanks to Oleg for his comment:
I can't to reproduce exception with this code: protected override void
OnModelCreating(DbModelBuilder modelBuilder) {
modelBuilder.Conventions.Add(new
System.Data.Entity.ModelConfiguration.Conventions.OneToManyCascadeDeleteConvention());
modelBuilder .Entity() .HasRequired(x => x.Person)
.WithMany() .HasForeignKey(x => x.PersonId)
.WillCascadeOnDelete(false); }
This fixed the model creation.
I'm using this kind of model for a 0..1 to many relationship. A Page must either have a valid book id or null.
public class Book
{
[Key]
public Guid Id { get; set; }
public virtual List<Page> Pages { get; set; }
}
public class Page
{
[Key]
public Guid Id { get; set; }
public virtual Book Book { get; set; }
}
I want to add cascading deletes, so that if a book is deleted then all of its pages are also deleted, not set to null.
I can (only?) do this with the fluent api:
modelBuilder.Entity<Page>()
.HasOptional(a => a.Book)
.WithOptionalDependent()
.WillCascadeOnDelete(true);
Using [Required] is not suitable, because the field is not required.
However, this creates another column Book_Id1, index and foreign key in the database, rather than adding cascading deletes on the existing FK, because it's defined twice.
If I comment out the Book.Pages property, it works, but I lose the ability to call book.Pages and have to instead call dbcontext.Pages.Where(p => p.Book.Id == book.Id), which is not ideal because I don't want the calling code to have to know about the dbcontext object.
Is there a way to have both the Book.Pages property and cascading deletes? Perhaps setting both to use the same FK name?
here what you can do
public class Book
{
[Key]
public Guid Id { get; set; }
public virtual List<Page> Pages { get; set; }
}
public class Page
{
[Key]
public Guid Id { get; set; }
public Guid BookId { get; set;}
//[ForeignKey("BookId")] you can add the fluent here or during entity builder
public virtual Book Book { get; set; }
}
modelBuilder.Entity<Page>()
.HasOptional(a => a.Book)
.WithMany(a=>a.Pages)
.HasForeignKey(a=>a.BookId)
.WillCascadeOnDelete(true);
var pages= dbcontext.Pages.Where(p => p.BookId == book.Id); // this will work
this code should work normally for you
i think in codefirst you have to try this
dbcontext.Page.RemoveRange(book.Pages);
dbcontext.Book.Remove(book);
dbContext.SaveChanges();
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.