Map tables using fluent api in asp.net MVC5 EF6? - claims-based-identity

I am trying to add profile/Membership information into my MVC5 application and adding configuration mappings.
I get the following error message:
my.Models.IdentityUserLogin: : EntityType 'IdentityUserLogin' has no
key defined. Define the key for this EntityType.
my.Models.IdentityUserRole: : EntityType 'IdentityUserRole' has no key
defined. Define the key for this EntityType.
IdentityUserLogins: EntityType: EntitySet 'IdentityUserLogins' is
based on type 'IdentityUserLogin' that has no keys defined.
IdentityUserRoles: EntityType: EntitySet 'IdentityUserRoles' is based
on type 'IdentityUserRole' that has no keys defined.
public class ApplicationUser : IdentityUser
{
public string City { get; set; }
public string Discriminator { get; set; }
public string Address { get; set; }
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new ApplicationUserConfiguration());
}
}

Calling base.OnModelCreating(modelBuilder) did not solve the issue for me.
The behavior of Microsoft.AspNet.Identity.EntityFramework seems to be different in VS2013-Preview, VS2013-RC, and VS2013-RTM. I'm using the RTM version.
After inheriting from IdentityUser I had to recreate all other primary keys in the model to make it work:
public class ApplicationUser : IdentityUser
{
public string DisplayName { get; set; }
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext() : base("DefaultConnection") { }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<IdentityUserLogin>().HasKey<string>(l => l.UserId);
modelBuilder.Entity<IdentityRole>().HasKey<string>(r => r.Id);
modelBuilder.Entity<IdentityUserRole>().HasKey(r => new { r.RoleId, r.UserId });
}
(See Configuring/Mapping Properties and Types with the Fluent API)
I guess work on AspNet.Identity.EntityFramework is ongoing and this will be fixed (?)

Call base.OnModelCreating(modelBuilder) after configuration added.

Follow these steps around OnModelCreating method and test after each one to be aware of taking effect:
Make sure you have one Context to prevent of their rule conflicts.
Call base.OnModelCreating(modelBuilder); inside the mentioned
method (first of all)
Add the followings plus previews step in the method:
modelBuilder.Entity<IdentityUserLogin>().HasKey<string>(l => l.UserId);
modelBuilder.Entity<IdentityRole>().HasKey<string>(r => r.Id);
modelBuilder.Entity<IdentityUserRole>().HasKey(r => new { r.RoleId, r.UserId });

Related

How can i use UserManager with custom user?

I have created custom user class which inherits IdentityUser<int>.
[Table("Users", Schema = "UserData")]
public class User : IdentityUser<int>
{
/// <summary>
/// Property for sake of creating One-to-One relationship UserDetails -> User
/// </summary>
[Required]
public UserDetails UserDetails { get; set; }
}
public class BlogDbContext : IdentityDbContext<User, IdentityRole<int>, int>
{
public BlogDbContext(DbContextOptions<BlogDbContext> options)
: base(options) {}
protected override void OnModelCreating(ModelBuilder builder)
{
builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
//UserDataConfig
builder.ApplyConfiguration(new UserConfiguration());
builder.ApplyConfiguration(new UserDetailsConfiguration());
//UserData
builder.Entity<Location>()
.HasOne<UserDetails>(s => s.UserDetails)
.WithOne(g => g.Location)
.HasForeignKey<UserDetails>(ad => ad.LocationId);
builder.Entity<User>()
.HasOne<UserDetails>(s => s.UserDetails)
.WithOne(g => g.User)
.HasForeignKey<UserDetails>(ad => ad.UserId);
base.OnModelCreating(builder);
}
//UserData
public DbSet<User> Users { get; set; }
public DbSet<UserDetails> UserDetails { get; set; }
}
}
Now I am trying to create following field
private UserManager<User, int> _userManager;
And my error:
The type 'ApplicationCore.DataModel.UserData.User' cannot be used as
type parameter 'TUser' in the generic type or method
'UserManager<TUser, TKey>
How can I fix it? Thanks for your attention.
Replace User to IdentityUser and Role to IdentityRole and working fine. like
public class DataContext : IdentityDbContext<IdentityUser,IdentityRole, string, IdentityUserClaim<string>, IdentityUserRole<string>,
IdentityUserLogin<string>, IdentityRoleClaim<string>,IdentityUserToken<string>>
Or use:-
public class ApplicationUser : IdentityUser<int>
{
}
public class ApplicationRole : IdentityRole<int>
{
}
public class BlogDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, int>
{
}
It will resolve your issue.

Extending EF Core Identity UserStore with integer user ID

Using Blazor server, dotnet 5 and Entity Framework Core, I have successfully customised Identity using integer IDs
e.g
public class ApplicationUser : IdentityUser<int>
{
[Required]
[MaxLength(50)]
public string FirstName { get; set; }
[Required]
[MaxLength(50)]
public string LastName { get; set; }
}
I now want to extend UserStore for custom login to store password history to prevent duplication. The documented way of doing this seems to be:
public class ApplicationUserStore : UserStore<ApplicationUser>
{
public ApplicationUserStore(ApplicationDbContext context, IdentityErrorDescriber describer = null) : base(context, describer)
{
}
public override async Task<IdentityResult> CreateAsync(ApplicationUser appUser)
{
await base.CreateAsync(appUser);
await AddToUsedPasswordAsync(appUser, appUser.PasswordHash);
}
public Task AddToUsedPasswordAsync(ApplicationUser appuser, string userpassword)
{
appuser.UserUsedPassword.Add(new UsedPassword() { UserID = appuser.Id, HashPassword = userpassword });
return UpdateAsync(appuser);
}
}
This works for the default GUID Id but when using an integer it throws the error:
The type ApplicationUser cannot be used as type parameter 'TUser' in the generic type or method 'UserStore'. There is no implicit reference conversion from ApplicationUser' to 'Microsoft.AspNetCore.Identity.IdentityUser'.
What is the correct way to do this?
EDIT:
Per #Yinqiu, modified for custom ApplicationRole to
public class ApplicationUserStore : UserStore<ApplicationUser, ApplicationRole, ApplicationDbContext, int>
{
public ApplicationUserStore(ApplicationDbContext context, IdentityErrorDescriber describer = null) : base(context, describer)
{
}
Builds successfully and creates users but gives run time error when trying to access ApplicationUserManager.IsInRoleAsync:
{"Cannot create a DbSet for 'IdentityUserRole' because this type is not included in the model for the context."}
However I have a custom User Role:
public class ApplicationUserRole : IdentityUserRole<int>
{
public ApplicationUserRole() : base() { }
public bool IsSystemEssential { get; set; }
}
With a definition in ApplicationDbContext :
public DbSet ApplicationUserRoles { get; set; }
SOLVED (provisionally)
After looking at the overloaded inheritance options you have to extend as follows:
public class ApplicationUserStore : UserStore<ApplicationUser, ApplicationRole, ApplicationDbContext, int, ApplicationUserClaim, ApplicationUserRole, ApplicationUserLogin, ApplicationUserToken, ApplicationRoleClaim>
{
public ApplicationUserStore(ApplicationDbContext context, IdentityErrorDescriber describer = null) : base(context, describer)
{
}
}
This seems to work. Still need to check all role and claim updates.
You can change your ApplicationUserStore as following.
public class ApplicationUserStore : UserStore<ApplicationUser,IdentityRole<int>, ApplicationDbContext,int>
{
public ApplicationUserStore(ApplicationDbContext context, IdentityErrorDescriber describer = null) : base(context, describer)
{
}
//...
}

Is there a nice pattern for implementing an abstract DbContext in EF7 / .Net Core 3 to avoid duplication of shared entities / config across projects?

I have a number of different projects that all implement the same schema for configuration, security and audit and are looking for a pattern that would allow me to put these schema definitions in an abstract classes (entity, configuration and dbcontext) that can be extended in the concrete implementations if needed. My current POC fails when the base configurations is applied. I get:
A key cannot be configured on 'UserRole' because it is a derived type. The key must be configured on the root type.
Any help / pointers will be greatly appreciated!
I have the following code samples....
Abstract base classes
RoleBase
public abstract class RoleBase
{
public RoleBase()
{
this.UserRoles = new List<UserRoles>();
}
public long Id { get; set; }
public string Name { get; set; }
public virtual IEnumerable<UserRoleBase> UserRoles { get; set; }
}
UserBase
public abstract class UserBase
{
public long Id { get; set; }
public string Username { get; set; }
public string Email { get; set; }
public virtual ICollection<UserRoleBase> UserRoles { get; set; }
}
UserRoleBase
public abstract class UserRoleBase
{
public long Id { get; set; }
public long RoleId { get; set; }
public long UserId { get; set; }
public bool Deleted { get; set; }
public virtual RoleBase Role { get; set; }
public virtual UserBase User { get; set; }
}
Each of these have an abstract configuration class for the base class...
RoleBase Configuration
public abstract class RoleConfiguration<T> : IEntityTypeConfiguration<T>
where T : RoleBase
{
public virtual void Configure(EntityTypeBuilder<T> builder)
{
// Primary Key
builder.HasKey(t => t.Id);
// Properties
builder.Property(t => t.Name)
.IsRequired()
.HasMaxLength(50);
// Table & Column Mappings
builder.ToTable("Role", "Security");
builder.Property(t => t.Id).HasColumnName("Id");
builder.Property(t => t.Name).HasColumnName("Name");
}
}
UserBase Configuration
public abstract class UserConfiguration<TBase> : IEntityTypeConfiguration<TBase>
where TBase : UserBase
{
public virtual void Configure(EntityTypeBuilder<TBase> builder)
{
// Primary Key
builder.HasKey(t => t.Id);
// Properties
builder.Property(t => t.Username).IsRequired().HasMaxLength(255);
builder.Property(t => t.Email).IsRequired().HasMaxLength(255);
// Table & Column Mappings
builder.ToTable("User", "Security");
builder.Property(t => t.Id).HasColumnName("Id");
builder.Property(t => t.Username).HasColumnName("Username");
builder.Property(t => t.Email).HasColumnName("Email");
}
}
UserRoleBase Configuration
public abstract class UserRoleConfiguration<T> : IEntityTypeConfiguration<T>
where T : UserRoleBase
{
public virtual void Configure(EntityTypeBuilder<T> builder)
{
// Primary Key
builder.HasKey(t => t.Id);
// Properties
builder.Property(t => t.RoleId).IsRequired();
builder.Property(t => t.UserId).IsRequired();
builder.Property(t => t.Deleted).IsRequired();
// Table & Column Mappings
builder.ToTable("UserRole", "Security");
builder.Property(t => t.Id).HasColumnName("Id");
builder.Property(t => t.RoleId).HasColumnName("RoleId");
builder.Property(t => t.UserId).HasColumnName("UserId");
builder.Property(t => t.Deleted).HasColumnName("Deleted");
// Relationships
builder.HasOne(t => t.Role)
.WithMany(t => (ICollection<TBase>)t.UserRoles)
.HasForeignKey(d => d.RoleId)
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(t => t.UserDetail)
.WithMany(t => (ICollection<TBase>)t.UserRoles)
.HasForeignKey(d => d.UserDetailId)
.OnDelete(DeleteBehavior.Restrict);
}
And a concrete implementation of the base classes:
Role
public class Role : RoleBase
{
}
User
public class User : UserBase
{
// Extension properties
public string FirstName { get; set; }
public string LastName { get; set; }
public string Phone { get; set; }
public string Mobile { get; set; }
}
UserRole
public class UserRole : UserRoleBase
{
}
And a concrete implementation of the configuration
RoleConfiguration
public class RoleConfiguration : Base.Configurations.RoleConfiguration<Role>
{
public override void Configure(EntityTypeBuilder<Role> builder)
{
base.Configure(builder);
this.ConfigureEntity(builder);
}
private void ConfigureEntity(EntityTypeBuilder<Role> builder)
{
}
}
UserConfiguration
public class UserConfiguration : Base.Configurations.UserConfiguration<User>
{
public override void Configure(EntityTypeBuilder<User> builder)
{
base.Configure(builder);
this.ConfigureEntity(builder);
}
private void ConfigureEntity(EntityTypeBuilder<User> builder)
{
//Registration of extension properties
builder.Property(t => t.FirstName).HasColumnName("FirstName");
builder.Property(t => t.LastName).HasColumnName("LastName");
builder.Property(t => t.Phone).HasColumnName("Phone");
builder.Property(t => t.Mobile).HasColumnName("Mobile");
}
}
UserRoleConfiguration
public class UserRoleConfiguration : Base.Configurations.UserRoleConfiguration<UserRole>
{
public override void Configure(EntityTypeBuilder<UserRole> builder)
{
base.Configure(builder);
this.ConfigureEntity(builder);
}
private void ConfigureEntity(EntityTypeBuilder<UserRole> builder)
{
}
}
And the base context
public abstract class BaseDbContext: DbContext
{
public BaseDbContext(DbContextOptions<BaseDbContext> options)
: base(options)
{
}
// https://github.com/aspnet/EntityFramework.Docs/issues/594
protected BaseDbContext(DbContextOptions options)
: base(options)
{
}
public DbSet<RoleBase> Roles { get; set; }
public DbSet<UserBase> Users { get; set; }
public DbSet<UserRoleBase> UserRoles { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
And the concrete context
public class MyDbContext: BaseDbContext
{
public MyDbContext(DbContextOptions<MyDbContext> options)
:base(options)
{
}
protected MyDbContext(DbContextOptions options)
: base(options)
{
}
public new DbSet<Role> Roles { get; set; }
public new DbSet<User> Users { get; set; }
public new DbSet<UserRole> UserRoles { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new RoleConfiguration());
modelBuilder.ApplyConfiguration(new UserConfiguration());
modelBuilder.ApplyConfiguration(new UserRoleConfiguration());
base.OnModelCreating(modelBuilder);
}
}
So all this works for items that does not have navigation properties and migrates to the database fine as long as there is no navigation properties. I can see the extension properties on User being done as long as I comment out all the navigation properties.
With the navigation properties present, I get an error on the base configuration class. after concrete implementation called base.Configure(builder);
I get the following error message on builder.HasKey(t => t.Id); and for the above sample code it would be on...
public abstract class UserRoleConfiguration<T> : IEntityTypeConfiguration<T>
where T : UserRoleBase
{
public virtual void Configure(EntityTypeBuilder<T> builder)
{
// Primary Key
builder.HasKey(t => t.Id);
System.InvalidOperationException: 'A key cannot be configured on 'UserRole' because it is a derived type. The key must be configured on the root type 'UserRoleBase'. If you did not intend for 'UserRoleBase' to be included in the model, ensure that it is not included in a DbSet property on your context, referenced in a configuration call to ModelBuilder, or referenced from a navigation property on a type that is included in the model.'
Is there a way in which I can keep these relational configuration in the abstract base class so that I would not need to replicate it in each concrete implementation of the base classes? Or is there a different approach that can be followed to overcome this issue?
System.InvalidOperationException: 'A key cannot be configured on 'UserRole' because it is a derived type. The key must be configured on the root type 'UserRoleBase'. If you did not intend for 'UserRoleBase' to be included in the model, ensure that it is not included in a DbSet property on your context, referenced in a configuration call to ModelBuilder, or referenced from a navigation property on a type that is included in the model.'
From the error, you could use Key attribute on the id of the base model to specify the primary key .
From breaking changes included in EF Core 3.0 , ToTable on a derived type throws an exception , currently it isn't valid to map a derived type to a different table. This change avoids breaking in the future when it becomes a valid thing to do.
You could use Data Annotations on the base model to configure the table that a type maps to:
[Table("Role", Schema = "Security")]
public abstract class RoleBase
{
public RoleBase()
{
this.UserRoles = new List<UserRoles>();
}
[Key]
public long Id { get; set; }
public string Name { get; set; }
public virtual ICollection<UserRoleBase> UserRoles { get; set; }
}

ASP.NET Core: DbSet to executing raw stored procedure

I am using ASP.NET Core 2.0 and razor pages. The SQL table does not have a primary key and I will not be able to make changes to the SQL table. I am trying to use a stored procedure to retrieve the data from the table and show the resultant data in UI.
Since there is no primary key available, I am getting error as - Microsoft.EntityFrameworkCore.Infrastructure.ModelValidator.ValidateNonNullPrimaryKeys. I would like to move the code from DBSet to Raw sql as defined in https://www.learnentityframeworkcore.com/raw-sql
Below is my existing code :
//Data - myDbContext
public class MyDbContext : DbContext
{
public DbSet<LOB> lobs { get; set; }
public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)
{
}
}
// Model
public class LOB
{
public string Desc { get; set; }
}
//Index.cshtml.cs
public class IndexModel : PageModel
{
private readonly MyDbContext _dbContext;
public IndexModel(MyDbContext dbContext)
{
_dbContext = dbContext;
}
public List<LOB> lOBs { get; set; } = new List<LOB>();
[BindProperty]
public string[] SelectedLOBs { get; set; }
public SelectList LOBOptions { get; set; }
public async Task OnGetAsync()
{
lOBs = await _dbContext.Set<LOB>().FromSql(
"EXECUTE sp")
.AsNoTracking()
.ToListAsync();
LOBOptions = new SelectList(lOBs, "Desc1");
}
}
// Index.cshtml
<select class="form-control" required multiple id="selLOB" asp-for="SelectedLOBs" asp-items="Model.LOBOptions"></select>
How to fill the dropdown using context.database property ?
Thanks
For Asp.Net Core and EF Core are different. Asp.Net Core 2.0 is corresponding to Microsoft.AspNetCore.All 2.0 and EF Core 2.1 is correspoinding to Microsoft.EntityFrameworkCore 2.1, you could refer Microsoft.EntityFrameworkCore 2.1 in Microsoft.AspNetCore.All 2.0.
Follow steps below to resolve your issue.
Update package Microsoft.EntityFrameworkCore to V2.2.3 and Microsoft.EntityFrameworkCore.Tools to V2.2.3
Change DbSet to DbQuery
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbQuery<LOB> lobs { 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);
}
}
Change your razor code.
public async Task OnGetAsync()
{
lOBs = await _dbContext.Query<LOB>().FromSql(
"EXECUTE sp")
.AsNoTracking()
.ToListAsync();
LOBOptions = new SelectList(lOBs, "Desc", "Desc", "Desc1");
}
Change your view
<select class="form-control" required multiple id="selLOB"
asp-for="SelectedLOBs" asp-items="Model.LOBOptions">
</select>

DbContext.set() cannot create a DbSet for entity because this type is not included in the model for the context

I am using EF Core. I am using DbContext.Set() method but it is giving me the error - "Cannot create a DbSet for 'MediaDate' because this type is not included in the model for the context.'"
Below is my code:
var context = new GoldentaurusContext();
DbSet<MediaDate> set = context.Set<MediaDate>();
mediaDateList = set.FromSql("[dbo].[sp_GetMediaDate]")
.Select(x => new SelectListItem { Text = x.DateText, Value = x.DateValue })
.ToList();
The MediaDate class:
public class MediaDate
{
public string DateText { get; set; }
public string DateValue { get; set; }
}
Why it is requiring me to add the MediaDate class to the DbContext class?
Please help what I am doing wrong?
Your DB Context class should be like below.
public partial class DatabaseContext : DbContext
{
public DatabaseContext (string ConnectionString) : base(new DbContextOptionsBuilder().UseSqlServer(ConnectionString).Options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Query<MediaData>();
}
Add your models in the DatabaseContext using model builder.
This is how I have resolved this isssue
For EF in DotNet Core 3.1+ make sure you add your non-table entity to the OnModelCreating override on your DbContext and call .HasNoKey() on the fluent API. This will allow you to call a stored procedure using DataContext.Set<MediaData>().FromSqlRaw("dbo.MyStoredProc") and return your entity list.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<MediaData>().HasNoKey();
}
A simple DatabaseContext would look like this:
using YourProject.Model;
using System.Data.Entity;
namespace YourProject.Data
{
public class DatabaseContext : DbContext
{
public DatabaseContext() :
base("name=YourDatabase")
{
}
public DbSet<MediaData> MediaDates{ get; set; }
}
}
You always need to include your models in the DatabaseContext to create the DbSets. Make sure you've declared the right namespaces and imported the right ones.
First, you should introduce your model (MediaDate) to DbContext.
add a DbSet<MediaDate> property to your context:
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext()
: base("DefaultConnection")
{
}
public DbSet<MediaDate> MediaDates { get; set; }
}