2

I am working with .NET Core 2.2 and I am having trouble understanding the right way to save data owned by a user.

I am using the out of the box identity scaffolding and code first migrations. Users are stored in the AspNetUsers table and I'm leveraging Google/Facebook providers.

As a basic example, if I have a simple model such as this, I can't find a clean example of how to reference the owner of the data in the model.

public class UserFavoriteColor
{
    public int Id { get; set; }
    public string Color {get; set;

    public IdentityUser User { get; set; }    // I've seen this in some documentation
    public string UserId { get; set; }        // I've seen this as well
    // Other ways??
}

Additionally, there seems to be a handful of ways to retrieve and store the user information as well:

// one way to get the id
User.FindFirst(ClaimTypes.NameIdentifier).Value;                   

// another way
 _userManager = MockUserManager.GetUserManager<ApplicationUser>(); 

Maybe I'm completely missing something obvious here, but I would love to see a very basic model and controller example that saves a record with the users identity value as a foreign key with a best practice approach.

Thanks!

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Eric Longstreet
  • 783
  • 1
  • 9
  • 23

2 Answers2

1

If in the model UserFavoriteColor you have defined both the foreign key field UserId and the navigation property User then you can choose which property to set. I would use the ForeignKey DataAnnotation attribute to further describe the relationship between the two fields.

If you only implemented the User navigation proerty in the model, then the Foreign Key field would still be implemented in the database level, but you would not be able to reference it in your code. So it is not explicitly required and in many online examples we leave it out to simplify the example and because many would consider exposing the foreign key in the model as implementation detail that should not be there...

public class UserFavoriteColor
{
    [Key]
    public int Id { get; set; }
    public string Color {get; set;

    [ForeignKey(nameof(UserId))]
    public virtual IdentityUser User { get; set; } // Navigation Property
    public string UserId { get; set; }     // ForeignKey Field        
}

To save a record of this type through a controller is pretty standard, generally through a controller I would set the Foreign Key field and not the Navigation Property. Its simpler code and less prone to duplicate entries being generated in the database.

If you have a User object, you can set that instead of the UserId and the key field will be resolved for you automatically on save changes, but the User object MUST have been loaded from or at least attached to the same DbContext or it can result in a new User record being created in the database.

To save a reference to the Current User then this is as good a reference as any, the mechanism that you use to retrieve the User Id is however highly dependent on how you have configured your authentication, which is out of scope for this question.

var currentUserId = User.FindFirst(ClaimTypes.NameIdentifier).Value;
var color = "Red";
...
// Insert a new favorite colour record
UserFavoriteColor record = new UserFavoriteColor {
    Color = color,
    UserId = currentUserId 
};
context.Add(record);
context.SaveChanges();

Chris Schaller
  • 13,704
  • 3
  • 43
  • 81
  • Excellent. I have used the foreign key structure in your example for other fields and was leaning towards it, but just could not find the trivial documentation for it to ensure it was appropriate for something as sensitive as identity. Thank you for the detailed write up and explanation! – Eric Longstreet Mar 08 '20 at 04:11
0

Remember EF uses convention over configuration and uses it to identify primary keys, foreign keys and mappings

Lets consider this scenario (See inline comments)

As per the default convention, EF makes a property as foreign key property when its name matches with the primary key property of a related entity.

public class UserFavoriteColor
{
  public int Id { get; set; }
  public string Color {get; set;


  public int UserId { get; set; } // By default convention EF will mark this as foreign key property as its name matches the primary key of related entity
  public IdentityUser User { get; set; } // Navigation property 

}

public class IdentityUser
{
  public int UserId { get; set; } // EF will treat this as Primary Key 
  public string UserName {get; set;}
  public ICollection<UserFavoriteColor> FavColors { get; set; }
}

To Override Foreign: Use this data annotation against the respective property in your Domain Class

[ForeignKey("IdentityUser")]
public int ThisUserId { get; set; }
public IdentityUser User { get; set; } 

[ForeignKey] annotation on the foreign key property in the dependent entity(as in the example above)
[ForeignKey] annotation on the navigation property in the dependent entity
[ForeignKey] annotation on the navigation property in the principal entity


Now to add migrations and update database

  • Add-Migration AddNewUser

Add a new user

var color1 = new UserFavoriteColor {Color="Red"}
var user = new IdentityUser {UserName="Bob",};
using  (var context = new DbContext())
{
    context.DBSetName.Add(user);  
    context.SaveChanges();
    //Save changes will - Examine each object context is tracking
                        - Read state of object (state=new) therefore needs to Insert
                        - Emit SQL commands
                        - Execute SQL commands
                        - Captures any results
}
  • Update-Database -verbose

Note: Make sure you have good separation between Domain Classes and Data Model while you are beginning.

Clint
  • 6,011
  • 1
  • 21
  • 28
  • I've seen other documentation that creates a "IdentityUser" class similar to your example, but shouldn't you use the built in one instead of trying to use a wrapper? Chris's answer seems to make more sense and doesn't require defining "IdentityUser" (unless i misunderstood your example). Thanks for the write-up. – Eric Longstreet Mar 08 '20 at 04:29
  • @EricLongstreet, what do you mean by "using a built in one instead of using a wrapper" – Clint Mar 08 '20 at 04:32
  • In your example, you define the class model "IdentityUser", however, this is a built in dotnet core identity scaffoled class from what I understand. https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.identity.entityframeworkcore.identityuser?view=aspnetcore-1.1 – Eric Longstreet Mar 08 '20 at 04:35
  • ahh ok, yes you can use the builtin scaffolding on it, the point here is to note how EF core determines ForeignKey entities by default and how you can override them – Clint Mar 08 '20 at 04:41
  • besides, there is no need to mark `id` as primary key as that is inferred by EF Cores conventions – Clint Mar 08 '20 at 04:49