![](/img/trans.png)
[英]Identity Server 4 Asp.Net Identity + EF Core not Seeding
[英].NET Core EF + Identity with PostgreSQL
所以我必须使用我们所有的 .net 核心应用程序从 SQL Server 迁移到 PostgreSQL - 数据库本身及其连接工作正常,但是如何使用数据库上下文以及 .NET Identity 扩展使用的异步方法存在问题。 我将使用我们的数据库播种程序类来解释我的问题。
目前我们已经在 Startup.cs 的 ConfigureServices 方法中定义了这样的 dbContext 和 Identity:
services.AddDbContext < ApplicationDbContext > (options =>
options.UseNpgsql(
Configuration.GetConnectionString("DefaultConnection")));
//values seeder
services.AddScoped < Seeder > ();
//http context
services.AddHttpContextAccessor();
//Auth
services.AddDefaultIdentity < User > (options => options.SignIn.RequireConfirmedAccount = true)
.AddRoles < IdentityRole > ()
.AddEntityFrameworkStores < ApplicationDbContext > ();
services.Configure < IdentityOptions > (o =>
{
o.Password.RequireDigit = true;
o.Password.RequireLowercase = true;
o.Password.RequireNonAlphanumeric = true;
o.Password.RequireUppercase = true;
o.Password.RequiredLength = 6;
o.Password.RequiredUniqueChars = 1;
o.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
o.Lockout.MaxFailedAccessAttempts = 5;
o.Lockout.AllowedForNewUsers = true;
o.User.RequireUniqueEmail = true;
});
和我们的 Seeder.cs:
using SomeApp.Data;
using SomeApp.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using System.Linq;
using System.Threading.Tasks;
namespace SomeApp
{
public class Seeder
{
private readonly ApplicationDbContext _dbContext;
private readonly UserManager<User> _userManager;
public Seeder(ApplicationDbContext dbContext, UserManager<User> userManager)
{
_dbContext = dbContext;
_userManager = userManager;
}
public async Task Seed()
{
var roles = new[] { "Superadmin", "User" };
foreach (var role in roles)
{
if (_dbContext.Roles.Any(r => r.Name == role))
continue;
var roleStore = new RoleStore<IdentityRole>(_dbContext);
var r = new IdentityRole(role)
{
NormalizedName = role.ToUpperInvariant()
};
await roleStore.CreateAsync(r);
await _dbContext.SaveChangesAsync();
}
if (_userManager.FindByNameAsync("admin1234").Result is null)
await CreateDefaultUser();
}
private async Task CreateDefaultUser()
{
var user = new User
{
Email = "admin@admin.pl",
FirstName = "Admin",
LastName = "Admin",
UserName = "admin1234",
Department = "IT",
ImagePath = "/assets/user_icon.png",
IsAnonimised = false,
IsLoggedIn = true
};
var result = _userManager.CreateAsync(user, "Test1234_").Result;
if (result.Succeeded)
{
await _userManager.AddToRoleAsync(user, "Superadmin");
}
}
}
}
它不是最漂亮的(这基本上是我们用来学习 .net 核心的项目),但它可以与 SQL Server 一起使用。 情况是它不再与 PostgreSQL 一起使用,因为它会引发如下异常:
Npgsql.NpgsqlOperationInProgressException (0x80004005): The connection is already in state 'Executing'
at Npgsql.Internal.NpgsqlConnector.<StartUserAction>g__DoStartUserAction|255_0(<>c__DisplayClass255_0& )
at Npgsql.Internal.NpgsqlConnector.StartUserAction(ConnectorState newState, NpgsqlCommand command, CancellationToken cancellationToken, Boolean attemptPgCancellation)
at Npgsql.Internal.NpgsqlConnector.StartUserAction(CancellationToken cancellationToken, Boolean attemptPgCancellation)
at Npgsql.Internal.NpgsqlConnector.Reset(Boolean async)
at Npgsql.NpgsqlConnection.CloseAsync(Boolean async)
at Npgsql.NpgsqlConnection.Close()
at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.CloseDbConnection()
at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.Close()
at Microsoft.EntityFrameworkCore.Storage.RelationalTransaction.ClearTransaction()
at Microsoft.EntityFrameworkCore.Storage.RelationalTransaction.Dispose()
at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.ResetState(Boolean disposeDbConnection)
at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.Dispose()
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.Dispose()
at Microsoft.EntityFrameworkCore.DbContext.Dispose()
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.Dispose()
at Microsoft.AspNetCore.Hosting.ConfigureBuilder.Invoke(Object instance, IApplicationBuilder builder)
at Microsoft.AspNetCore.Hosting.ConfigureBuilder.<>c__DisplayClass4_0.<Build>b__0(IApplicationBuilder builder)
at Microsoft.AspNetCore.Hosting.GenericWebHostBuilder.<>c__DisplayClass15_0.<UseStartup>b__1(IApplicationBuilder app)
at Microsoft.AspNetCore.Mvc.Filters.MiddlewareFilterBuilderStartupFilter.<>c__DisplayClass0_0.<Configure>g__MiddlewareFilterBuilder|0(IApplicationBuilder builder)
at Microsoft.AspNetCore.HostFilteringStartupFilter.<>c__DisplayClass0_0.<Configure>b__0(IApplicationBuilder app)
at Microsoft.AspNetCore.Hosting.GenericWebHostService.StartAsync(CancellationToken cancellationToken)
at Microsoft.Extensions.Hosting.Internal.Host.StartAsync(CancellationToken cancellationToken)
at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.Run(IHost host)
at AllegroSerwis.Program.Main(String[] args) in D:\Work\ASPdotNETProjects\SomeApp\SomeApp\Program.cs:line 10
看起来由于某种原因 await 没有暂停循环的其余部分,直到异步方法完成它的工作并且 PostgreSQL 无法处理一个数据库上下文上的多个调用。 我做了一些挖掘,最普遍的意见是我应该使用工厂模式在每次调用时创建单独的实例,而不是只在各处注入一个上下文。 所以我做了:
//db
services.AddDbContextFactory < ApplicationDbContext > (options =>
options.UseNpgsql(
Configuration.GetConnectionString("DefaultConnection")));
并注入工厂而不是上下文,它适用于角色播种循环,我可以在其中创建新的数据库上下文,但不适用于下面使用的 _userManager - 因为它可能从 Startup.cs 获取数据库上下文,并且目前没有在那里定义。 有人可以或多或少地向我解释解决这个问题的最佳实践是什么,并一起使用实体框架 + 身份 + PostgreSQL,最终如何为所有这些实现工厂模式。 在这一点上,我感到非常沮丧,而且 Microsoft 文档缺少任何不是 SQL Server 的东西。
编辑:添加 ApplicationDbContext.cs
public class ApplicationDbContext: IdentityDbContext
{
public DbSet < User > Users
{
get;
set;
}
public DbSet < IdentityRole > Roles
{
get;
set;
}
public DbSet < Summary > Summaries
{
get;
set;
}
public DbSet < Report > Reports
{
get;
set;
}
//report models subsets
public DbSet < LogginData > LogginData
{
get;
set;
}
public DbSet < EnergyCapsules > EnergyCapsules
{
get;
set;
}
public DbSet < BossFightLog > BossFight
{
get;
set;
}
public DbSet < Interaction > Interactions
{
get;
set;
}
public DbSet < CustomerCall > Calls
{
get;
set;
}
public DbSet < Questionaire > Questionaires
{
get;
set;
}
public DbSet < Post > Posts
{
get;
set;
}
public DbSet < FaqItem > FaqItems
{
get;
set;
}
public ApplicationDbContext(DbContextOptions < ApplicationDbContext > options): base(options)
{}
protected override void OnModelCreating(ModelBuilder builder)
{
builder.HasDefaultSchema("public");
base.OnModelCreating(builder);
builder.Entity < User > ()
.Property(u => u.Email)
.IsRequired();
builder.Entity < Role > ()
.Property(r => r.Name)
.IsRequired();
}
}
你不需要await _dbContext.SaveChangesAsync();
因为
RoleStore.CreateAsync()
已经在做这个操作了。
除非必须,否则不要在 for 循环中使用数据库访问。 为此创建列表并使用_dbSet.AddRange()
重载。
确保等待异步数据库调用。
通过应用上述最佳实践,我做了与您相同的事情,并且它有效。 查看我的模板以获取示例。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.