ASP.Net MVC EF Map To Junction Table With Three Primary Keys - c#

I am having the hardest time figuring this out. I am using MVC 4 and Code First Entity Framework. I have three classes. They are Users, Roles, and Project. A user can have many projects and for a particular project, a user can have a particular role. I am using a junction table to combine the three tables. Here is an example of the Junction Table:
public class UserRolesinProject
{
[Key, Column(Order = 0)]
public int UserId { get; set;}
[Key, Column(Order = 1)]
public int RoleId { get; set;}
[Key, Column(Order = 2)]
public int ProjectId { get; set;}
}
Here are samples of each of the classes
public class DBUser
{
[Key]
public int UserId { get; set;}
public string UserName { get; set;}
public virtual ICollection< UserRolesinProject > Roles { get; set; }
}
public class DBRole
{
[Key]
public int RoleId { get; set;}
public string RoleName { get; set;}
public virtual ICollection< UserRolesinProject > Projects { get; set; }
}
public class Project
{
[Key]
public int ProjectId { get; set;}
public string ProjectName { get; set;}
public virtual ICollection< UserRolesinProject > Users { get; set; }
}
Lastly a sample of the context class
Public DBSet<DBUser>Users {get; set;}
Public DBSet<DBRoles>Roles {get; set;}
Public DBSet<Project>Projects {get; set;}
Public DBSet<UserRolesInProject>UserRolesProjects {get; set;}
So at the moment this builds and the tables are created when I use the package manager console to update the database. What I wanted to achieve was if I do a LINQ statement from the User I would like to retrieve all the projects that the User is assigned to and the respective role. Right now, I’m just getting the ids of each field, which is from the junction table and the mapping that I did in each individual class to the junction table. I thought the result would be they all would be joined. What am I doing wrong? I searched and viewed different examples, but they were all for two PK columns not three. Are my tables wrong? Did I set them up in an inefficient way? Thanks in advance for the help.

Related

EF Core multiple realationship of a single column

I am using EF Core 3.1 and I have five Models: Plant, Area, Unit, Schema, and EntitiesSchema.
In the EnititiesSchema, the EntityId may be a foreign key of Plant(PlantId), Area(AreaId), Unit(UnitId) tables.
How to handle this optional Relationship between these tables?
Thanks
public class EntitiesSchema
{
public int Id { get; set; }
public int EntityId { get; set; }
public int TopicId { get; set; }
public int SchemaId { get; set; }
public string Description { get; set; }
public Schema Schema { get; set; }
public ICollection<Topic> Topic { get; set; }
}
No, you can't relate a foreign key to multiple tables. But you can put another property named EntityType to store the type of entity. Then on the client-side, you can handle it. The EntityType can be an enum type.
Another approach is that storing "EntitesSchemaId" in the Plant, Area, Unit, etc models and relate them to the EntitiesSchema.
You can create an intermediary entity to map to different entity types. :
Public class EntityMap
{
public int Id {get;set;}
public string EntityKind {get;set;} // could be "Plant", "Area", "Unit", "Schema"
}
public class Plant
{
public int Id {get;set;}
public string EntityKind {get;set;} = "Plant";
}
public class EntitySchema
{
public int Id {get;set;}
public int EntityMapId {get;set;}
public EntityMap Map {get;set;}
}
The logic to read data from individual schema, has to be implemented in the client,but common properties of the entities can be added in EntityMap.
Here's a similar answer you might want to reference : https://stackoverflow.com/a/53649452/7491048

Problem with relations beetwen entity models

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

How to return this result using linq in entity framework( 2 foreign key mapping table)

I have a table that contains 2 foreign key that reference separately to 2 different table.
I would like to return the result of all person that has course of "Science".
How to retrieve the record back using LINQ?
This is what i gotten so far:
return
_ctx.Person
.Include(u => u.Course
.Where(ug=>ug.CourseName== "Science"));
This is not working as it shows the error.
The Include path expression must refer to a navigation property
defined on the type
public class Course
{
public int CourseID {get; set;}
public string CourseName {get; set;}
public virtual ICollection<Person> Persons { get; set; }
}
public class Person
{
public int PersonID {get; set;}
public virtual ICollection<Course> Courses { get; set; }
}
This is the mapping table. Only contains 2 foreign key from 2 different table.
I could not use this table inside the solution.As the code first won't generate this table as it doesn't contain it's own PK.
//This is not shown in the EntityFramework when generating Code First.
public class PersonCouseMap
{
public int PersonID {get; set;}
public int CourseID {get; set;}
}
Update : this works after I switched the entity.
return _ctx.Course
.Include(u=>u.Person)
.Where(ug=>ug.CourseName == "Sciene");
Anyone can explain why it won't work the another way round.
I need to display a List of Person who have course of "Science",
not Course Science that has a list of user.
The original query does not work because you've pushed the Where predicate inside the Include expression, which is not supported as indicated by the exception message.
The Include method is EF specific extension method used to eager load related data. It has nothing to do with the query filtering.
To apply the desired filter person that has course of "Science" you need Any based predicate since the Person.Courses is a collection:
return _ctx.Person
.Where(p => p.Courses.Any(c => c.CourseName == "Science"));
To include the related data in the result, combine it with Include call(s):
return _ctx.Person
.Include(p => p.Courses)
.Where(p => p.Courses.Any(c => c.CourseName == "Science"));
It looks like there is no relations between these two entites, you can establish a relationship by making the following changes to your code:
Here I am assuming that you want to establish Many-to-Many relationship between these two tables by having a third entity PersonCourseMap
public class Course
{
public int CourseID {get; set;}
public string CourseName {get; set;}
public virtual ICollection<CoursePersons> Courses { get; set; }
}
public class Person
{
public int PersonID {get; set;}
public virtual ICollection<PersonCourse> Courses { get; set; }
}
public class PersonCourseMap
{
public int PersonID {get; set;}
public int CourseID {get; set;}
public virtual ICollection<Person> Persons { get; set; }
public virtual ICollection<Course> Courses { get; set; }
}
After making above changes you can simply navigate through properties.
Include Foreign Key Mapping
public class Course
{
public int CourseID {get; set;}
public string CourseName {get; set;}
public virtual ICollection<Person> Person {get; set}
}
public class Person
{
public int PersonID {get; set;}
public virtual ICollection<Course> Course {get; set;}
}
using System.ComponentModel.DataAnnotation.Schema;
public class PersonCouseMap
{
[ForeignKey("Person")]
public int PersonID {get; set;}
[ForeignKey("Course")]
public int CourseID {get; set;}
public virtual ICollection<Person> Person {get; set;}
public virtual ICollection<Course> Course {get; set;}
}

Bridge table that join multiple tables

I've a Product table and a Feature table that are joined together using a bridge table, ProductFeature. Below are simplified version of these three tables. These works fine. No problem here.
[Table("Products")]
public partial class ProductEntity
{
public int Id { get; set; }
public string Code { get; set; }
public ICollection<ProductFeatureEntity> productFeatures {get; set;}
}
[Table("Features")]
public partial class FeatureEntity
{
public int Id { get; set; }
public string Code { get; set; }
public ICollection<ProductFeatureEntity> productFeatures {get; set;}
}
[Table("ProductFeatures")]
public partial class ProductFeatureEntity
{
[Key, Column(Order = 1)]
public int ProductId { get; set; }
[Key, Column(Order = 2)]
public int FeatureId { get; set; }
public int SequenceNbr {get; set;}
public ProductEntity Product {get; set;}
public FeatureEntity Feature {get; set;}
}
But now, I need to make that same bridge table to also be able to join the Product table itself. Another word, I need to change from "Product can have multiple Features" to "Product can have multiple features and can also have multiple sub-product". I need to use the same bridge table because I need to know the sequence, which is controlled by SequenceNbr field, of each features and sub-products. Is it possible to do this in EF?
you can add ParentId to ProductEntity
and use this in Configuration Class:
HasMany(x => x.ProductEntity).WithOptional()
.HasForeignKey(x => x.ParentId);
Or
Use this in OnModelCreating method in DbContext:
modelBuilder.Entity<ProductEntity>()
.HasOptional().WithMany().HasForeignKey(x=>x.ParentId)

Code first : Sequence contains more than one matching element - multiple classes with multiple references

I'm getting the "Sequence contains more than one matching element" error message while attempting to run the "Add-Migration" command. I believe that the issue is stemming from the fact that I have multiple classes that have multiple references to a shared class.
class User {
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid UserId {get; set;}
public string Name {get; set;}
// other stuff removed for display purposes
[InverseProperty("CreateUser")]
public virtual ICollection<Foo> FooCreateUser { get; set; }
[InverseProperty("ModifyUser")]
public virtual ICollection<Foo> FooModifyUser { get; set; }
[InverseProperty("CreateUser")]
public virtual ICollection<Bar> BarCreateUser { get; set; }
[InverseProperty("ModifyUser")]
public virtual ICollection<Bar> BarModifyUser { get; set; }
}
class Foo {
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid FooId {get; set;}
[InverseProperty("FooCreateUser")]
public User CreateUser {get; set;}
[InverseProperty("FooModifyUser")]
public User ModifyUser {get; set;}
}
class Bar {
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid BarId {get; set;}
[InverseProperty("BarCreateUser")]
public User CreateUser {get; set;}
[InverseProperty("BarModifyUser")]
public User ModifyUser {get; set;}
}
What is wrong with the above code?
Do I have to change the field names in the [Bar] and [Foo] database tables to be unique?
i.e. change [Foo][CreateUser] to [Foo][Foo_CreateUser]?
I need to have a "CreateUser" and "ModifyUser" field on multiple database tables and it doesn't make any sense (from a database design perspective) to have all of those fields uniquely named.
How do I tell CodeFirst to do that?

Categories