I'm trying to model a simple QA application using code first for learning purposes. Users should be able to ask questions, answer questions and write comments for both questions and answers. Here is my model classes:
[Table("UserProfile")]
public class UserProfile
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
public DateTime? BirthDate { get; set; }
public ICollection<Gym> Gyms { get; set; }
}
[Table("Question")]
public class Question
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int QuestionId { get; set; }
public int UserProfileId { get; set; }
[ForeignKey("UserProfileId")]
public UserProfile UserProfile { get; set; }
public string Header { get; set; }
public string Content { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime CreateDate { get; set; }
public ICollection<Answer> Answers { get; set; }
public ICollection<QuestionComment> QuestionComments { get; set; }
}
[Table("QuestionComment")]
public class QuestionComment
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int QuestionCommentId { get; set; }
public int UserProfileId { get; set; }
[ForeignKey("UserProfileId")]
public UserProfile UserProfile { get; set; }
public int QuestionId { get; set; }
[ForeignKey("QuestionId")]
public Question Question { get; set; }
[Column("Content", TypeName = "ntext")]
public string Content { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime CreateDate { get; set; }
}
[Table("Answer")]
public class Answer
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int AnswerId { get; set; }
public int UserProfileId { get; set; }
[ForeignKey("UserProfileId")]
public UserProfile UserProfile { get; set; }
public int QuestionId { get; set; }
[ForeignKey("QuestionId")]
public Question Question { get; set; }
[Column("Content", TypeName="ntext")]
public string Content { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime CreateDate { get; set; }
public IList<AnswerComment> AnswerComments { get; set; }
}
[Table("AnswerComment")]
public class AnswerComment
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int AnswerCommentId { get; set; }
public int UserProfileId { get; set; }
[ForeignKey("UserProfileId")]
public UserProfile UserProfile { get; set; }
public int AnswerId { get; set; }
[ForeignKey("AnswerId")]
public Answer Answer { get; set; }
[Column("Content", TypeName = "ntext")]
public string Content { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime CreateDate { get; set; }
}
And here is my db context class:
public class TestDbContext : DbContext
{
public TestDbContext()
: base("DefaultConnection")
{
}
public DbSet<UserProfile> UserProfiles { get; set; }
public DbSet<Question> Questions { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// After update
modelBuilder.Entity<UserProfile>()
.HasMany(p => p.Questions)
.WithRequired()
.HasForeignKey(c => c.UserProfileId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<UserProfile>()
.HasMany(p => p.Answers)
.WithRequired()
.HasForeignKey(c => c.UserProfileId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<UserProfile>()
.HasMany(p => p.AnswerComments)
.WithRequired()
.HasForeignKey(c => c.UserProfileId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<UserProfile>()
.HasMany(p => p.QuestionComments)
.WithRequired()
.HasForeignKey(c => c.UserProfileId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Question>()
.HasMany(p => p.Answers)
.WithRequired()
.HasForeignKey(c => c.QuestionId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Question>()
.HasMany(p => p.QuestionComments)
.WithRequired()
.HasForeignKey(c => c.QuestionId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Answer>()
.HasMany(p => p.AnswerComments)
.WithRequired()
.HasForeignKey(c => c.AnswerId)
.WillCascadeOnDelete(false);
// After update
}
}
I'm getting the following error when creating db using the above declarations:
Introducing FOREIGN KEY constraint 'AnswerComment_UserProfile' on table 'AnswerComment' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.\r\nCould not create constraint. See previous errors.
What do I have to do to fix this?
Thanks in advance,
Try adding this to your UserProfile entity:
public virtual ICollection<AnswerComment> AnswerComments { get; set; }
And then add this to your modelBuilder, this should get rid off the error in your question, and you will probably have to do this for QuestionComment, Question, and Answer:
modelBuilder.Entity<AnswerComment>()
.HasRequired(u => u.UserProfile)
.WithMany(a => a.AnswerComments)
.WillCascadeOnDelete(false);
You have a table A that contains a FK to another table B.
In the same time table A contains a FK to table C.
Now, both tables B and C contains FKs to table D.
If all FKs are defined as delete cascade a record in table C can be deleted twice.
This is not a logic problem, but SQL Server does not support this option.
To avoid this problem set on delete no action.
Related
I am using SQL and attempting to Add-Migration using Entity Framework Core. I am unsure how to resolve this. It is for associating a review system with the user and the product. This worked in SQLite. Now using SQL server. I have tried to provide everything while being brief. I can provide more if needed. Below is my code, can anyone please help?
An error occurred while accessing the IWebHost on class 'Program'. Continuing without the application service provider. Error: Introducing FOREIGN KEY constraint 'FK_ProductReviews_AspNetUsers_ReviewerId' on table 'ProductReviews' 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 or index. See previous errors.
I have tried the commented out code.
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<MemberReview>()
.HasKey(k => new { k.RevieweeId, k.ReviewerId });
builder.Entity<MemberReview>().
HasOne(u => u.Reviewer)
.WithMany(u => u.ReviewedMembers);
// .HasForeignKey(u => u.ReviewerId)
// .OnDelete(DeleteBehavior.Restrict);
builder.Entity<MemberReview>().
HasOne(u => u.Reviewee)
.WithMany(u => u.MemberReviews);
// .HasForeignKey(u => u.RevieweeId)
// .OnDelete(DeleteBehavior.Restrict);
builder.Entity<ProductReview>()
.HasKey(k => new { k.ReviewerId, k.ReviewedProductId });
builder.Entity<ProductReview>().
HasOne(u => u.ReviewedProduct)
.WithMany(u => u.ProductReviews);
//.HasForeignKey(u => u.ReviewedProductId)
//.OnDelete(DeleteBehavior.Restrict);
builder.Entity<ProductReview>().
HasOne(u => u.Reviewer)
.WithMany(u => u.ReviewedProducts);
//.HasForeignKey(u => u.ReviewerId)
//.OnDelete(DeleteBehavior.Restrict);
}
public class ProductReview
{
public Product ReviewedProduct { get; set; }
public User Reviewer { get; set; }
[Required]
public int ReviewerId { get; set; }
[Required]
[MaxLength(30)]
public string ReviewerUserName { get; set; }
[Required]
public int ReviewedProductId { get; set; }
[Required]
[MaxLength(35)]
public string Title { get; set; }
[Required]
[MaxLength(420)]
public string Review { get; set; }
[Required]
[MaxLength(2)]
public int Rating { get; set; }
}
public class User : IdentityUser<int>
{
[Required]
[MaxLength(12)]
public string UserType { get; set; }
[Required]
public DateTime DateOfEstablishment { get; set; }
[Required]
[MaxLength(75)]
public string KnownAs { get; set; }
public DateTime Created { get; set; }
public DateTime LastActive { get; set; }
[MaxLength(420)]
public string Description { get; set; }
public ICollection<Photo> Photos { get; set; }
public ICollection<Product> Products { get; set; }
// REVIEW THING
public ICollection<MemberReview> MemberReviews { get; set; }
public ICollection<MemberReview> ReviewedMembers { get; set; }
public ICollection<ProductReview> ReviewedProducts { get; set; }
// *****
}
public class Product
{
public int Id { get; set; }
[Required]
[MaxLength(75)]
public string Name { get; set; }
[Required]
[MaxLength(420)]
public string Description { get; set; }
public DateTime DateAdded { get; set; }
public User User { get; set; }
[Required]
public int UserId { get; set; }
// REVIEW THINGS
public ICollection<ProductReview> ProductReviews { get; set; }
// *****
}
I just tried this:
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<MemberReview>()
.HasKey(e => new { e.RevieweeId, e.ReviewerId });
builder.Entity<ProductReview>()
.HasKey(e => new { e.ReviewerId, e.ReviewedProductId });
builder.Entity<MemberReview>()
.HasOne<User>(e => e.Reviewer)
.WithMany(e => e.MemberReviews)
.HasForeignKey(e => e.ReviewerId)
.OnDelete(DeleteBehavior.Restrict); ////////
//
builder.Entity<MemberReview>() //
.HasOne<User>(e => e.Reviewee) /// => only one of these two can be cascade
.WithMany(e => e.ReviewedMembers) //
.HasForeignKey(e => e.RevieweeId) //
.OnDelete(DeleteBehavior.Restrict); ////////
builder.Entity<ProductReview>()
.HasOne<User>(e => e.Reviewer)
.WithMany(e => e.ReviewedProducts)
.HasForeignKey(e => e.ReviewerId)
.OnDelete(DeleteBehavior.Restrict);
}
You had not provided the MemberReview class so I created this:
public class MemberReview
{
public User Reviewer { get; set; }
public int ReviewerId { get; set; }
public User Reviewee { get; set; }
public int RevieweeId { get; set; }
}
And this is the result:
I have the following:
public class Event : IEntity
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[JsonProperty("id")]
public Guid Id { get; set; }
[JsonProperty("description")]
public string Description { get; set; }
[JsonProperty("date")]
public DateTimeOffset Date { get; set; }
[JsonProperty("distance")]
public int Distance { get; set; }
[JsonProperty("verticalAscend")]
public int VerticalAscend { get; set; }
[ForeignKey("User")]
[JsonProperty("userId")]
public Guid UserId { get; set; }
//attending
public DateTimeOffset DateCreated { get; set; }
public DateTimeOffset DateModified { get; set; }
[JsonProperty("user")]
public virtual User User { get; set; }
[JsonProperty("comments")]
public virtual ICollection<Comment> Comments { get; set; }
[JsonProperty("attending")]
public virtual ICollection<User> AttendingList { get; set; }
}
And:
public class User : IEntity
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[JsonProperty("id")]
public Guid Id { get; set; }
[JsonProperty("profilePicUrl")]
public string ProfilePicUrl { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("surname")]
public string LastName { get; set; }
[JsonProperty("email")]
public string Email { get; set; }
public DateTimeOffset DateCreated { get; set; }
public DateTimeOffset DateModified { get; set; }
public virtual ICollection<Event> AttendingEvents { get; set; }
[InverseProperty("User")]
public virtual ICollection<Event> Events { get; set; }
}
Relationships:
Event:
Many Users attending (AttendingList)
User:
Can attend many events (AttendingEvents)
Can create multiple events (Events)
There exists a many-many relationship between the Event.AttendingList and User.AttendingEvents.
There exists 0-many relationship between Event.User and User.Events, with ForeignKey as UserId.
I am trying to configure these with Fluent API, and using the InverseProperty to configure the other side of the relationship, mapping back to Event.User, but getting the following error:
Introducing FOREIGN KEY constraint 'FK_dbo.UserEvents_dbo.Events_Event_Id' on table 'UserEvents' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
I am unsure on how to solve this relationship on one table. What am I doing wrong?
In the DbContext configure your models as follows:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Event>().HasRequired(e => e.User)
.WithMany(u => u.Events)
.HasForeignKey(e => e.UserId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<User>()
.HasMany<Event>(s => s.AttendingEvents)
.WithMany(c => c.AttendingList)
.Map(cs =>
{
cs.MapLeftKey("UserId");
cs.MapRightKey("EventId");
cs.ToTable("UserEvents");
});
}
I have two tables User and Message like below:
public partial class User
{
public int UserID { get; set; }
public string Name { get; set; }
public bool Status { get; set; }
public DateTime SubmitDate { get; set; }
public virtual ICollection<Message> Messages { get; set; }
}
public partial class Message
{
public int MessageID { get; set; }
public int SenderID { get; set; }
public int ReceiverID { get; set; }
public DateTime SubmitDate { get; set; }
public string Text { get; set; }
public bool Status { get; set; }
public int? ReplyToMessaageID { get; set; }
public virtual ICollection<Message> Messages { get; set; }
public virtual User SenderUser { get; set; }
public virtual User ReceiverUser { get; set; }
}
And in domain layer class:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//Configure domain classes using modelBuilder here
modelBuilder.Entity<Message>()
.HasOptional(c => c.Messages)
.WithMany()
.HasForeignKey(c => c.ReplyToMessaageID);
modelBuilder.Entity<Message>()
.HasRequired(c => c.SenderUser)
.WithMany(c => c.Messages)
.HasForeignKey(c => c.SenderID)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Message>()
.HasRequired(c => c.ReceiverUser)
.WithMany(c => c.Messages)
.HasForeignKey(c => c.ReceiverID)
.WillCascadeOnDelete(false);
base.OnModelCreating(modelBuilder);
}
Everything is fine but as can be seen in the final result, Message table has a waste column with name User_UserID.
How can I avoid creating it?
Here is the case, I have 2 entities, such as Contract、Media。
public class Media : Entity
{
public string Name {get; set;}
public bool Enabled
*//other properties can be ignored..*
}
public class Contract : Entity
{
public string Code {get; set;}
*//other properties can be ignored..*
}
Contract has many Medias, it seems that they are many to many.
But!! at ef code first, i need 3 more fields in the ContractMedia table(ef auto generated).
such as StartDate,EndDate and Price. these could not be added in Media entity.
How to map at this case??
If you want to create many to many relationship with additional data in association table, you have to make the association table as entity. The pure many to many relationship is only in pure table with entity id's.
In you case it will be:
public class Media // One entity table
{
public int Id { get; set; }
public string Name { get; set; }
public bool Enabled { get; set; }
public virtual ICollection<ContractMedia> ContractMedias { get; set; }
}
public class Contract // Second entity table
{
public int Id { get; set; }
public string Code { get; set }
public virtual ICollection<ContractMedia> ContractMedias { get; set; }
}
public class ContractMedia // Association table implemented as entity
{
public int MediaId { get; set; }
public int ContractId { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public double Price { get; set; }
public virtual Media Media { get; set; }
public virtual Contract Contract { get; set; }
}
And after you created models/entities, you need to define relationships in context:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ContractMedia>()
.HasKey(c => new { c.MediaId, c.ContractId });
modelBuilder.Entity<Contract>()
.HasMany(c => c.ContractMedias)
.WithRequired()
.HasForeignKey(c => c.ContractId);
modelBuilder.Entity<Media>()
.HasMany(c => c.ContractMedias)
.WithRequired()
.HasForeignKey(c => c.MediaId);
}
Also you can refer to these links:
Many to many mapping with extra fields in Fluent API
Entity Framework CodeFirst many to many relationship with additional information
Create code first, many to many, with additional fields in association table
Adding to #Tomas answer without having to use Fluent API.
public class Media // One entity table
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<ContractMedia> ContractMedias { get; set; }
}
public class Contract // Second entity table
{
public int Id { get; set; }
public string Code { get; set }
public virtual ICollection<ContractMedia> ContractMedias { get; set; }
}
public class ContractMedia // Association table implemented as entity
{
[Key]
[Column(Order = 0)]
[ForeignKey("Media")]
public int MediaId { get; set; }
[Key]
[Column(Order = 1)]
[ForeignKey("Contract")]
public int ContractId { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public double Price { get; set; }
public virtual Media Media { get; set; }
public virtual Contract Contract { get; set; }
}
EF Core needs to use Fluent API but it would look like this:
internal class MyContext : DbContext
{
public MyContext(DbContextOptions<MyContext> options)
: base(options)
{
}
public DbSet<Post> Posts { get; set; }
public DbSet<Tag> Tags { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>()
.HasMany(p => p.Tags)
.WithMany(p => p.Posts)
.UsingEntity<PostTag>(
j => j
.HasOne(pt => pt.Tag)
.WithMany(t => t.PostTags)
.HasForeignKey(pt => pt.TagId),
j => j
.HasOne(pt => pt.Post)
.WithMany(p => p.PostTags)
.HasForeignKey(pt => pt.PostId),
j =>
{
j.Property(pt => pt.PublicationDate).HasDefaultValueSql("CURRENT_TIMESTAMP");
j.HasKey(t => new { t.PostId, t.TagId });
});
}
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public ICollection<Tag> Tags { get; set; }
public List<PostTag> PostTags { get; set; }
}
public class Tag
{
public string TagId { get; set; }
public ICollection<Post> Posts { get; set; }
public List<PostTag> PostTags { get; set; }
}
public class PostTag
{
public DateTime PublicationDate { get; set; }
public int PostId { get; set; }
public Post Post { get; set; }
public string TagId { get; set; }
public Tag Tag { get; set; }
}
Source:
https://learn.microsoft.com/en-us/ef/core/modeling/relationships?tabs=fluent-api%2Cfluent-api-simple-key%2Csimple-key#join-entity-type-configuration
I have aproject with codefirst migrations wiht the following classes:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Chat> Chats { get; set; }
}
and
public class Chat
{
[Key, Column(Order = 0)]
public int User1Id { get; set; }
public virtual User User1 { get; set; }
[Key, Column(Order = 1)]
public int User2Id { get; set; }
public virtual User User2 { get; set; }
public string Channel { get; set; }
}
The Idea is A chat has two users, and the chat Primary key is a composite key of the two user Ids, which are foreign keys.
A user has also a list of chats, in which he participates.
now when I try to run this, I get the following error:
Introducing FOREIGN KEY constraint 'FK_dbo.Chat_dbo.User_User2Id' on table 'Chat' 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 kind of understand what's happening, but I don't know how to fix it. Tried this (from a topic here somewhere):
public DbSet<User> Users { get; set; }
public DbSet<Chat> Chats { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<Chat>()
.HasRequired(s => s.User1)
.WithMany()
.WillCascadeOnDelete(true);
modelBuilder.Entity<Chat>()
.HasRequired(s => s.User2)
.WithMany()
.WillCascadeOnDelete(true);
base.OnModelCreating(modelBuilder);
}
but with no result, i still get the error. Can anyone shine some light on this matter?
try
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Chat> InitiatedChats { get; set; }
public virtual ICollection<Chat> ParticipantInChats { get; set; }
}
public class Chat
{
[Key, Column(Order = 0)]
public int User1Id { get; set; }
public virtual User User1 { get; set; }
[Key, Column(Order = 1)]
public int User2Id { get; set; }
public virtual User User2 { get; set; }
public string Channel { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Chat>()
.HasRequired(s => s.User1)
.WithMany(u => u.InitiatedChats)
.HasForeignKey(s => s.User1Id)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Chat>()
.HasRequired(s => s.User2)
.WithMany(u => u.ParticipantInChats)
.HasForeignKey(s => s.User2Id)
.WillCascadeOnDelete(false);
base.OnModelCreating(modelBuilder);
}
The anwser comes from this post