Search code examples
c#asp.net-mvcentity-frameworkasp.net-identitydbcontext

How to correctly implement other entity's reference to Identity user?


I'm using Identity which has his own context.

public class ApplicationUser : IdentityUser {
    // some custom fields
}

public class IdentityContext : IdentityDbContext<ApplicationUser> {
   //...
}

Also I have some other entities like this

public class Comment{
   public int Id {get;set;}
   public string Message{get;set;}
   public DateTime Time{get;set;}
}

which are used by my other context

public class MyContext :DbContext {
   public DbSet<Comment> Comments { get; set; }
   //... other DbSets
}

Question. I want that my Comment entity had author property, so I'll have something like

public class Comment{
   public int Id {get;set;}
   public string Message{get;set;}
   public DateTime Time{get;set;}
   public virtual ApplicationUser Author {get;set;}
}

but ApplicationUser is located in different context, though in the same DB. I bet this is impossible.

How to correctly implement this? Should I move DbSets from MyContext to IdentityContext, so I can freely use code like this

public virtual ApplicationUser Author {get;set;}

or should I leave it be in different context, but add something like

public string AuthorId {get;set}

and make some workarounds to get the author info from different context every time I need it? Or something else?

Thanks


Edit

Ok, I ended up with something like this:

public class ApplicationUser : IdentityUser {
        public virtual UserProfile UserProfile { get; set; }
}

public class UserProfile {
        [Key, ForeignKey("ApplicationUser")]
        public string Id { get; set; }
        //... custom fields
        public virtual ApplicationUser ApplicationUser { get; set; }

}

public class IdentityContext : IdentityDbContext<ApplicationUser> {
        //...
        public DbSet<UserProfile> UserProfiles { get; set; }
 }

But how I should implement Comment's author reference? Like this? So it will not be linked via EF relationships and I'll just fill UserProfileId somewhere in code by myself?

public class Comment{
   public int Id {get;set;}
   public string UserProfileId{get;set;}
}

Is this correct way to do?


Solution

  • Ask yourself the question, what information does ApplicationUser have that you can use in your business model? And if so, is that the right location to store it? Or do you just want to link the user?

    ApplicationUser is located in different context, though in the same DB.

    But now suppose it is not. Suppose you want to use something like IdentityServer in the future.

    I think the best approach would be to keep your business information seperated from the identity information. I wouldn't want to expose login information to the business with the posibility that it may be read or changed.

    I've seen code where the ApplicationUser (as part of the business context) was sent in a ViewModel to the client, including HashPassword. Definately something you want to prevent.

    What you can do is add a User table in MyContext to store the data you want to use. The ApplicationUser doesn't have any information you want in your business model.

    I assume all you want is to link the information to a user. And you want to profit from Entiy Framework's object linking.

    So create a User table and add a property to ApplicationUser to store the UserId of your User table. Or you can link the other way around: add ApplicationUserId to your User table. It is also possible to use the same Id for both: set ApplicationUser.Id yourself (doesn't have to be a guid) or use the generated guid for User.Id.

    In case there is some additional information in the identity context that you want to use, e.g. EmailAddress, you may consider to add claims.

    -- update --

    The idea is to add a user table to your context, not the identity context. To make it more clear I will call the table Person (not user). Notice that Person does not inherit IdentyUser / ApplicationUser.

    public class Person {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        //etc..
        public string ApplicationUserId { get; set; }
    }
    
    public class MyContext :DbContext {
       public DbSet<Comment> Comments { get; set; }
       public DbSet<Person> Persons { get; set; }
       //... other DbSets
    }
    
    public class Comment{
       public int Id {get;set;}
       public string Message{get;set;}
       public DateTime Time{get;set;}
       public virtual Person Author {get;set;}
    }
    

    Now when I query all comments for the current user I can lookup the Person.Id (based on User.Identity.GetUserId()).

    Do not forget to add a Person when you create a login.

    I hope this helps. If not, please let me know.