I want to require that all entities of a particular type have a corresponding user in my ASP.NET MVC app, and that this is enforced at the database level i.e. as a non-nullable field. However, when I set the Required attribute on the IdentityUser property in my model class, like so:
using Microsoft.AspNetCore.Identity;
using System.ComponentModel.DataAnnotations;
namespace Test.Models
{
public class Foo
{
public int Id { get; set; }
[Required]
public IdentityUser User { get; set; }
}
}
the corresponding migration that gets generated sets the UserId table field to nullable:
UserId = table.Column<string>(type: "TEXT", nullable: true)
I've read that Table Per Hierarchy can cause this, but I'm not using any kind of inheritance.
What am I missing? Is there a way to achieve what I want?
Try to fix your class
public class Foo
{
[Key]
public int Id { get; set; }
[Required]
public string UserId { get; set; }
[ForeignKey(nameof(UserId))]
public IdentityUser User { get; set; }
}
Net 5 automatically creates a shadow property UserId in order you could save your Foo class. Since you used [Required], EF automatically added that it is nullable. If this property was not able to be null (for example if it was int type ( not int? ! )) you would not need a [Required] attribute.
Also what you can do is to use Fluent API to configure your contraints.
public class Foo
{
public int Id { get; set; }
public string UserId { get; set; }
public IdentityUser User { get; set; }
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public DbSet<Foo> Foos { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Foo>()
.HasRequired(c => c.User)
.WithMany(d => d.Foos)
.HasForeignKey(c => c.UserId);
}
}
Related
query.Include("Store_Location").Load();
throws:
An exception of type 'System.InvalidOperationException' occurred in EntityFramework.SqlServer.dll but was not handled in user code
Additional information: A specified Include path is not valid. The EntityType 'Model.Order' does not declare a navigation property with the name 'Store_Location'.
I used the following code in order to create the navigation code-first:
public partial class Order
{
public Nullable<int> Store_Location_ID { get; set; }
public virtual Store_Location Store_Location { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
}
public partial class Store_Location
{
public int ID { get; set; }
public virtual ICollection<Order> Orders { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
}
https://learn.microsoft.com/en-us/ef/ef6/fundamentals/relationships
Do I need to use the designer? Is there anything I need to do in order for the navigation to be created?
If you create your database with code first approach, then your entities should not be partial classes. Define them like this:
public class Order
{
public int? StoreLocationId { get; set; }
public virtual StoreLocation StoreLocation { get; set; }
}
public class StoreLocation
{
public int Id { get; set; }
public virtual ICollection<Order> Orders { get; set; }
}
Then you should create a DbContext class:
public class StoreDbContext : DbContext
{
public StoreDbContext(DbContextOptions<StoreDbContext> options) : base(options)
{
}
public virtual DbSet<StoreLocation> StoreLocations { get; set; }
public virtual DbSet<Order> Orders { get; set; }
}
After creating a context you can use the ef commands to create your database. You can read more about ef core here: https://learn.microsoft.com/en-us/ef/core/get-started/?tabs=netcore-cli
If you define your classes like I did above, you can include your navigation properties strongly typed like this:
query.Include(order => order.StoreLocation);
I have recently extended my AspNetRoles table like this:
public class AspNetRoles:IdentityRole
{
public AspNetRoles() : base() { }
public String Label { get; set; }
public String ApplicationId { get; set; }
public AspNetApplications Application { get; set; }
public static readonly String SystemAdministrator = "SystemAdministrator";
}
It works fine when I create a new role. However, when I try to extract it to a list like this:
var data = dbContext.Roles.ToList();
And try to do a search like this:
data = data.Where(u => u.Id.ToString().ToLower().Contains(Input.Search.ToLower())).ToList();
I can't access the ApplicationId column. Am I missing something?
EDIT:
My dbContext
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, AspNetRoles<string>, string>
{
public virtual DbSet<AspNetUsersExtendedDetails> AspNetUsersExtendedDetails { get; set; }
public virtual DbSet<AspNetApplications> AspNetApplications { get; set; }
public virtual DbSet<AspNetEventLogs> AspNetEventLogs { get; set; }
public virtual DbSet<AspNetRoles> AspNetRoles { get; set; }
public ApplicationDbContext() : base("AppStudio")
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
I have updated my dbContext but now it shows this error: 'IdentityDbContext<ApplicationUser, AspNetRoles<string>, string>' does not contain a constructor that takes 2 arguments
You need to tell ASP.Net Identity about the custom role table that you want to use.
Edit: since the default IdentityRole implementation uses a string as the PK, the type can be omitted. Just checking futher on ASP.Net Identity version 2, as soon as you specify a custom IdentityRole class, the class declaration needs to include all types.
That means you need to declare your ApplictionDbContext like this:
public class ApplicationDbContext: IdentityDbContext<ApplicationUser, AspNetRoles, string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>
{
public ApplicationDbContext() : base("AppStudio")
{
//note: before this change, if you included the
//throwIfV1Schema parameter in the constructor,
//it needs to be removed.
}
//implementation
}
Note that this assumes that the Primary Key of the users table is a string. If this is not the case, substitute with the applicable type (e.g. a Guid).
You have to add AspNetRoles to your IdentityDbContext.
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, AspNetRoles, string>
{
public virtual DbSet<AspNetUsersExtendedDetails> AspNetUsersExtendedDetails { get; set; }
public virtual DbSet<AspNetApplications> AspNetApplications { get; set; }
public virtual DbSet<AspNetEventLogs> AspNetEventLogs { get; set; }
public ApplicationDbContext() : base("AppStudio", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
I have 3 tables Violation,Comment and and auto generated AspNetUsers respectively.The relationship between them as follows.
I am using code-first approach and my models are as follows.Some properties are removed for brevity.
Violation Model
public class Violation
{
public Violation()
{
this.Comments = new HashSet<Comment>();
}
public int Id { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
public virtual ApplicationUser CreatorUser { get; set; }
}
Comment Model
public class Comment
{
public int Id { get; set; }
[Required]
public string Content { get; set; }
[Required]
public DateTime PostedDateTime { get; set; }
public ApplicationUser ApplicationUser { get; set; }
public Violation Violation { get; set; }
}
ApplicationUser(AspNetUsers Table)
public class ApplicationUser : IdentityUser
{
public ApplicationUser()
{
this.Comments = new List<Comment>();
this.Violations = new List<Violation>();
}
public virtual List<Comment> Comments { get; set; }
public virtual List<Violation> Violations { get; set; }
}
The problem is that when I try to retrieve Comment's ApplicationUser navigation property , I see many of them pointing to a null property even database has proper record for each of them.
Shortly,EF doesn't retrieve database records properly.I stuck with it,can't find the reason.
In fact, it's not being lazy-loaded. You didn't add the virtual keyword to your Comment.ApplicationUser property, so Entity Framework cannot override it to add the lazy-loading logic. As a result, it's always going to be null unless you explicitly load it. Add the virtual keyword, and you'll be fine.
If you want the navigation properties populated you need to include them in the query:
var comments = context.Comments
.Include(c => c.Violation)
.Include(c => c.ApplicationUser)
.Where(x => x.Violation.Id == violationId);
https://msdn.microsoft.com/en-us/data/jj574232.aspx#eager
I wanted to have a view to add a few extra proprieties and I tried the following:
Base table:
namespace Core.Model {
public class Item
{
[Key]
public int ItemId { get; set; }
[Required]
public ItemType Type { get; set; }
[Required]
[MaxLength(255)]
public string Name { get; set; }
//...
}
}
extended view:
namespace Core.Model
{
public class ItemExtended : Item
{
public int? CommentsCount { get; set; }
}
}
Then in the Context I have:
public DbSet<QuiverItemExtended> ItemsExtended { get; set; }
modelBuilder.Entity<ItemExtended>()
.ToTable("ItemsExtended")
.HasKey(view => new { view.ItemId });
This works for querying but when I try to insert a normal Item I get this exception:
All objects in the EntitySet 'Context.Items' must have unique primary
keys. However, an instance of type 'Core.Model.ItemExtended' and an
instance of type 'Core.Model.Item' both have the same primary key
value, 'EntitySet=Items;ItemId=1097'.
Any ideias how I can fix this?
thanks in advance
In this case I will solve this using a calculated column: http://www.davepaquette.com/archive/2012/09/23/calculated-columns-in-entity-framework-code-first-migrations.aspx
I'm looking at the interfaces on the new ASP.NET Identity classes and the database it creates using Entity Framework Code First. I'm using the Visual Studio 2013 RC.
At first glance the database schema looks reasonably normal:
But all the keys are NVARCHAR(128)
And for some crazy reason AspNetUserSecrets.Id is a PK that looks like it could point to more than one record in the AspNetUsers table. Does this mean multiple AspNetUsers will have to share the same password?
When I look at the Looking at the interfaces you're forced to implement, these are all strings...
public class User : IUser
{
public string Id { get; set; }
public string UserName { get; set; }
}
public class UserSecret : IUserSecret
{
public string UserName { get; set; }
public string Secret { get; set; }
}
public class UserRole : IUserRole
{
public string UserId { get; set; }
public string RoleId { get; set; }
}
public class UserClaim : IUserClaim
{
public string UserId { get; set; }
public string ClaimType { get; set; }
public string ClaimValue { get; set; }
}
public class UserManagement : IUserManagement
{
public string UserId { get; set; }
public bool DisableSignIn { get; set; }
public DateTime LastSignInTimeUtc { get; set; }
}
public class Tokens : IToken
{
public string Id { get; set; }
public string Value { get; set; }
public DateTime ValidUntilUtc { get; set; }
}
public class UserLogin : IUserLogin
{
public string UserId { get; set; }
public string LoginProvider { get; set; }
public string ProviderKey { get; set; }
}
public class Role : IRole
{
public string Id { get; set; }
public string Name { get; set; }
}
So I'm coming to terms with the fact that I may have to implement this using strings for PK and FK relationships.
But I'd really love to know WHY it's built like this...?
EDIT: Time has passed and there are now articles on how to extend the asp.net identity to use int (or guid) fields:
http://www.asp.net/identity/overview/extensibility/change-primary-key-for-users-in-aspnet-identity
The intent was to allow both arbitrary id types (i.e. int, guid, string), but also avoid having serialization/casting issues for the id property.
So you can define your keys however you like and just implement the interface method
public class MyUser : IUser {
public int Id { get; set; }
string IUser.Id { get { return Id.ToString(); } }
}
Adding to what Hao said:
The Identity runtime prefers strings for the user ID because we don’t want to be in the business of figuring out proper serialization of the user IDs (we use strings for claims as well for the same reason), e.g. all (or most) of the Identity interfaces refer to user ID as a string.
People that customize the persistence layer, e.g. the entity types, can choose whatever type they want for keys, but then they own providing us with a string representation of the keys.
By default we use the string representation of GUIDs for each new user, but that is just because it provides a very easy way for us to automatically generate unique IDs.
With ASP.NET Core, you have a very simple way to specify the data type you want for Identity's models.
First step, override identity classes from < string> to < data type you want> :
public class ApplicationUser : IdentityUser<Guid>
{
}
public class ApplicationRole : IdentityRole<Guid>
{
}
Declare your database context, using your classes and the data type you want :
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, Guid>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
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);
}
}
And in your startup class, declare the identity service using your models and declare the data type you want for the primary keys :
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext, Guid>()
.AddDefaultTokenProviders();
In ASP.NET identity tables, primary keys will still be in NVARCHAR but in your application it's will be the data type you want.
You can check this in a controller :
[HttpGet]
public async Task<IActionResult> Test()
{
ApplicationUser user = await _userManager.GetUserAsync(HttpContext.User);
Guid userId = user.Id; // No cast from string, it's a Guid data type
throw new NotImplementedException();
}