Skip to content

WIP: Add EF6 back Identity Core storage#656

Draft
twsouthwick wants to merge 1 commit intomainfrom
identity-core
Draft

WIP: Add EF6 back Identity Core storage#656
twsouthwick wants to merge 1 commit intomainfrom
identity-core

Conversation

@twsouthwick
Copy link
Member

This implements a backing store for ASP.NET Core Identity that uses EF6. This is intended for a bridge during migration to move to ASP.NET Core Identity before migrating to Entity Framework Core. By doing this, people can separate out the migration of identity and ef.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request implements an Entity Framework 6 backing store for ASP.NET Core Identity, intended as a migration bridge allowing users to adopt ASP.NET Core Identity while maintaining their existing EF6 database infrastructure before migrating to EF Core.

Changes:

  • Added EF6-based store implementations (RoleStoreEF6, UserStoreEF6) for ASP.NET Core Identity
  • Created EF6-compatible entity models that extend ASP.NET Core Identity base classes
  • Implemented a sample Blazor application demonstrating the EF6 Identity integration

Reviewed changes

Copilot reviewed 96 out of 141 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
RoleStoreEF6.cs Implements role storage using EF6 with multiple generic overloads
IdentityEF6DbContext.cs EF6 DbContext configured for ASP.NET Framework Identity schema compatibility
IdentityUserEF6.cs, IdentityRoleEF6.cs Entity models extending ASP.NET Core Identity with EF6 navigation properties
IdentityEntityFramework6BuilderExtensions.cs DI extension methods for registering EF6 stores
README.md Documentation explaining EF6 vs EF Core distinction and usage
AuthIdentityCoreEF6/* Complete sample Blazor application using the EF6 Identity implementation

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

entry.State = EntityState.Modified;
}

role.ConcurrencyStamp = Guid.NewGuid().ToString();
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ConcurrencyStamp is being updated unconditionally on every update operation, but in the context of EF6 and ASP.NET Identity v1 schema compatibility (where ConcurrencyStamp is ignored per IdentityEF6DbContext.cs line 213-214), this stamp update won't be persisted to the database. This creates a mismatch where the in-memory object has a new stamp but the database doesn't reflect it, which could lead to inconsistent behavior in concurrency checking.

Copilot uses AI. Check for mistakes.
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
// Query against Name with normalization in the query since NormalizedName is not mapped
return Roles.FirstOrDefaultAsync(r => r.Name.ToUpper() == normalizedName, cancellationToken);
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The normalization logic uses ToUpper() for case-insensitive comparison, but this may not work correctly for all cultures and database collations. If the database uses a case-sensitive collation, this query will not match as expected. Additionally, ToUpper() in a LINQ query may not translate to SQL correctly in all scenarios with EF6. Consider using a culture-invariant comparison or ensuring the database collation matches the expected behavior.

Copilot uses AI. Check for mistakes.
Comment on lines +373 to +383
foreach (var claim in claims)
{
var matchedClaims = await Context.Set<TUserClaim>()
.Where(uc => uc.UserId.Equals(user.Id) && uc.ClaimValue == claim.Value && uc.ClaimType == claim.Type)
.ToListAsync(cancellationToken);

foreach (var c in matchedClaims)
{
Context.Set<TUserClaim>().Remove(c);
}
}
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This foreach loop immediately maps its iteration variable to another variable - consider mapping the sequence explicitly using '.Select(...)'.

Copilot uses AI. Check for mistakes.
Comment on lines +133 to +145
private async ValueTask LoadSharedKeyAndQrCodeUriAsync(ApplicationUser user)
{
// Load the authenticator key & QR code URI to display on the form
var unformattedKey = await UserManager.GetAuthenticatorKeyAsync(user);
if (string.IsNullOrEmpty(unformattedKey))
{
await UserManager.ResetAuthenticatorKeyAsync(user);
unformattedKey = await UserManager.GetAuthenticatorKeyAsync(user);
}

sharedKey = FormatKey(unformattedKey!);

var email = await UserManager.GetEmailAsync(user);
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Local scope variable 'user' shadows EnableAuthenticator.user.

Suggested change
private async ValueTask LoadSharedKeyAndQrCodeUriAsync(ApplicationUser user)
{
// Load the authenticator key & QR code URI to display on the form
var unformattedKey = await UserManager.GetAuthenticatorKeyAsync(user);
if (string.IsNullOrEmpty(unformattedKey))
{
await UserManager.ResetAuthenticatorKeyAsync(user);
unformattedKey = await UserManager.GetAuthenticatorKeyAsync(user);
}
sharedKey = FormatKey(unformattedKey!);
var email = await UserManager.GetEmailAsync(user);
private async ValueTask LoadSharedKeyAndQrCodeUriAsync(ApplicationUser applicationUser)
{
// Load the authenticator key & QR code URI to display on the form
var unformattedKey = await UserManager.GetAuthenticatorKeyAsync(applicationUser);
if (string.IsNullOrEmpty(unformattedKey))
{
await UserManager.ResetAuthenticatorKeyAsync(applicationUser);
unformattedKey = await UserManager.GetAuthenticatorKeyAsync(applicationUser);
}
sharedKey = FormatKey(unformattedKey!);
var email = await UserManager.GetEmailAsync(applicationUser);

Copilot uses AI. Check for mistakes.
Comment on lines +130 to +143
if (identityContext == null)
{
userStoreType = typeof(UserOnlyStoreEF6<,,>).MakeGenericType(userType, contextType, keyType);
}
else
{
userStoreType = typeof(UserOnlyStoreEF6<,,,,,>).MakeGenericType(
userType,
contextType,
identityContext.GenericTypeArguments[2], // TKey
identityContext.GenericTypeArguments[5], // TUserClaim
identityContext.GenericTypeArguments[3], // TUserLogin
typeof(IdentityUserTokenEF6<>).MakeGenericType(identityContext.GenericTypeArguments[2])); // TUserToken
}
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both branches of this 'if' statement write to the same variable - consider using '?' to express intent better.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant