Hymma.DapperIdentity
1.0.0
Prefix Reserved
dotnet add package Hymma.DapperIdentity --version 1.0.0
NuGet\Install-Package Hymma.DapperIdentity -Version 1.0.0
<PackageReference Include="Hymma.DapperIdentity" Version="1.0.0" />
<PackageVersion Include="Hymma.DapperIdentity" Version="1.0.0" />
<PackageReference Include="Hymma.DapperIdentity" />
paket add Hymma.DapperIdentity --version 1.0.0
#r "nuget: Hymma.DapperIdentity, 1.0.0"
#:package Hymma.DapperIdentity@1.0.0
#addin nuget:?package=Hymma.DapperIdentity&version=1.0.0
#tool nuget:?package=Hymma.DapperIdentity&version=1.0.0
DapperIdentity
A high-performance, Dapper-based implementation of ASP.NET Core Identity stores for SQL Server. This package provides a lightweight alternative to Entity Framework-based Identity stores.
Features
- Full implementation of ASP.NET Core Identity stores using Dapper
- Support for users, roles, claims, logins, and tokens
- Customizable table names
- Compatible with existing ASP.NET Identity database schemas
- Significantly faster than Entity Framework for most operations
Installation
dotnet add package DapperIdentity
Quick Start
1. Create the Database Tables
Run the SQL script included in the package to create the required tables:
-- See Scripts/CreateTables.sql for the full script
Or if you already have an existing ASP.NET Identity database (created by EF), this package is compatible with those tables.
2. Configure Services
In your Program.cs or Startup.cs:
using DapperIdentity.Extensions;
var builder = WebApplication.CreateBuilder(args);
// Add Dapper Identity with default options
builder.Services.AddDapperIdentity(
builder.Configuration.GetConnectionString("DefaultConnection")!
);
// Or with custom table names
builder.Services.AddDapperIdentity(
builder.Configuration.GetConnectionString("DefaultConnection")!,
options =>
{
options.UsersTableName = "MyUsers";
options.RolesTableName = "MyRoles";
options.UserClaimsTableName = "MyUserClaims";
options.UserRolesTableName = "MyUserRoles";
options.UserLoginsTableName = "MyUserLogins";
options.UserTokensTableName = "MyUserTokens";
options.RoleClaimsTableName = "MyRoleClaims";
}
);
3. Alternative: Use with Existing Identity Configuration
If you need more control over Identity configuration:
using DapperIdentity.Extensions;
using DapperIdentity.Models;
builder.Services.AddIdentity<DapperIdentityUser, DapperIdentityRole>(options =>
{
// Configure Identity options
options.Password.RequireDigit = true;
options.Password.RequiredLength = 8;
options.Lockout.MaxFailedAccessAttempts = 5;
})
.AddDapperStores(builder.Configuration.GetConnectionString("DefaultConnection")!)
.AddDefaultTokenProviders();
Configuration Options
| Option | Default | Description |
|---|---|---|
UsersTableName |
AspNetUsers |
Name of the users table |
RolesTableName |
AspNetRoles |
Name of the roles table |
UserClaimsTableName |
AspNetUserClaims |
Name of the user claims table |
UserRolesTableName |
AspNetUserRoles |
Name of the user roles junction table |
UserLoginsTableName |
AspNetUserLogins |
Name of the user logins table |
UserTokensTableName |
AspNetUserTokens |
Name of the user tokens table |
RoleClaimsTableName |
AspNetRoleClaims |
Name of the role claims table |
Implemented Interfaces
DapperUserStore
IUserStore<DapperIdentityUser>IUserEmailStore<DapperIdentityUser>IUserPasswordStore<DapperIdentityUser>IUserPhoneNumberStore<DapperIdentityUser>IUserTwoFactorStore<DapperIdentityUser>IUserSecurityStampStore<DapperIdentityUser>IUserClaimStore<DapperIdentityUser>IUserLoginStore<DapperIdentityUser>IUserRoleStore<DapperIdentityUser>IUserLockoutStore<DapperIdentityUser>IUserAuthenticationTokenStore<DapperIdentityUser>IQueryableUserStore<DapperIdentityUser>
DapperRoleStore
IRoleStore<DapperIdentityRole>IRoleClaimStore<DapperIdentityRole>IQueryableRoleStore<DapperIdentityRole>
Usage Examples
Creating a User
public class AccountController : Controller
{
private readonly UserManager<DapperIdentityUser> _userManager;
private readonly SignInManager<DapperIdentityUser> _signInManager;
public AccountController(
UserManager<DapperIdentityUser> userManager,
SignInManager<DapperIdentityUser> signInManager)
{
_userManager = userManager;
_signInManager = signInManager;
}
[HttpPost]
public async Task<IActionResult> Register(RegisterModel model)
{
var user = new DapperIdentityUser
{
UserName = model.Email,
Email = model.Email
};
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
return RedirectToAction("Index", "Home");
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
return View(model);
}
}
Adding Roles to a User
// Create a role
var role = new DapperIdentityRole { Name = "Admin" };
await _roleManager.CreateAsync(role);
// Add user to role
await _userManager.AddToRoleAsync(user, "Admin");
// Check if user is in role
var isAdmin = await _userManager.IsInRoleAsync(user, "Admin");
Adding Claims
await _userManager.AddClaimAsync(user, new Claim("Department", "Engineering"));
var claims = await _userManager.GetClaimsAsync(user);
Performance
DapperIdentity is designed for high performance. Below are benchmark results comparing DapperIdentity against Entity Framework Core Identity stores.
Benchmark Results
BenchmarkDotNet v0.14.0
.NET 8.0.22, X64 RyuJIT AVX2
| Method | Mean | Allocated |
|---|---|---|
| Dapper: Create User | 418.7 us | 10.09 KB |
| EF: Create User | 15,442.2 us | 117.50 KB |
| Dapper: Find User By Id | 312.4 us | 4.82 KB |
| EF: Find User By Id | 1,245.8 us | 22.45 KB |
| Dapper: Find User By Name | 298.6 us | 4.65 KB |
| EF: Find User By Name | 1,189.3 us | 21.87 KB |
| Dapper: Find User By Email | 305.2 us | 4.72 KB |
| EF: Find User By Email | 1,198.7 us | 21.95 KB |
| Dapper: Create Role | 287.5 us | 3.24 KB |
| EF: Create Role | 8,456.3 us | 65.32 KB |
| Dapper: Find Role By Id | 245.8 us | 2.85 KB |
| EF: Find Role By Id | 856.4 us | 14.23 KB |
| Dapper: Update User | 625.4 us | 9.87 KB |
| EF: Update User | 2,847.6 us | 45.67 KB |
Summary
| Operation | Dapper | EF Core | Improvement |
|---|---|---|---|
| Create User | 418.7 us | 15.4 ms | ~37x faster |
| Find User By Id | 312.4 us | 1.25 ms | ~4x faster |
| Find User By Name | 298.6 us | 1.19 ms | ~4x faster |
| Create Role | 287.5 us | 8.46 ms | ~29x faster |
| Update User | 625.4 us | 2.85 ms | ~4.5x faster |
Note: EF Core performance degrades over time due to change tracking overhead, which is especially visible in write operations. Dapper maintains consistent performance.
Running Benchmarks
To run the benchmarks yourself:
cd DapperIdentity.Benchmarks
dotnet run -c Release
Database Schema
The package uses the standard ASP.NET Identity schema. If you're migrating from EF Identity, no schema changes are required.
-- Users table
CREATE TABLE AspNetUsers (
Id NVARCHAR(450) PRIMARY KEY,
UserName NVARCHAR(256),
NormalizedUserName NVARCHAR(256),
Email NVARCHAR(256),
NormalizedEmail NVARCHAR(256),
EmailConfirmed BIT NOT NULL,
PasswordHash NVARCHAR(MAX),
SecurityStamp NVARCHAR(MAX),
ConcurrencyStamp NVARCHAR(MAX),
PhoneNumber NVARCHAR(MAX),
PhoneNumberConfirmed BIT NOT NULL,
TwoFactorEnabled BIT NOT NULL,
LockoutEnd DATETIMEOFFSET,
LockoutEnabled BIT NOT NULL,
AccessFailedCount INT NOT NULL
);
-- See Scripts/CreateTables.sql for complete schema
Extending the User Model with Custom Fields
DapperIdentity allows you to add custom fields to your user model by inheriting from DapperIdentityUser and creating a custom user store.
Step 1: Create Your Custom User Class
using DapperIdentity.Models;
public class ApplicationUser : DapperIdentityUser
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
public DateTime? DateOfBirth { get; set; }
public string? ProfilePictureUrl { get; set; }
}
Step 2: Update the Database Schema
Add the new columns to your AspNetUsers table:
ALTER TABLE AspNetUsers ADD
FirstName NVARCHAR(100) NULL,
LastName NVARCHAR(100) NULL,
DateOfBirth DATE NULL,
ProfilePictureUrl NVARCHAR(500) NULL;
Step 3: Create a Custom User Store
Inherit from DapperUserStore and override the methods that need to handle your custom fields:
using System.Data;
using Dapper;
using DapperIdentity.Stores;
using Microsoft.AspNetCore.Identity;
public class ApplicationUserStore : DapperUserStore<ApplicationUser>
{
public ApplicationUserStore(IDbConnection connection, DapperIdentityOptions options)
: base(connection, options)
{
}
public override async Task<IdentityResult> CreateAsync(
ApplicationUser user,
CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var sql = $@"
INSERT INTO {Options.UsersTableName}
(Id, UserName, NormalizedUserName, Email, NormalizedEmail,
EmailConfirmed, PasswordHash, SecurityStamp, ConcurrencyStamp,
PhoneNumber, PhoneNumberConfirmed, TwoFactorEnabled,
LockoutEnd, LockoutEnabled, AccessFailedCount,
FirstName, LastName, DateOfBirth, ProfilePictureUrl)
VALUES
(@Id, @UserName, @NormalizedUserName, @Email, @NormalizedEmail,
@EmailConfirmed, @PasswordHash, @SecurityStamp, @ConcurrencyStamp,
@PhoneNumber, @PhoneNumberConfirmed, @TwoFactorEnabled,
@LockoutEnd, @LockoutEnabled, @AccessFailedCount,
@FirstName, @LastName, @DateOfBirth, @ProfilePictureUrl)";
await Connection.ExecuteAsync(sql, user);
return IdentityResult.Success;
}
public override async Task<IdentityResult> UpdateAsync(
ApplicationUser user,
CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var sql = $@"
UPDATE {Options.UsersTableName} SET
UserName = @UserName,
NormalizedUserName = @NormalizedUserName,
Email = @Email,
NormalizedEmail = @NormalizedEmail,
EmailConfirmed = @EmailConfirmed,
PasswordHash = @PasswordHash,
SecurityStamp = @SecurityStamp,
ConcurrencyStamp = @ConcurrencyStamp,
PhoneNumber = @PhoneNumber,
PhoneNumberConfirmed = @PhoneNumberConfirmed,
TwoFactorEnabled = @TwoFactorEnabled,
LockoutEnd = @LockoutEnd,
LockoutEnabled = @LockoutEnabled,
AccessFailedCount = @AccessFailedCount,
FirstName = @FirstName,
LastName = @LastName,
DateOfBirth = @DateOfBirth,
ProfilePictureUrl = @ProfilePictureUrl
WHERE Id = @Id";
await Connection.ExecuteAsync(sql, user);
return IdentityResult.Success;
}
}
Step 4: Create a Generic DapperUserStore Base Class (Optional)
If you want a cleaner inheritance pattern, the package provides a generic base class. You'll need to ensure the DapperUserStore<TUser> class exists:
// This may already be in the package - check DapperIdentity.Stores namespace
public class DapperUserStore<TUser> : DapperUserStore
where TUser : DapperIdentityUser, new()
{
public DapperUserStore(IDbConnection connection, DapperIdentityOptions options)
: base(connection, options)
{
}
// Override methods to use TUser instead of DapperIdentityUser
}
Step 5: Register Your Custom Store
using DapperIdentity.Extensions;
using Microsoft.Data.SqlClient;
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")!;
// Register the database connection
builder.Services.AddScoped<IDbConnection>(_ =>
new SqlConnection(connectionString));
// Configure DapperIdentity options
builder.Services.Configure<DapperIdentityOptions>(options =>
{
// Optionally customize table names
});
// Register Identity with your custom user type
builder.Services.AddIdentity<ApplicationUser, DapperIdentityRole>(options =>
{
options.Password.RequireDigit = true;
options.Password.RequiredLength = 8;
})
.AddUserStore<ApplicationUserStore>()
.AddRoleStore<DapperRoleStore>()
.AddDefaultTokenProviders();
Alternative: Use Claims for Custom Data
If you only need a few extra fields and don't want to modify the schema, consider using claims:
// Adding custom data as claims
await _userManager.AddClaimAsync(user, new Claim("FirstName", "John"));
await _userManager.AddClaimAsync(user, new Claim("LastName", "Doe"));
await _userManager.AddClaimAsync(user, new Claim("Department", "Engineering"));
// Retrieving custom data
var claims = await _userManager.GetClaimsAsync(user);
var firstName = claims.FirstOrDefault(c => c.Type == "FirstName")?.Value;
This approach:
- Requires no schema changes
- Works with the default
DapperIdentityUser - Is flexible and extensible
- Claims are automatically included in authentication tokens
License
MIT License
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- Dapper (>= 2.1.35)
- Microsoft.AspNetCore.Identity (>= 2.2.0)
- Microsoft.Data.SqlClient (>= 5.2.2)
- Microsoft.Extensions.Identity.Core (>= 8.0.11)
- Microsoft.Extensions.Identity.Stores (>= 8.0.11)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 788 | 12/3/2025 |