Search code examples
c#entity-framework.net-coreentity-framework-coreasp.net-identity

What type of relationship is this and how should it be configured?


I am trying to create a discussion board using Identity where ApplicationUsers can create, save, hide, and comment on Posts. I was able to get Posts and Comments working without data annotations or overriding OnModelCreating as follows:

Post.cs:

public class Post
    {
        public int ID { get; set; }
        public string Title { get; set; }
        public string Content { get; set; }
        [DataType(DataType.DateTime)]
        public DateTime CreationDate { get; set; }
        public ApplicationUser OriginalPoster { get; set; }
        public int Upvotes { get; set; }
        public int Downvotes { get; set; }
        public int VoteScore { get; set; }
        public ICollection<Comment> Comments { get; set; }
    }

Comment.cs:

public class Comment
    {
        public int ID { get; set; }
        public string Content { get; set; }
        public ApplicationUser Commenter { get; set; }
        [DataType(DataType.DateTime)]
        public DateTime CreationDate { get; set; }
        public int Upvotes { get; set; }
        public int Downvotes { get; set; }
        public int VoteScore { get; set; }
    }

ApplicationDbContext.cs:

public class ApplicationDbContext : IdentityDbContext
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
        {
        }

        public DbSet<Post> Posts { get; set; }
        public DbSet<Comment> Comments { get; set; }
    }

But when I extend IdentityUser to add my own custom fields:

public class ApplicationUser : IdentityUser
    {
        public ICollection<Post> CreatedPosts { get; set; }
        public ICollection<Post> SavedPosts { get; set; }
        public ICollection<Post> HiddenPosts { get; set; }
    }

Add-Migration returns with error:

"Unable to determine the relationship represented by navigation 'ApplicationUser.CreatedPosts' of type 'ICollection'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'."

Why is EF Core able to determine the relationship between a Post and its Comments but not an ApplicationUser and its created/saved/hidden Posts? I understand that I will have to specify the relationship either by using data annotations or overriding OnModelCreating but I am unsure of how to go about doing this. Any amount of help would be very much appreciated.


Solution

  • The reason is because you have multiple collection properties referencing the same model, Post. This type of situation you need to specifically tell EF Core which foreign properties each of CreatedPosts, HiddenPosts and SavedPosts to reference from Post. Given you only have one ApplicationUser foreign property named OriginalPoster, that would be impossible because there are no other properties HiddenPosts and SavedPosts would reference. You would only be able to reference one by configuring it like this.

    builder.Entity<ApplicationUser>().HasMany(s => s.CreatedPosts)
        .WithOne(f => f.OriginalPoster);
    

    Now, which properties do the other two (HiddenPosts and SavedPosts) reference? I hope you see the problem here.

    But assuming you have another type of poster defined in your Post model like this.

    public ApplicationUser HiddenPoster {get;set;} 
    

    You make the collection it belongs to reference it as well.

    builder.Entity<ApplicationUser>().HasMany(s => s.HiddenPosts)
        .WithOne(f => f.HiddenPoster);
    

    But you don't, so this approach would not work because it's only one type of poster you have in your Post. I would suggest you redefined your model to have an enum in Post with values Created,Hidden and Saved.

    public enum PostStatus
    {
        Created,
        Hidden,
        Saved
    }
    

    Then define the status in the Post model like this.

    public PostStatus Status {get;set;}
    

    So that in your ApplicationUser, you do not have to define multiple collections, you only have Posts;

    public class ApplicationUser : IdentityUser
    { 
        public ICollection<Post> Posts {get;set;}
    }
    

    and you can then filter which post is created, hidden or saved using the Status enum property from Post.