Here is the simplified version of my problem. Think of a survey application:
There are questions,
A question have many options, an option can only belong to a question
A question should have one category, a category can belong to many questions
A question may have many tags, a tag can belong to many questions
Besides, I would like to define dependencies between a questions and options of other questions such that if a user has voted 'No' option for 'Do you have a car?' question, he/she will not be asked for 'What is the brand of your car?'.
Both auto generated database schema created by Code First and auto generated code created by database first are not satisfying. Below are codes and db schemas generated for both methods.
EF can handle Question - Tag relationship as expected but it cannot handle Dependency relationship between Question and Option (as I understood) because Question already has its own options.
In terms of EF, what would be the ideal code/db design for this case?
Code First - Code
public class Question
{
public long Id { get; set; }
public string Text { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
public virtual ICollection<Option> Options { get; set; }
public virtual ICollection<Option> DependencyOptions { get; set; }
[ForeignKey("CategoryId")]
public virtual Category Category { get; set; }
public long CategoryId { get; set; }
}
public class Option
{
public long Id { get; set; }
public string Text { get; set; }
public virtual ICollection<Question> DependencyQuestions { get; set; }
[ForeignKey("QuestionId")]
public virtual Question Question { get; set; }
public long QuestionId { get; set; }
}
public class Tag
{
public long Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Question> Questions { get; set; }
}
public class Category
{
public long Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Question> Questions { get; set; }
}
Code First - Tables
Db First - Tables
Db First - Code
public class Question
{
public long Id { get; set; }
public string Text { get; set; }
public long CategoryId { get; set; }
public virtual Category Category { get; set; }
public virtual ICollection<Option> Options { get; set; }
public virtual ICollection<Option> Options1 { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}
public class Option
{
public long Id { get; set; }
public string Text { get; set; }
public long QuestionId { get; set; }
public virtual Question Question { get; set; }
public virtual ICollection<Question> Questions { get; set; }
}
public class Tag
{
public long Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Question> Questions { get; set; }
}
public class Category
{
public long Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Question> Questions { get; set; }
}
My understanding is you want a subsequent question to be dependent on the answer to a prior question...
My suggestion would be to to have a relationship table between questions and options called something like RequiredAnswer. This relationship will signify that a user must have answered a question or questions with one or all (up to you for implementation) of the related options (your diagrams don't include an answers table). Then it's up to the code implementation on how you want to use this relationship.
I'm not sure if I understood it right but I think you're going to need (and already have) a many-to-many on Option/Question - plus, you also have a one-to-many (owner) relation in between them.
I don't know enough to discuss whether that is the best solution - but taking your word for having both the 'dependency' relationship (and the other one)...
...for that you'd need to 'fine tune' your relationships and your index tables. EF/CF creates the default ones in the background, what you need I think is to create the relations in fluent configuration - and add extra columns yourself.
And I would generally recommend doing your own configuration in the
code - vs attributes / default one - as in any complex scenarios that
gives you more options and control over it, less errors etc. In my
case, I just like to know which tables and columns are created for me.
And here is a specific sample code (I removed other tables unnecessary, just Q/O)...
public class QuestionContext : DbContext
{
public DbSet<Question> Questions { get; set; }
public DbSet<Option> Options { get; set; }
public DbSet<QuestionOption> QuestionOptions { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<QuestionOption>()
.HasKey(i => new { i.OptionID, i.QuestionID });
modelBuilder.Entity<QuestionOption>()
.HasRequired(i => i.Opiton)
.WithMany(u => u.DependencyOptions)
.HasForeignKey(i => i.OptionID)
.WillCascadeOnDelete(false);
modelBuilder.Entity<QuestionOption>()
.HasRequired(i => i.Question)
.WithMany(u => u.DependencyOptions)
.HasForeignKey(i => i.QuestionID)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Option>()
.HasRequired(i => i.Question)
.WithMany(u => u.Options)
.HasForeignKey(i => i.QuestionId)
.WillCascadeOnDelete(false);
}
}
public class Question
{
public long Id { get; set; }
public string Text { get; set; }
public virtual ICollection<Option> Options { get; set; }
public virtual ICollection<QuestionOption> DependencyOptions { get; set; }
}
public class Option
{
public long Id { get; set; }
public string Text { get; set; }
// [ForeignKey("QuestionId")]
public virtual Question Question { get; set; }
public long QuestionId { get; set; }
public virtual ICollection<QuestionOption> DependencyOptions { get; set; }
}
public class QuestionOption
{
public long OptionID { get; set; }
public Option Opiton { get; set; }
public long QuestionID { get; set; }
public Question Question { get; set; }
public int DependencyType { get; set; }
public string DependencyNote { get; set; }
public bool Active { get; set; }
public bool UseEtc { get; set; }
}
With the following migration...
CreateTable(
"dbo.Questions",
c => new
{
Id = c.Long(nullable: false, identity: true),
Text = c.String(maxLength: 4000),
})
.PrimaryKey(t => t.Id);
CreateTable(
"dbo.Options",
c => new
{
Id = c.Long(nullable: false, identity: true),
Text = c.String(maxLength: 4000),
QuestionId = c.Long(nullable: false),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.Questions", t => t.QuestionId)
.Index(t => t.QuestionId);
CreateTable(
"dbo.QuestionOptions",
c => new
{
OptionID = c.Long(nullable: false),
QuestionID = c.Long(nullable: false),
DependencyType = c.Int(nullable: false),
DependencyNote = c.String(maxLength: 4000),
Active = c.Boolean(nullable: false),
UseEtc = c.Boolean(nullable: false),
})
.PrimaryKey(t => new { t.OptionID, t.QuestionID })
.ForeignKey("dbo.Options", t => t.OptionID)
.ForeignKey("dbo.Questions", t => t.QuestionID)
.Index(t => t.OptionID)
.Index(t => t.QuestionID);
You have two separate relationships defined (one one-to-many and other m-to-m), side by side.
Within QuestionOption table you can now manually specify all you need to add for your dependency (that's your DependencyOption - I just thought this naming clarifies it more). So you'd have something like Question(A) -> allows Option(B) - but given your logic you may need to add some more.
It looks like you'd need to establish relationships in between Question, Question, Option - so 3 indexes etc. Given the code above you can do that with just a simple extension if needed.
modelBuilder.Entity<QuestionOption>()
.HasKey(i => new { i.OptionID, i.QuestionLeftID, i.QuestionRightID });
modelBuilder.Entity<QuestionOption>()
.HasRequired(i => i.Opiton)
.WithMany(u => u.DependencyOptions)
.HasForeignKey(i => i.OptionID)
.WillCascadeOnDelete(false);
modelBuilder.Entity<QuestionOption>()
.HasRequired(i => i.QuestionLeft)
.WithMany(u => u.DependencyOptionsLeft)
.HasForeignKey(i => i.QuestionLeftID)
.WillCascadeOnDelete(false);
modelBuilder.Entity<QuestionOption>()
.HasRequired(i => i.QuestionRight)
.WithMany(u => u.DependencyOptionsRight)
.HasForeignKey(i => i.QuestionRightID)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Option>()
.HasRequired(i => i.Question)
.WithMany(u => u.Options)
.HasForeignKey(i => i.QuestionId)
.WillCascadeOnDelete(false);
public class Question
{
public long Id { get; set; }
public string Text { get; set; }
public virtual ICollection<Option> Options { get; set; }
public virtual ICollection<QuestionOption> DependencyOptionsLeft { get; set; }
public virtual ICollection<QuestionOption> DependencyOptionsRight { get; set; }
}
public class QuestionOption
{
public long QuestionLeftID { get; set; }
public Question QuestionLeft { get; set; }
public long QuestionRightID { get; set; }
public Question QuestionRight { get; set; }
public long OptionID { get; set; }
public Option Opiton { get; set; }
public int DependencyType { get; set; }
public string DependencyNote { get; set; }
public bool Active { get; set; }
public bool AllowForbid { get; set; }
}
For more complex scenarios (with many-to-many and manually defining relations in fluent code - which I recommend) - take a look at these detailed examples I made a while ago - it has most of the mappings you may need.
Many to many (join table) relationship with the same entity with codefirst or fluent API?
Code First Fluent API and Navigation Properties in a Join Table
EF code-first many-to-many with additional data
...that's if you need any fast pointers - let me know with questions - and some more details
Related
1) I am trying to establish a relationship between two classes. So, I have the following class
public class Team
{
[Key]
public int Id { get; set; }
public string Team { get; set; }
public List<MatchGame> MatchGames { get; set; }
}
and
public class MatchGame
{
[Key]
public int Id { get; set; }
public int HomeTeamId { get; set; }
public Team HomeTeam { get; set; }
public int AwayTeamId { get; set; }
public Team AwayTeam { get; set; }
}
The configuration that I tried to perform the relashioship is
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<MatchGame>()
.HasOne(h => h.HomeTeam)
.WithMany(m => m.MatchGames)
.HasForeignKey(k => k.HomeTeamId)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<MatchGame>()
.HasOne(h => h.AwayTeam)
.WithMany(m => m.MatchGames)
.HasForeignKey(k => k.AwayTeamId)
.OnDelete(DeleteBehavior.NoAction);
}
the produced error is:
Cannot create a relationship between 'Team.MatchGames' and
'MatchGame.AwayTeam' because a relationship already exists between
'Team.MatchGames' and 'MatchGame.HomeTeam'. Navigation properties can
only participate in a single relationship. If you want to override an
existing relationship call 'Ignore' on the navigation
'MatchGame.AwayTeam' first in 'OnModelCreating'.
I have also seen the following threads but couldn't find the reason why the relationship couldn't establish.
EF code first: one-to-many twice to same collection type
https://learn.microsoft.com/en-us/ef/ef6/modeling/code-first/fluent/relationships
EFCore - How to have multiple navigation properties to the same type?
2) Also, in order not to create a new post: I wanted to have the public string Team { get; set; } in the Team Class to be Unique. I tried some DataAnnotation that I have seen but didn't work. what do I have to use for this purpose.
The problem is you try to use one navigation property MatchGames for defining relationship. Try create two separate navigation properties like below.
public class Team
{
public int Id { get; set; }
public string TeamName { get; set; }
public List<Match> HomeMatches { get; set; }
public List<Match> AwayMatches { get; set; }
}
public class MatchGame
{
public int Id { get; set; }
public int HomeTeamId { get; set; }
public Team HomeTeam { get; set; }
public int AwayTeamId { get; set; }
public Team AwayTeam { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<MatchGame>(entity =>
{
entity.HasOne(m => m.HomeTeam)
.WithMany(t => t.HomeMatches)
.HasForeignKey(m => m.HomeTeamId)
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
entity.HasOne(m => m.AwayTeam)
.WithMany(t => t.AwayMatches)
.HasForeignKey(m => m.AwayTeamId)
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
});
}
Ad 2. You need to create unique index on TeamName:
modelBuilder.Entity<Team>(e => e.HasIndex(t => t.TeamName).IsUnique());
Also you can consider to add MaxLength attributes in your model and so on.
I have the following entities
public class Course
{
public long Id { get; set; }
public virtual ICollection<User> Users{ get; set; }
public virtual ICollection<UserCourse> CourseUsers { get; set; }
}
public class User
{
public long Id { get; set; }
public virtual ICollection<Course> Courses { get; set; }
public virtual ICollection<UserCourse> UserCourses { get; set; }
}
public class UserCourse
{
public long UserId { get; set; }
public User User { get; set; }
public long CourseId { get; set; }
public Course Course { get; set; }
public bool IsRequired { get; set; }
}
with the following mappings for
UserCourse mapping :
builder
.HasOne(nav => nav.User)
.WithMany(self => self.UserCourses)
.HasForeignKey(fk => fk.UserId)
.OnDelete(DeleteBehavior.Cascade);
builder
.HasOne(nav => nav.Course)
.WithMany(self => self.CourseUsers)
.HasForeignKey(fk => fk.CourseId)
.OnDelete(DeleteBehavior.Cascade);
and the User mapping
builder
.HasMany(nav => nav.Courses)
.WithMany(nav => nav.Users);
When trying to create a new migration I'm not exactly sure why I'm getting this.
Cannot use table 'UserCourse' for entity type 'UserCourse' since it is
being used for entity type 'UserCourse(Dictionary<string, object>)'
and potentially other entity types, but there is no linking
relationship. Add a foreign key to 'UserCourse' on the primary key
properties and pointing to the primary key on another entity typed
mapped to 'UserCourse'.
I understand what the error is, but not sure how to force the UserCourse mapping to use the User mapping generated join table or vice-versa
Also, I need the direcat mapping for OData, and the indirect mapping using the join entity to conduct operations on DbSet<UserCourse>
The public virtual ICollection<User> Users{ get; set; } in Course entity and the the public virtual ICollection<Course> Courses { get; set; } in Users entity are redundant. The entities should look more like this
public class Course
{
public long Id { get; set; }
public virtual ICollection<UserCourse> UserCourses { get; set; }
}
public class User
{
public long Id { get; set; }
public virtual ICollection<UserCourse> UserCourses { get; set; }
}
public class UserCourse
{
public long UserId { get; set; }
public User User { get; set; }
public long CourseId { get; set; }
public Course Course { get; set; }
}
And the OnModelCreating method should have this code
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<UserCourse>()
.HasKey(uc => new { uc.UserId, uc.CourseId });
modelBuilder.Entity<UserCourse>()
.HasOne(uc => uc.Course)
.WithMany(c => c.Users)
.HasForeignKey(uc => uc.CourseId);
modelBuilder.Entity<UserCourse>()
.HasOne(uc => uc.User)
.WithMany(c => c.Courses)
.HasForeignKey(uc => uc.UserId);
}
If you use EF core 5 you can directly skip the join table. It will be generated and handled by EF behind the scenes. More on the topic here https://www.thereformedprogrammer.net/updating-many-to-many-relationships-in-ef-core-5-and-above/
I know there are some answered questions on SO about 1:0..1-relationships. I have looked at this and this, but don't think they apply to my question.
I have these three (simplified) models in a CMS-system.
public class FrontPageItem
{
public int Id { get; set; }
public int ItemType { get; set; } // 1 = Article, 2 = WebPage, etc...
public int? ArticleId { get; set; }
public Article Article { get; set; }
public int? WebPageId { get; set; }
public WebPage WebPage { get; set; }
}
public class Article
{
public int Id { get; set; }
public string Title { get; set; }
public string Preamble { get; set; }
public string MainText { get; set; }
public int? FrontPageItemId { get; set; }
public FrontPageItem FrontPageItem { get; set; }
}
public class WebPage
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int? FrontPageItemId { get; set; }
public FrontPageItem FrontPageItem { get; set; }
}
The relationships between a FrontPageItem and each of the different element types are one-to-zero-or-one. An element can exist without being added as a FrontPageItem, meaning that a FrontPageItem has a relationship to just one element, either an Article or a WebPage.
In an attempt to configure the relationships, I have added this bit of code:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Article>()
.HasOne(p => p.FrontPageItem)
.WithOne(i => i.Article)
.HasForeignKey<FrontPageItem>(b => b.Id);
modelBuilder.Entity<WebPage>()
.HasOne(p => p.FrontPageItem)
.WithOne(i => i.WebPage)
.HasForeignKey<FrontPageItem>(b => b.Id);
}
But I don't think it's correct. I haven't made the CRUD-views for FrontPageItem yet, but if I try to add items directly in the SQL Server Object Explorer in VS, I have to enter a value for the PK.
What am I doing wrong?
As you said:
The relationships between a FrontPageItem and each of the different element types are one-to-zero-or-one. An element can exist without being added as a FrontPageItem, meaning that a FrontPageItem has a relationship to just one element, either an Article or a WebPage.
Then remove ArticleId and WebPageId from FrontPageItem as EF core support one-to-one-or-zero association without foreign key in principle table:
public class FrontPageItem
{
public int Id { get; set; }
public int ItemType { get; set; } // 1 = Article, 2 = WebPage, etc...
public Article Article { get; set; }
public WebPage WebPage { get; set; }
}
Then the Fleunt API configuration for Article and WebPage as follows:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Article>()
.HasOne(a => a.FrontPageItem)
.WithOne(f => f.Article)
.HasForeignKey<Article>(a => a.FrontPageItemId); // <-- Here it is
modelBuilder.Entity<WebPage>()
.HasOne(wp => wp.FrontPageItem)
.WithOne(f => f.WebPage)
.HasForeignKey<WebPage>(wp => wp.FrontPageItemId); // <--Here it is
}
I have entity Program that has two foreign keys to my subject table (MainContactSubjectId, SecondaryContactSubjectId). Both main and secondary are nullable longs. For some reason, when I try to insert entity Program it errors (Internal Server Error) and will not let me insert unless Main and Secondary are present. Below is my entity Program and some of my dbContext. Can anyone see what I am doing wrong?
[Table("Program")]
public class Program : Entity<long>
{
[Required]
public int TenantId { get; set; }
[Required]
public long ProgramTypeId { get; set; }
[Required]
[MaxLength(4000)]
public string ProgramName { get; set; }
public long? MainContactSubjectId { get; set; }
public long? SecondaryContactSubjectId { get; set; }
public virtual ICollection<AppTables.Case_ProgramRequirements.Case_ProgramRequirement> Case_ProgramRequirement { get; set; }
public virtual AppTables.ProgramTypes.ProgramType ProgramType { get; set; }
public virtual AppTables.Subjects.Subject MainSubject { get; set; }
public virtual AppTables.Subjects.Subject SecondarySubject { get; set; }
}
I'm guessing the problem is here but I'm not sure what it is. My best
guess is the .HasRequired but I'm not sure how to rewrite it.
Without this code, the foreign keys are not getting created correctly
and circular reference issues. The WillCascadeOnDelete(false) stops the
circular reference issue.
public virtual IDbSet<AppTables.Programs.Program> Programs { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<AppTables.Programs.Program>()
.HasRequired(m => m.MainSubject)
.WithMany(t => t.ProgramsMain)
.HasForeignKey(m => m.MainContactSubjectId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<AppTables.Programs.Program>()
.HasRequired(m => m.SecondarySubject)
.WithMany(t => t.ProgramsSecondary)
.HasForeignKey(m => m.SecondaryContactSubjectId)
.WillCascadeOnDelete(false);
}
Edit 1:
I am fairly confident my problem is in the data access layer but if not, I am making an ajax call directly to my Application Service layer "Create". As I step through it everything looks perfect. It hits the return and then I get Internal Server Error. No other details. I tried wrapping in try/catch and catch is never hit.
I am using Asp.net Boilerplate framework. I have posted in their private forum too with no luck, yet. I just assumed it was a data access layer issue which is why I posted here.
public async Task<> Create(ProgramInput model)
{
Program domainModel = new Program();
domainModel.TenantId = (int)AbpSession.TenantId;
domainModel.ProgramName = model.ProgramName;
domainModel.ProgramTypeId = model.ProgramTypeId;
domainModel.MainContactSubjectId = model.MainContactId;
domainModel.SecondaryContactSubjectId = model.SecondaryContactId;
domainModel.CreatedBy = (long)AbpSession.UserId.Value;
domainModel.CreatedDate = Clock.Now;
domainModel.IsDeleted = false;
await _programRepository.InsertAsync(domainModel);
return;
}
Edit 2:
Here is my Subject Table. It has 50 some columns and 10 foreign keys but I will shorten with just the pertinent data.
public class Subject : Entity<long>
{
public Subject()
{
this.ProgramsMain = new HashSet<AppTables.Programs.Program>();
this.ProgramsSecondary = new HashSet<AppTables.Programs.Program>();
}
[Required]
public int TenantId { get; set; }
[Required]
public long SubjectTypeId { get; set; }
[MaxLength(1000)]
public string FirstName { get; set; }
[MaxLength(1000)]
public string MiddleName { get; set; }
[MaxLength(1000)]
public string LastName { get; set; }
[MaxLength(100)]
public string Suffix { get; set; }
[MaxLength(3000)]
public string FullName { get; set; }
//A TON MORE COLUMNS ....
public virtual AppTables.SubjectTypes.SubjectType SubjectType { get; set; }
public virtual ICollection<AppTables.Programs.Program> ProgramsMain { get; set; }
public virtual ICollection<AppTables.Programs.Program> ProgramsSecondary { get; set; }
ANSWER
I figured out my answer. I switched .HasRequired to .HasOptional and everything now works. Sorry to have wasted everyone's time. Thanks!
From the OP's answer in his question edit and comment:
Change .HasRequired to .HasOptional as per the code below.
public virtual IDbSet<AppTables.Programs.Program> Programs { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<AppTables.Programs.Program>()
.HasOptional(m => m.MainSubject)
.WithMany(t => t.ProgramsMain)
.HasForeignKey(m => m.MainContactSubjectId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<AppTables.Programs.Program>()
.HasOptional(m => m.SecondarySubject)
.WithMany(t => t.ProgramsSecondary)
.HasForeignKey(m => m.SecondaryContactSubjectId)
.WillCascadeOnDelete(false);
}
I have a Question entity in my web application. A user can like and bookmark a question. Similar to the way Likes function in Facebook, if a question is liked or bookmarked, the question should have undo-like or undo-bookmark links instead of like and bookmark links, when the existing questions are listed.
To achieve this, first I think I will need to define List<Question> LikedQuestions property for User entity, and List<User> LikedBy property for Question entity, and define Many-to-Many relationship between User and Question entity.
Then, it will be easy to retrieve the liked questions per user. However, my goal is the retrieve the all questions posted in the system, and include undo-like (or undo-bookmark) links if the question is liked by the current user. I guess I can achieve this by using LinQ to check if each question is in the List<Question> LikedQuestions. But, this seems to be a poor design and may lead to very low performances.
I also thought about adding properties such as IsLikedByCurrentUser IsBookmarkedByCurrentUser to Question entity. Is this doable?
What would be the best approach to achieve this?
UPDATE
Here is my current model:
public abstract class Like
{
[Key]
public int LikeItemId { get; set; }
[Required]
public DateTime WhenLiked { get; set; }
[ForeignKey("WhoLiked")]
public string UserId { get; set; }
public Person WhoLiked { get; set; }
}
public class PostLike : Like
{
[Required]
public Post Post { get; set; }
[ForeignKey("Post")]
public int PostId { get; set; }
}
public class CommentLike : Like
{
[ForeignKey("Comment")]
public int CommentId { get; set; }
[Required]
public Comment Comment { get; set; }
}
public class PostBookmark
{
public int PostBookmarkId { get; set; }
[Required]
public string BookmarkedBy { get; set; }
[ForeignKey("BookmarkedBy")]
public Person Person { get; set; }
[Required]
public int BookmarkedPostId { get; set; }
[ForeignKey("BookmarkedPostId")]
public Post BookmarkedPost { get; set; }
[Required]
public DateTime WhenBookmared { get; set; }
}
public class Post
{
public int PostId { get; set; }
[Required]
public string Title { get; set; }
public List<Comment> CommentsAssociated { get; set; }
public List<PostLike> PostLikes { get; set; }
public List<PostBookmark> PostBookmarks { get; set; }
}
public class Comment
{
public int CommentId { get; set; }
[Required]
public string CommentText { get; set; }
[Required]
public Post PostAssociated { get; set; }
public List<CommentLike> CommentLikes { get; set; }
}
Here is the fluent API to define the relationships:
modelBuilder.Entity<Like>()
.HasRequired(l => l.WhoLiked)
.WithMany(p => p.Likes)
.HasForeignKey(l => l.UserId);
modelBuilder.Entity<CommentLike>()
.HasRequired(cl => cl.Comment)
.WithMany(cm => cm.CommentLikes)
.HasForeignKey(cl => cl.CommentId);
modelBuilder.Entity<PostLike>()
.HasRequired(pl => pl.Post)
.WithMany(p => p.PostLikes)
.HasForeignKey(pl => pl.PostId);
modelBuilder.Entity<PostBookmark>()
.HasRequired(pb => pb.Person)
.WithMany(p => p.PostBookmarks)
.HasForeignKey(pb => pb.BookmarkedBy);
modelBuilder.Entity<PostBookmark>()
.HasRequired(pb => pb.BookmarkedPost)
.WithMany(p => p.PostBookmarks)
.HasForeignKey(pb => pb.BookmarkedPostId);
how about:
//Question Model:
[NotMapped]
public bool IsLikedByCurrentUser {get;set;}
The NotMapped attribute will keep this field out of the DB but still allow you to use it as part of your model.
Then when doing your binding or selecting:
Select(x => new {
//other properties
x.IsLikedByCurrentUser = x.LikedBy.Contains(User.Id)
})
This is pseudo code obviously, but I believe the Contains gets converted to a SQL in () statement. As long as LikedBy is a foreign property of a collection of user ids.
If you provide more of your model I'll update my answer.
Update
This is how I would do it:
public class Post
{
public int PostId { get; set; }
[Required]
public string Title { get; set; }
[NotMapped]
public bool IsLikedByCurrentUser {get;set;}
public virtual ICollection<Comment> CommentsAssociated { get; set; }
public virtual ICollection<PostLike> PostLikes { get; set; }
public virtual ICollection<PostBookmark> PostBookmarks { get; set; }
}
Controller
public ActionResults Index() {
var posts = _db.Posts.Select(x => new Post {
Title = x.Title,
IsLikedByCurrentUser = x.PostLike.Select(y => y.UserId).Contains(User.Id)
});
return View();
}
I'm still doing this from memory so you might have to make some adjustments.
Update 2
Something else you could do that might be a better practice is to create a "ViewModel" type of class that has the IsLikedByCurrentUser property instead of using a NotMapped column. The select statement would then be
Select(x => new PostVidewModel {
Title = x.Title,
IsLikedByCurrentUser = x.PostLike.Select(y => y.UserId).Contains(User.Id)
});