Problem with relations beetwen entity models - c#

I want to make a Like and Dislike system in my project ,
I have a model for User, a model for Post, a model for Comment with relations below:
User 1 ---> * Post
User 1 ---> * Comment
Post 1 ---> * Comment
now I want to add a new model called Like with relations below:
Post 1 ---> * Like
User 1 ---> * Like
but when I want to update database I get an error that says :
"may cause cycles or multiple cascade paths"
I found out if I remove one of my properties it fix the error,
for example:
public class Post
{
public Post()
{
}
[Key]
public int Id { get; set; }
public string Title { get; set; }
public virtual List<Like> Likes { get; set; }
public virtual List<Comment> Comments { get; set; }
}
public class Like
{
public Like()
{
}
[Key]
public int Id { get; set; }
public bool IsLike { get; set; }
public int postId { get; set; } // I remove these properties
public virtual Post post { get; set; }
public int commentId { get; set; } // I remove these properties
public virtual Comment comment { get; set; }
}
for fixing the "multiple cascade" error I delete "PostId" and "commentId" properties .
But when I add entity (new data) to my table (Like) in database ,
I don't know some how my post gets duplicated I mean a repeat of post become added to the table.
can any body tell me the problem?

For better design, separate your like table for Post and Comment as follows:
public class User
{
[Key]
public int Id { get; set; }
//......
public virtual List<Post> Posts { get; set; }
public virtual List<Comment> Comments { get; set; }
public virtual List<PostLikes> PostLikes { get; set; }
public virtual List<CommentLIkes> CommentLikes { get; set; }
}
public class Post
{
[Key]
public int Id { get; set; }
public string Title { get; set; }
public virtual List<PostLike> PostLikes { get; set; }
public virtual List<Comment> Comments { get; set; }
}
public class Comment
{
[Key]
public int Id { get; set; }
public string CommentBody { get; set; }
//.....
public virtual List<CommentLike> CommentLikes { get; set; }
}
public class PostLike
{
[Key]
public int Id { get; set; }
public int PostId { get; set; }
public int UserId {get; set;}
public bool IsLike { get; set; }
public virtual Post post { get; set; }
public virtual User User { get; set; }
}
public class CommentLike
{
[Key]
public int Id { get; set; }
public int CommentId { get; set; }
public int UserId {get; set;}
public bool IsLike { get; set; }
public virtual Comment Comment { get; set; }
public virtual User User { get; set; }
}
Now generate a brand new Migration and update the database accordingly.
Note: you may face cascade delete problem on migration update. If you face let me know I shall update the answer with Fluent API configuration.

The problem is, that your database is not normalized enough.
I see that users can create Posts. They can also Comment on a Post and they can Like comments.
Because a Comment is a Comment about a Post, a Like on this Comment is automatically a Like on the Post that the comment is about
In other words: if someone created a comment (4) about Post (10), then it is ridiculous to create a Like for comment (4) and Post (20). comment (4) has nothing to do with post (20)!
Every Like is created by exactly one User about exactly one Comment. A user creates zero or more likes (one-to-many), and a comment has been liked zero or more times (also a one to many)
So you have the following sequence of actions:
User 1 creates Post 10: Post 10 has foreign key CreateByUserId 1
User 2 creates comment 20 about Post 10. Comment 20 has CommentedByUserId 2 and PostId 20
User 3 likes comment 20. Like 30 has LikedByUserId 3 and CommentId 20
This is normalized enough for entity framework. To make the relations clearer I changed the foreign keys a bit.
class User
{
public int Id {get; set;}
...
// Every User creates zero or more Posts (one-to-many)
public virtual ICollection<Post> Posts {get; set;}
// Every User creates zero or more Comments (one-to-many)
public virtual ICollection<Comment> Comments {get; set;}
// Every User creates zero or more Likes (one-to-many)
public virtual ICollection<Like> Likes {get; set;}
}
class Post
{
public int Id {get; set;}
...
// Every Post is posted by exactly one User, using foreign key
public int PostedByUserId {get; set;}
public User User {get; set;}
// Every Post has zero or more Comments (one-to-many)
public virtual ICollection<Comment> Comments {get; set;}
}
And Classes Comment and Like:
class Comment
{
public int Id {get; set;}
...
// Every Comment is posted by exactly one User, using foreign key
public int CommentedByUserId {get; set;}
public virtual User User {get; set;}
// Every Comment is about exactly one Post, using foreign key
public int PostId {get; set;}
public virtual Post Post {get; set;}
// Every Comment has zero or more Likes (one-to-many)
public virtual ICollection<Like> Likes {get; set;}
}
class Like
{
public int Id {get; set;}
...
// Every Like is created by exactly one User, using foreign key
public int LikedByUserId {get; set;}
public virtual User User {get; set;}
// Every Like is about exactly one Comment, using foreign key
public int CommentId {get; set;}
public virtual Comment Comment {get; set;}
}
Because my foreign keys deviate from the conventions I need to inform entity framework about these foreign keys using fluent API:
Post has foreign key to User:
modelBuilder.Entity<Post>()
.HasRequired(post => post.User)
.WithMany(user => user.Posts)
.HasForeignKey(post => post.CreatedByUserId);
Comment has foreign keys to User and Post:
var commentEntity = modelBuilder.Entity<Comment>();
commentEntity.HasRequired(comment => comment.User)
.WithMany(user => user.Comments)
.HasForeignKey(comment => comment.CommentedByUserId);
commentEntity.HasRequired(comment => comment.Post)
.WithMany(post => post.Comments)
.HasForeignKey(comment => comment.PostId);
Like has foreign keys to User and Comment:
var likeEntity = modelBuilder.Entity<Like>();
likeEntity.HasRequired(like => like.User)
.WithMany(user => user.Likes)
.HasForeignKey(like => like.LikedByUserId);
likeEntity.HasRequired(like => like.Comment)
.WithMany(comment => comment.Likes)
.HasForeignKey(like => like.CommentId);
If in future you want to give the possibility to for users to Like a Post instead of a Comment, or maybe like a User, the relations will be very similar. Start by giving the User the proper virtual ICollection<...> (every User like zero or more ...), and you will automatically know where to put the foreign keys

Related

EF code first Reference key as primary key

Landed up in a quite ridiculous situation here.
I'm coding in EF core, ASP.NET core, Visual Studio 2017 community.
I have a model which does not have a primary key like:
public class LoginRecord
{
public User User { get; set; }
public int UserId { get; set; }
public DateTime LastLogin { get; set; }
public int LoginCount { get; set; }
}
where User is a table with all the fields like name, email etc.
When I do add-migration, an error pops up saying:
The entity type 'LoginRecord' requires a primary key to be defined.
Then I tried by adding the [Key] annotation to the UserId field like this:
public class LoginRecord
{
public User User { get; set; }
[Key]
public int UserId { get; set; }
public DateTime LastLogin { get; set; }
public int LoginCount { get; set; }
}
but now, EF creates a new column called UserId1 which becomes the foreign key.
I need the UserId to be just a reference key and don't need a primary key for this table.
Is it possible? If yes please help!
You should add an primary key.
I've learned that every table should get a field which is named like the table + id. You could also name it id if you like it more. :)
So that every record gets unique.
So I would advice you to change your model to something like this:
public class LoginRecord
{
[Key]
public int LoginRecordID {get; set;}
public User User { get; set; }
public int UserId { get; set; }
public DateTime LastLogin { get; set; }
public int LoginCount { get; set; }
}
Try:
[ForeignKey("UserId"), Column(Order = 0)]
public int UserId { get; set; }

`Only primitive types or enumeration are supported in this context` error whilst seeding ASP.NET MVC using AddOrUpdate match on multiple fields

My first question on stackoverflow.com! Wish me luck explaining :)
I am trying to seed a development database with some test data, requiring matching on two fields, and encountered the issue solved here How to seed data with AddOrUpdate with a complex key in EF 4.3
I get the Only primitive types or enumeration types are supported in this context. error as mentioned in the comments by lukyer.
However, despite adding foreign key attributes, and required attributes to the properties in the model as he suggested, I can find no obvious way to clear the error.
Any ideas on how to seed the database successfully would be gratefully received.
The important parts of the model are as follows:
public class User {
public string ID { get; set; }
public string Email {get; set; }
}
public class Institution {
public Guid ID { get; set;}
public string Name {get; set; }
}
public class UserInstitutionAssociation {
public Guid ID { get; set; }
public virtual User User { get; set; }
public virtual Institution Institution {get; set;}
}
And then the code to migrate from configuration.cs adds a user (jamesbond#mi5.com) and an institution (Top Secret Establishment). It then attempts to add an association between the user and the establishment, for which it should be added only if an association between these two does not already exist.
//Add User to Database
context.Users.AddOrUpdate(u => u.Email,
new User {
Email = "jamesbond#mi5.com"
});
context.SaveContext();
//Add Institution to Database
context.Institutions.AddOrUpdate(u => u.Name,
new Institution {
Name = "Top Secret Establishment"
});
context.SaveContext();
//Add Association between User and Institution
context.InstitutionUserAssociations.AddOrUpdate(u => new { u.User, u.Institution },
new InstitutionUserAssociation
{
User = context.Users.Single(x => x.Email=="jamesbond#mi5.com"),
Institution = context.Institutions.Single(x => x.Name=="Top Secret Establishment")
}
);
context.SaveContext();
I have tried changing the model in a number of ways as I understand Lukyer to have suggested, including all solutions described here How Should I Declare Foreign Key Relationships Using Code First Entity Framework (4.1) in MVC3?
In short, I have tried all combinations of the following without success:
public class UserInstitutionAssociation {
public Guid ID { get; set; }
[Required]
[ForeignKey("UserID")]
public virtual User User { get; set; }
[Required]
[ForeignKey("InstitutionID")]
public virtual Institution Institution {get; set;}
}
and
public class UserInstitutionAssociation {
public Guid ID { get; set; }
[Required]
[ForeignKey("User")]
public string UserID {get; set; }
public virtual User User { get; set; }
[Required]
[ForeignKey("Institution")]
public Guid InstitutionID { get; set; }
public virtual Institution Institution {get; set;}
}
Solved. Occurred because of the classes that I included as part of the LINQ query. You can't use x => new { x.User, x.Institution} because x.User and x.Institution are both complex classes. Instead, these need to be primitive base types (such as string or Guid). It was changed in this way AddOrUpdate(x => new {x.UserID, x.InstitutionID}, Associations). The model then needed to be changed so it included properties called UserID and InstitutionID that could be queried by LINQ.
I adjusted the UserInstitutionAssociation code as follows so it included properties called UserID and InstitutionID as well as the User and I:
public class UserInstitutionAssociation {
public Guid ID { get; set; }
[Required]
[ForeignKey("User")]
public string UserID {get; set; }
public virtual User User { get; set; }
[Required]
[ForeignKey("Institution")]
public Guid InstitutionID { get; set; }
public virtual Institution Institution {get; set;}
}
Then adjust the AddOrUpdate call as follows:
//Add Association between User and Institution
context.InstitutionUserAssociations.AddOrUpdate(u => new { u.UserID, u.InstitutionID },
new InstitutionUserAssociation
{
User = context.Users.Single(x => x.Email=="jamesbond#mi5.com"),
Institution = context.Institutions.Single(x => x.Name=="Top Secret Establishment")
}
);

Dbset<TEntity>.Add(entity) assigns an ID and this results in a exception

For the following classes :
public Car
{
public int ID { get; set; }
public string Brand {get; set; }
}
Normally when we do :
Car c = new Car { Brand = "Jaguar" } ; // Point A
context.Cars.Add(c); // Point B
context.SaveChanges() // Point C
At point B, the ID should remain 0, and an ID should only be assigned at point C. However, I have found that for one of my classes, an ID is assigned at point B and this results in this exception being thrown :
Cannot insert explicit value for identity column in table
'Cars' when IDENTITY_INSERT is set to OFF.
I have played with Fluent API and I'm 99% sure my relationships are correctly defined. I can't figure out why this DbSet tries to assign an ID for this entity.
Update
Thank you for your help, so here is a more detailed illustration of my situation :
public Car
{
public int ID { get; set; }
public string Brand {get; set; }
public int Driver1ID {get; set;}
public Person Driver1 {get; set;}
public int Driver2ID {get; set;}
public Person Driver2 {get; set;}
}
public Person
{
public int ID { get; set; }
public string Name { get; set; }
}
And here is my fluent configuration :
modelBuilder.Entity<Car>().HasKey(x => x.ID);
modelBuilder.Entity<Car>().Property(x => x.ID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); // Added following Igor's suggestion
modelBuilder.Entity<Car>().HasRequired(x => x.Driver1).WithOptional().WillCascadeOnDelete(false);
modelBuilder.Entity<Car>().HasRequired(x => x.Driver2).WithOptional().WillCascadeOnDelete(false);
Edit 2
Well, I have found that actually Migrations messed up. For some reason EF put the 2nd foreign key (Driver2), on the primary key column. This is why, DbSet.Add() was populating the ID column with a value that was actually the Driver 2 ID.
I really don't know why EF got confused like that. And the weird thing is that I didn't see this FK when I looked in SQL Management Studio. It looks like EF applied some relashionships that were not actually in the DB.
I reset the whole migrations (deleted the migration folder and the _migrationhistory table, then executed Enable-Migrations and Add-Migration Init in PowerShell), and I have been able to see the problematic lines in the initial migration file.
Or course I have modified them and It seems to have solved the problem.
In your fluent (and also declaritivly) mapping you can do specify if the ID is assigned by the database using Identity or if its not. If you specify that it IS assigned your code should not also assign it because you will get an exception. In fluent you can do it like this:
public Car
{
public int ID { get; set; }
public string Brand {get; set; }
public int Driver1ID {get; set;}
public Person Driver1 {get; set;}
public int Driver2ID {get; set;}
public Person Driver2 {get; set;}
}
public Person
{
public int ID { get; set; }
public string Name { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
// ....
modelBuilder.Entity<Car>().HasKey(x => x.ID);
modelBuilder.Entity<Car>().Property(x => x.ID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); // set it on
modelBuilder.Entity<Car>().HasRequired(x => x.Driver1).WithMany().HasForeignKey(x => x.Driver1ID).WillCascadeOnDelete(false);
modelBuilder.Entity<Car>().HasRequired(x => x.Driver2).WithMany().HasForeignKey(x => x.Driver2ID).WillCascadeOnDelete(false);
// ....
}
You can attribute the Id column with
public Car
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Brand {get; set; }
}

Creating many to many junction table in Entity Framework

I am trying to add a many to many relationship between two of my entities. I need a junction table with an additional field, I'm aware that means EF cannot do this automatically and that I need to create an Entity for my junction table.
I have the following models
public class Supplier
{
public int Id { get; set;}
public virtual ICollection<SupplierUsers> UserPermissions { get; set; }
}
And
public class User
{
public string Id { get; set;}
public virtual ICollection<SupplierUsers> UserPermissions { get; set; }
}
I need for a user to have a permission stored in the junction table. So I have created the following entity
public class SupplierUsers
{
public string UserId { get; set; }
public int SupplierId { get; set; }
public SupplierUserPermission Permission { get; set; }
public virtual Supplier Supplier { get; set; }
public virtual User User { get; set; }
}
In my OnModelCreating I've also added the following (this is probably where I'm going wrong)
modelBuilder.Entity<SupplierUsers>()
.HasKey(x => new { x.UserId, x.SupplierId });
This works to an extent, I can successfully add a user/supplier/permission to this table.
But I cannot add the same user / supplier multiple times to this table (probably due to the PK?).
How can I alter my code so that I can add the same user or supplier multiple times in this table?
Here's what the table structure looks like at the moment:
Thank you.
If i understand you correctly you want to add multiple equal pairs of UserId and SupplierId to SupplierUsers, right?
Add a SupplierUsersId field to your SupplierUsers entity and make it primary key.
public class SupplierUsers
{
public int SupplierUsersId { get;set; }
public string UserId { get; set; }
public int SupplierId { get; set; }
public SupplierUserPermission Permission { get; set; }
public virtual Supplier Supplier { get; set; }
public virtual User User { get; set; }
}
Remove the configuration from OnModelCreating()

Entity Framework Cascade Delete with Multiple Column Key

I have these models set up:
public class Advisor
{
public virtual int AdvisorId { get; set; }
public virtual int UserId { get; set; }
public User User { get; set; }
public ICollection<AdvisorStudentMap> AdvisorStudentMaps { get; set; }
}
public class AdvisorStudentMap
{
[Required]
public virtual int AdvisorId { get; set; }
[Required]
public virtual int UserId { get; set; }
}
public class User
{
public virtual int UserId { get; set; }
public virtual string UserName { get; set; }
public virtual string FirstName { get; set; }
public ICollection<AdvisorStudentMap> AdvisorStudentMaps { get; set; }
}
In my OnModelCreating I have:
modelBuilder.Entity<AdvisorStudentMap>()
.HasKey(t => new {t.AdvisorId, t.UserId});
In my fluent api how do I set it up so that when I delete an advisor, it deletes the AdvisorStudentMap as well? I keep getting the error: Message=Introducing FOREIGN KEY constraint 'FK_dbo.AdvisorStudentMaps_dbo.Users_UserId' on table 'AdvisorStudentMaps' 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.
Update - also in OnModelCreating I have:
modelBuilder.Entity<Advisor>()
.HasRequired(t => t.AdvisorStudentMaps).WithMany().WillCascadeOnDelete(true);
With that I get the error 'Cascading foreign key 'FK_dbo.Advisors_dbo.AdvisorStudentMaps_AdvisorId_UserId' cannot be created where the referencing column 'Advisors.AdvisorId' is an identity column.
You appear to be trying to model a many-to-many relationship between Students and Advisors. This would be the way you'd normally do that:
public class Advisor
{
//Key fields don't need to be marked virtual
public int AdvisorId { get; set; }
//If you want the property to lazy load then you should mark it virtual
public virtual ICollection<Student> Students{ get; set; }
//Advisors have a UserProfile
public int UserProfileId{get;set;}
public virtual UserProfile UserProfile {get; set;}
}
public class Student
{
public int StudentId { get; set; }
public virtual ICollection<Advisor> Advisors { get; set; }
//Students also have a UserProfile
public int UserProfileId{get;set;}
public virtual UserProfile UserProfile {get; set;}
}
public class UserProfile
{
public int UserProfileId{get;set;}
//NB not marked virtual - that is only needed on navigation properties when
//we want to use lazy loading
public string UserName {get;set}
public string FirstName {get;set}
}
Entity framework will automatically create the join table to model the relationship. You don't need the AdvisorStudentMap entity unless you need to add attributes to the relationship.
As for the cascade on delete problem. If you delete a User then this can cascade to the Student table and the Advisor table. There is a cascade path from Student to StudentAdvisorMap and another from Advisor to StudentAdvisorMap. Hence multiple cascade paths. Sql Server does not allow this. You will have to explicitly implement the deletes in your code to avoid this
I figured this out with help from this link: http://blog.cdeutsch.com/2011/09/entity-framework-code-first-error-could.html
As he says on the blog, it isn't very intuitive but here is the syntax that made it work:
modelBuilder.Entity<AdvisorStudentMap>()
.HasRequired(u=>u.User)
.WithMany(m=>m.AdvisorStudentMaps)
.WillCascadeOnDelete(false);

Categories