How do I add a foreign key to Identity user in EF Core? - asp.net

Let's say I have a Todo model with a corresponding Todo table (DbSet<Todo>) in an EF-generated database. The table will store a todo item per row. Each user (in an ASP.NET Core Identity + IdentityServer app) will be associated with multiple todo items (one-to-many, a user can have multiple todo items).
The way I'd do it is to add a UserId foreign key to the Todo model that represents the user that owns the todo item .
How would I do that with EF Core while utilizing the classes proved by ASP.NET Core Identity? My intuition was to add the following to the Todo class model:
public string UserId { get; set; }
[ForeignKey("UserId")]
public ApplicationUser User { get; set; }
This uses ApplicationUser (the ASP.NET Core Identity default user class model) as discovery to populate UserId with a foreign key.
Here's the problem: when I run Update-Database I get an error! It says The entity type 'IdentityUserLogin<string>' requires a primary key to be defined..
Browsing through the code for IdentityUserLogin, I can see it has a property UserId which should be considered a primary key, right?
Anyway, to make sure its registered as a primary key, I added this code to ApplicationUser's OnModelCreating method: builder.Entity<ApplicationUser>().HasKey(u => u.Id);. I still get the same error! Not sure what I'm doing wrong here :(. Edit: Yes, I called the base class so that's not the problem.
Note: I know I can add a field to each user that carries a collection of todo item Ids instead of having a foreign key, but I'm not sure if that's the good way of doing this. Guidance on this issue appreciated.

You haven't shared the code for this, but I'm guessing you have made an override of OnModelCreating in your db context, but have not called base.OnModelCreating(modelBuilder);.
Without the base call, your context isn't applying the Identity related schema.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Add Identity related model configuration
base.OnModelCreating(modelBuilder);
// Your fluent modeling here
}
Edit: Felt like giving this a whirl so I just performed the below tasks. The TLDR would be that it worked out for me. I believe the issue you are having has nothing to do with the key you are making and something unrelated is causing your failure, because the failure you are getting is on a completely different table than the one you are building schema for.
I created a new .net core web application using Identity. First thing I did was call update-database to build up the DB from the initial migration that the project came with. This worked.
Next I added a Todo class. No properties, just an Id and the User field. Didn't even mark the foreign key.
public class Todo
{
public int Id { get; set; }
public string UserId { get; set; }
public ApplicationUser User { get; set; }
}
I added it to the ApplicationDbContext as a DbSet. Only alteration to the context was to add the ToDo DbSet
Then I called add-migration todo in the package manager console to build the todo migration. It generated as expected with the FK in place.
public partial class Todo : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Todos",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
UserId = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Todos", x => x.Id);
table.ForeignKey(
name: "FK_Todos_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_Todos_UserId",
table: "Todos",
column: "UserId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Todos");
}
}
I then added some code to my About controller action to just stick a record into the table to make sure the FK was working. It was.
public async Task<IActionResult> About()
{
var user = await _userManager.GetUserAsync(HttpContext.User);
var todo = new Todo
{
User = user
};
_context.Todos.Add(todo);
await _context.SaveChangesAsync();
return View();
}
Screenshot of the record in the DB along with tables and keys:
Other than the obvious that you have more fields on your Todo class is there something drastically different that you've done differently that may lead us to the problem?

Dave, your solution worked fine !
Just some details for readers, adding the class to ApplicationDbContext as a DbSet means adding to the file ApplicationDbContext.cs Something like :
public DbSet<MyClass> MyClass { get; set; }
and when it's needed (in Package Manager Console) :
PM> Add-Migration MyClass
PM> Update-Database

Related

How do you add a new user and claim in a single transaction using ASP.NET Core 2.1 Identity?

I am trying to add a new user and some other associated entities including a claim as one transaction. My classes are basically defined as below. Note that I am using int primary keys on my User class and that it is an identity column (value is provided by the database on insertion).
public class User : IdentityUser<int>
{
// custom props here
}
public class UserClaim : IdentityUserClaim<int>
{
// i actually haven't added anything to this class, it's mainly here so
// i don't have to keep specifying the user's primary key type
}
public class OtherEntity
{
public int UserId { get; set; }
[ForeignKey(nameof(UserId))]
public User User { get; set; }
// other stuff
}
I then want to add the user etc. to the database something like this:
User user = new User(){ /* set props */ };
OtherEntity other = new OtherEntity()
{
User = user
};
UserClaim claim = new UserClaim()
{
/* this is where the problem is */
UserId = user.Id,
ClaimType = "my-claim-type",
ClaimValue = "my-claim-value"
};
context.AddRange(user, other, claim);
context.SaveChanges();
I can easily link the User to the OtherEntity because I have set up the navigation property so I can just add the User to it and entity framework takes care of the filling in the UserId column. I cannot do this with UserClaim because it doesn't have the navigation property. I could call context.SaveChanges() after adding the User and entity framework would get the User.Id created by the database for me which I could use to set UserId on the UserClaim, but that would mean two transactions.
I have tried adding the navigation property to my definition of UserClaim as follows:
public class UserClaim : IdentityUserClaim<int>
{
[ForeignKey(nameof(UserId))]
public User User { get; set; }
}
But I get following runtime error:
InvalidOperationException: The relationship from 'UserClaim.User' to 'User' with foreign key properties {'UserId' : int} cannot target the primary key {'Id' : int} because it is not compatible. Configure a principal key or a set of compatible foreign key properties for this relationship.
Is there a way of creating both the user, and the claim in the same transaction?
My question should have been: "How do I add navigation properties between ASP.NET Identity classes?"
If I had looked for the answer to that I would have found the microsoft docs explaining how to do it!
The docs linked above tell you how to add the User.UserClaims navigation property:
public class User : IdentityUser<int>
{
public virtual ICollection<UserClaim> Claims { get; set; }
}
public class DataContext : IdentityDbContext</* Identity classes */>
{
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<User>(e =>
{
e.HasMany(u => u.Claims)
.WithOne()
.HasForeignKey(c => c.UserId)
.IsRequired();
});
}
}
But it doesn't show how to make the reverse UserClaim.User navigation property. I worked out that this can be done like this:
public class UserClaim : IdentityUserClaim<int>
{
public virtual User User { get; set; }
}
public class DataContext : IdentityDbContext</* Identity classes */>
{
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<User>(e =>
{
e.HasMany(u => u.Claims)
.WithOne(c => c.User) // <- this line is different
.HasForeignKey(c => c.UserId)
.IsRequired();
});
}
}
You can then create a new user and claim at the same time as per my question:
User user = new User(){ /* set props */ };
UserClaim claim = new UserClaim()
{
User = user, // <- this is the bit you can't do without the nav. property
ClaimType = "my-claim-type",
ClaimValue = "my-claim-value"
};
context.AddRange(user, claim);
context.SaveChanges();
I guess it's common sense, though I didn't realise until I inspected the actual SQL hitting the database, but this will still require two trips to the database even though we are only calling .SaveChanges() once! Entity Framework will first save the User and get the ID for the inserted row which it will then use when inserting the UserClaim.
Inserting related data is documented in Entity Framework: https://learn.microsoft.com/pl-pl/ef/core/saving/related-data
And it is also well described in other topics, for example: Entity Framework Foreign Key Inserts with Auto-Id
Every of you entites need to be set correctly for relations (foreign keys) in your entites models (without them, EF don't know what to do) and when you are adding it, you need to start from beginning, so UserClaim must be set from your User entity, for example
var user = new User(){
//properites
OtherEntity = new OtherEntity()
{
Id = 0, /*will be set automatically*/
UserId = 0 /*will be set automatically*/
/* other properites */
};
Claim = new UserClaim(){
Id = 0, /*will be set automatically*/
UserId = 0 /*will be set automatically*/
/* other properites */
}
}
ontext.Add(user);
context.SaveChanges();
You didn't provide all the information about your relations, I've just assumed this from your code.
PS. AddRange has only one parameter.
EDIT:
To clarify my answer, for everything to work, AddRange/Add need to be called with your base entity and relations inside, in tree manner.
But first you need to configure your models, something like that.
public class User : IdentityUser<int>
{
[ForeignKey("UserClaimId")]
public virtual UserClaim Claim {get;set;}
}
public class UserClaim : IdentityUserClaim<int>
{
[ForeignKey("UserId")]
public virtual User User {get;set;}
}
public class OtherEntity
{
public int UserId { get; set; }
[ForeignKey("UserId")]
public User User { get; set; }
// other stuff
}
You can also use OnModelCreating to set up entites, as described in documentation: https://learn.microsoft.com/en-us/ef/core/modeling/relationships
Entity Framework right now don't know anything about relations in Database, data types etc.
Updated code example
Edit, don't have enough reputation to add comment to your answer
Well, EF still needs to track entities. I would need to write and run some examples to check if there is some way to improve trips to database. Maybe someone could tell you more about mechanism that is behind adding related-data and if there is a way to improve it.
There are some libraries that helps with improving performance of saving changes to database, for example: https://entityframework-extensions.net
You could try it.

EF not creating identity table when trying to create new database

I have 2 model classes:
Customer.cs with name and Id
Movies.cs with name and Id
I tried to run enable-migrations, but I got this error:
No context type was found in the assembly WebApplication2'.
Then I saw some answers on websites and people told to make a DBContext class. I do not have any DBContext class as I just made a new MVC project.
So, I tried to make a DbContext class of my own as follows:
{
public class MyDBContext:DbContext
{
public void MyDbContext()
{
}
}
}
Then I was able to run enable-migrtaions command and Migration folder was created with configuration.cs as follows:
internal sealed class Configuration : DbMigrationsConfiguration<WebApplication2.Models.MyDBContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}
protected override void Seed(WebApplication2.Models.MyDBContext context)
{
// This method will be called after migrating to the latest version.
// You can use the DbSet<T>.AddOrUpdate() helper extension method
// to avoid creating duplicate seed data.
}
}
}
Now when I run the add-migration Initialmodel the Up() and Down() methods are empty and there are no Identity tables.
Please help !
First off I suggest you refer to creating a new MVC project using Entity Framework. There are a lot of tutorials but here's the Microsoft one which is accurate and pretty complete:
Get Started with Entity Framework 6 Code First using MVC 5
It also includes a section on Migrations, however you don't need Migrations until you have a database and a model that's changing.
I would suggest backing out your Migrations until we're ready for them. Rick Strahl has a good article on how to back them out and get back to a clean state:
Resetting Entity Framework Migrations to a clean State
Finally, your DbContext class has to have a DbSet. A DbSet class is an entity set that can be used for create, read, update, and delete operations. With your DbContext class as it is, Entity Framework has no idea what to do or map.
Change your DbContext class to something like this:
{
public class MyDBContext:DbContext
{
public void MyDbContext()
{
}
public virtual DbSet<Movie> Movies {get; set;}
public virtual DbSet<Customer> Customers {get; set;}
}
This will allow you (say in a Controller) to do something like this to add a new Customer to the database:
var customer = new Customer { name = "John Smith" };
using(var context = new MyDbContext())
{
context.Customers.Add(customer); // adds the customer to the DbSet in memory
context.SaveChanges(); // commits the changes to the database
}
NOTE: I don't recommend creating a DbContext this way in a controller, in the first link on using EF6 with MVC 5 there are better ways.
Hope that helps.

Creating new project with asp.net Identity and Database First [duplicate]

I need to integrate Asp.Net latest MVC version with an existing database which has an additional column String Address to table dbo.AspNetUsers
I need to create an instance ApplicationUser which has property Address.
Any idea how to do it?
A possible solution which works for me, basically I am able to integrate Asp.Net Identity User Profiles with an existing Database.
Getting the Asp.Identity Tables:
Create an MVC Project with Authentication Individual User Account
Open the DB listed under the DefaultConnection in Web.config. It will be called (aspnet-[timestamp] or something like that.)
Script the database tables using SQL Server Management Studio (attach database for mdc).
Alternatively use something like http://identity.codeplex.com/
Integrating with your existing db:
Insert the scripted tables into existing database in SQL Server Management Studio.
Customize and add relationships to ApplicationUser (if necessary).
Create new Web Project > MVC > DB First Project > Import DB with EF ... .
In IdentityModels.cs change the ApplicationDbContext :base("DefaltConnection") to use your project's DbContext.
Now you have the Asp.Identity Tables in your db with ER model in your application.
Asp.Identity Profile Adding new properties:
Enable Entity Framework Code First Database Migrations, just in VS go under Tools ‘Package Manager Console’,
Execute the command “Enable-Migrations”; Once we enabled the database migrations, we can go ahead and add new properties for our UserProfile
To Add new properties modify IdentityModels.cs file, example:
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string EmailID { get; set; }
}
Add New Migration
Once we added the properties, bring the Package Manager Console and execute the following command.
Add-Migration “YouMigrationName”
This command will generate a database script file, now execute following command to run this script file against the database.
Update-Database
Now, all the new properties will turn into table fields in the same database table.
I hope it can help others, if you have a better idea please let me know.
Take a look at these projects on GitHub:
https://github.com/kriasoft/AspNet.Identity - Identity Database Project + VS Template
https://github.com/kriasoft/AspNet-Server-Template - Sample / reference project
Which includes:
SQL Database Project Template for ASP.NET Identity 2.0
Entity Framework Database-First Provider(s)
Source Code and Samples
I had recently the same problem. I had an apllication created with DBFirst aproach and I needed to add Identity. This is what I did.
Install the next packages:
1. Microsoft.EntityFrameworkCore
2. Microsoft.EntityFrameworkCore.Design
3. Microsoft.EntityFrameworkCore.SqlServer
4. Microsoft.AspNetCore.Identity
5. Microsoft.AspNetCore.Identity.EntityFrameworkCore
6. Microsoft.AspNetCore.Aututhentication.JwtBearer
Do DbContext inherit from IdentityDbContext, like this:
public partial class BookStoresDBContext : IdentityDbContext
OnModelCreating I called the base constructor in order to avoid an error like "'IdentityUserLogin' requires a primary key to be defined"
protected override void OnModelCreating(ModelBuilder modelBuilder) {
base.OnModelCreating(modelBuilder);
}
As far as it was a created project the StringConnection was already there, if not add it.
On the Startup.cs configure Identity service on ConfigureServices
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<BookStoresDBContext>(options => options.UseSqlServer(Configuration.GetConnectionString("BookStoreDB")));
services.AddIdentity<IdentityUser, IdentityRole>(options =>
{
options.Password.RequireDigit = true;
options.Password.RequiredLength = 5;
}).AddEntityFrameworkStores<BookStoresDBContext>()
.AddDefaultTokenProviders();
}
You can configure the Authetication service too
services.AddAuthentication(auth =>
{
auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
auth.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options => {
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
RequireExpirationTime = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("Your key to encrypt"))
};
});
Then run the migration from the Package Manager Console
Add-Migration InitDb
On the migration file, remove all the migrationBuilder.CreateTable for the tables you already have in your Database
Update the Database from the Package Manager Console
Update-Database
Then you will see the Identity Tables on your db
I hope it result usefull 😁
Don't forget to add migrations and update the database. Otherwise it throws a dependecy injection exceptions for the identity.
public class MyUser : IdentityUser
{
public virtual MyUserInfo MyUserInfo { get; set; }
}
public class MyUserInfo{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class MyDbContext : IdentityDbContext<MyUserInfo> //Edited to MyUserInfo
{
public MyDbContext()
: base("DefaultConnection")
{
}
public System.Data.Entity.DbSet<MyUserInfo> MyUserInfo { get; set; }
}
Getting Profile information
When the User Logs in, you can display the profile information by doing the following
Get the current logged in UserId, so you can look the user up in ASP.NET Identity system
var currentUserId = User.Identity.GetUserId();
Instantiate the UserManager in ASP.Identity system so you can look up the user in the system
var manager = new UserManager<MyUser>(new UserStore<MyUser>(new MyDbContext()));
Get the User object
var currentUser = manager.FindById(User.Identity.GetUserId());
Get the profile information about the user
currentUser.MyUserInfo.FirstName

Extending Identity3 in MVC6

using the latest (current) RC1 of asp.net5 I'm looking at creating a simple relationship between a User entity and a WorkLog entity.
Is it possible to use the ApplicationUser Class from Identity as a starting point and use the ApplicationUser key which is defined as the linking key? I have had problems extending the ApplicationUser in the past and therefore generated a seperate dbcontext (pointing to the same database) and created my own plumbing in order to pass the IdentityUsers Id into my seperate dbcontext. Does anyone have any examples of extending the IdentityDbContext adding foreign key tables mapping to the IdentityUser Class?
Example below
//DBContext
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public DbSet<WorkLogItem> WorkLogItems { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Customize the ASP.NET Identity model and override the defaults if needed.
// For example, you can rename the ASP.NET Identity table names and more.
// Add your customizations after calling base.OnModelCreating(builder);
builder.Entity<WorkLogItem>(
e =>
{
e.Property(p => p.id).IsRequired().UseSqlServerIdentityColumn();
});
}
}
//WorkLogItem
public class WorkLogItem
{
public int id { get; set;}
public String UserId { get; set; }
public int Hours { get; set; }
public String Description { get; set; }
}
//ApplicationUser
public class ApplicationUser : IdentityUser
{
public ICollection<WorkLogItem> WorkLogItems { get; set; }
}
Doing what you've asked is expected to work out of the box. You can look at this commit to see the difference between a newly created MVC 6 project with Identity and your schema above.
Registering a user, and refreshing /Home/Index causes WorkLogItems to be added as expected. Note you don't need a separate DB context for this.
public IActionResult Index()
{
var user = _db.Users.Include(p => p.WorkLogItems).FirstOrDefault();
if (user != null)
{
user.WorkLogItems.Add(new WorkLogItem { Description = "New item added" });
_db.SaveChanges();
ViewBag.WorkItems = user.WorkLogItems.ToList();
}
else ViewBag.WorkItems = new WorkLogItem[] { };
return View();
}
The key items to be aware of when you add any collection to an existing entity are;
Make sure you add the migration and update the databse
Make sure you use Include on the query because EF7 does not support Lazy Loading.

How can I properly access my EF foreign key column straight after it has been added?

I have a basic notification system working on my site and I have started to make it work in real-time with SignalR but I'm having trouble accessing my foreign key to the user table.
I have the following code in one of my pages that generates a new Notification object
Notification NewNotification = new Notification
{
UserToId = u.UserId,
NotificationText = "some text"
};
db.Notifications.Add(NewNotification);
db.SaveChanges();
string name = NewNotification.UserFrom.DisplayName;
On the last line I get System.NullReferenceException: Object reference not set to an instance of an object
When I pass the NewNotification through SignalR to the client and try to access NewNotification.UserFrom.DisplayName the js console tells me that NewNotification.UserFrom is undefined. This happens despite me being able to access it on the layout page after the Notifications have been added.
The relevant parts of my model for my Notification is as follows
public class Notification
{
public Notification()
{
this.UserFromId = WebSecurity.CurrentUserId;
}
[Key]
public int NotificationId { get; set; }
public int UserToId { get; set; }
public int UserFromId { get; set; }
public virtual UserProfile UserTo { get; set; }
public virtual UserProfile UserFrom { get; set; }
[MaxLength]
public string NotificationText { get; set; }
}
And then my db context has the following to set up the foreign key relationship
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Notification>()
.HasRequired(n => n.UserFrom)
.WithMany(u => u.NotificationsFrom)
.HasForeignKey(n => n.UserFromId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Notification>()
.HasRequired(n => n.UserTo)
.WithMany(u => u.NotificationsTo)
.HasForeignKey(n => n.UserToId)
.WillCascadeOnDelete(false);
}
The question is:
How can I access UserFrom.DisplayName of the notification just after it's been created?
it is likely that your entity is not being refreshed after the save. you can either
1) reacquire it from the datastore with another query. If Notification has some sort of Identity column, it will be updated on SaveChanges() on your NewNotification object. That id can be used to retrieve the object. (Something like NewNotification = db.Notifications.Single(x => x.Id == NewNotification.Id)
2) If you hate the above option, you can do something like this:
((IObjectContextAdapter)db).ObjectContext.Refresh(RefreshMode.ClientWins, NewNotification); This will update your NewNotification object. Not pretty, but I ran into an issue similar to this recently and it worked for me.
3) since you have the userid in scope already, I would consider doing a db.Users.Single(x => x.UserId == u.UserId).DisplayName
If you want to access the User through the NewNotification object, I personally like option 2 because it becomes a bit clearer what your intentions are.... You want to refresh your NewNotifications object against the database and you want whatever is in the Client that doesn't match your object to "WIN".
I think option 3 is perhaps the cleanest, but its your call!

Resources