Migrating from MariaDB to MongoDB using EF Core Provider
Hello! I am trying to migrate from MariaDB to MongoDB using MongoDB EF Core Provider. When attempting to use RoleManager.AddClaimAsync(TRole role, Claim claim)
I get a [Microsoft.AspNetCore.Identity.RoleManager] Role Manager error
.
Stack Trace
The property ‘IdentityRoleClaim.Id’ does not have a value set and no value generator is available for properties of type ‘int’. Either set a value for the property before adding the entity or configure a value generator for properties of type ‘int’ in ‘OnModelCreating’.
Here is the stack trace:
at Microsoft.EntityFrameworkCore.ValueGeneration.ValueGeneratorSelector.Create(IProperty property, ITypeBase typeBase)
at Microsoft.EntityFrameworkCore.ValueGeneration.ValueGeneratorSelector.<Select>b__6_0(IProperty p, ITypeBase t)
at Microsoft.EntityFrameworkCore.ValueGeneration.ValueGeneratorCache.<>c.<GetOrAdd>b__7_0(CacheKey ck, ValueTuple`3 p)
at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd[TArg](TKey key, Func`3 valueFactory, TArg factoryArgument)
at Microsoft.EntityFrameworkCore.ValueGeneration.ValueGeneratorCache.GetOrAdd(IProperty property, ITypeBase typeBase, Func`3 factory)
at Microsoft.EntityFrameworkCore.ValueGeneration.ValueGeneratorSelector.Select(IProperty property, ITypeBase typeBase)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ValueGenerationManager.GetValueGenerator(IProperty property)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ValueGenerationManager.Generate(InternalEntityEntry entry, Boolean includePrimaryKey)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState entityState, Boolean acceptChanges, Boolean modifyProperties, Nullable`1 forceStateWhenUnknownKey, Nullable`1 fallbackState)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.PaintAction(EntityEntryGraphNode`1 node)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraph[TState](EntityEntryGraphNode`1 node, Func`2 handleNode)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.AttachGraph(InternalEntityEntry rootEntry, EntityState targetState, EntityState storeGeneratedWithKeySetTargetState, Boolean forceStateWhenUnknownKey)
at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.SetEntityState(InternalEntityEntry entry, EntityState entityState)
at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.Add(TEntity entity)
at Microsoft.AspNetCore.Identity.EntityFrameworkCore.RoleStore`5.AddClaimAsync(TRole role, Claim claim, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Identity.RoleManager`1.AddClaimAsync(TRole role, Claim claim)
DbContext Implementation
Here is my DbContext implementation:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, string> , IApplicationDbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<FidoStoredCredential> FidoStoredCredential => Set<FidoStoredCredential>();
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<FidoStoredCredential>().HasKey(m => m.Id);
base.OnModelCreating(builder);
}
}
Identity Registration Code
Here is my identity registration code:
services.AddDbContextFactory<ApplicationDbContext>(options =>
options.UseMongoDB(_mongoClient, "DatabaseName")
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking));
services.AddIdentity<ApplicationUser, ApplicationRole>(options => {
options.SignIn.RequireConfirmedAccount = false;
options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddRoleManager<RoleManagerScopedService<ApplicationRole>>()
.AddUserManager<UserManagerScopedService<ApplicationUser>>()
.AddDefaultTokenProviders()
.AddTokenProvider<Fido2UserTwoFactorTokenProvider<ApplicationUser>>("FIDO2")
;
User and Role Models
My IdentityUser and IdentityRole implementations aren’t very sophisticated, I simply extend IdentityUser and IdentityRole adding a few properties:
public class ApplicationUser : IdentityUser
{
public ApplicationUser() : base() { }
public ApplicationUser(string userName) : base(userName) { }
public string FullName { get; set; } = string.Empty;
/// <summary> Profile picture. </summary>
public byte[] Picture { get; set; } = Array.Empty<byte>();
/// <summary> Indicates if this account belong to a human
/// or is it some kind of service account. </summary>
public bool IsHuman { get; set; } = true;
/// <summary> Indicates if interactive login is allowed for this account. </summary>
public bool IsLoginAllowed { get; set; } = true;
/// <summary> Indicates if user can be deleted </summary>
public bool IsDeleteAllowed { get; set; } = true;
/// <summary> The last time user was active. </summary>
public DateTimeOffset LastActive { get; set; } = DateTimeOffset.UtcNow;
/// <summary> Indicates if user should be logged out upon their session timeout. </summary>
public bool IsSessionTimeoutAllowed { get; set; } = true;
public DateTimeOffset LastPasswordChangeDate { get; set; } = DateTimeOffset.UtcNow;
}
public class ApplicationRole : IdentityRole
{
public ApplicationRole():base(){}
public ApplicationRole(string roleName):base(roleName){}
[Required]
public override string Name { get => base.Name; set => base.Name = value; }
/// <summary> Description of the role </summary>
public string Description { get;set;} = string.Empty;
/// <summary> Indicates if role is enabled. </summary>
public bool Enabled { get;set;} = true;
/// <summary> Indicates if role can be deleted. </summary>
public bool IsDeleteAllowed { get; set; } = true;
/// <summary> Indicates if role can be disabled. </summary>
public bool IsDisableAllowed { get; set; } = true;
public byte[] MappedAdGroupSid { get; set; } = Array.Empty<byte>();
public string MappedAdGroupName { get; set; } = string.Empty;
public uint DisplayPriority { get; set; } = 0;
/// <summary> Role picture. </summary>
public byte[] Picture { get; set; } = Array.Empty<byte>();
}