让 IdentityManager 在两个不同的数据库上进行更改
Getting IdentityManager to make changes on two different databases
我们有两个数据库,其中 User table 如下所示:
- MasterDB.AspNetUsers
- Customer1DB.AspNetUsers
我们使用 MasterDb 登录用户,然后将他们连接到客户数据库,例如 Customer1DB(或一些其他客户数据库取决于用户)。两个用户 table 都具有基于 ASP.NET Identity Framework 的相同模式,我们可以使用 IdentityManager 来管理 MasterDb.
上的用户
现在我想要的是对 MasterDb 用户记录执行的任何更改,将其镜像到客户数据库中的用户 table(具有相同的用户 ID)。我想知道最好的方法是什么?我需要修改UserStore
、RoleStore
、UserManager
、RoleManager
上的所有操作吗?我有一个获取 UserId 并将其从第一个数据库添加或更新到第二个数据库的函数,但我不确定我应该如何将它准确地集成到身份框架实现中。
谢谢!
我想你有两个选择:
为您需要的商店和功能创建适配器。适配器将在两个数据库上调用相同的方法。
在主 DbContext 中,当您保存更改时,您查看 ChangeTracker 并将更改复制到另一个 DbContext。
每种方法各有优缺点。
1
示例适配器:
class MultiDatabaseUserStore<T> : IUserStore<T>
where T : IdentityUser
{
private readonly UserStore<T>[] stores;
public MultiDatabaseUserStore(params IdentityDbContext[] dbContexts)
{
if (dbContexts == null || dbContexts.Length <= 0)
{
throw new ArgumentException("At least one db context is required.", "dbContexts");
}
this.stores = dbContexts.Select(x => new UserStore<T>(x)).ToArray();
}
public void Dispose()
{
foreach (var store in this.stores)
{
store.Dispose();
}
}
public Task CreateAsync(T user)
{
return this.ExecuteOnAll(x => x.CreateAsync(user));
}
public Task UpdateAsync(T user)
{
return this.ExecuteOnAll(x => x.UpdateAsync(user));
}
public Task DeleteAsync(T user)
{
return this.ExecuteOnAll(x => x.DeleteAsync(user));
}
public Task<T> FindByIdAsync(string userId)
{
return this.stores.First().FindByIdAsync(userId);
}
public Task<T> FindByNameAsync(string userName)
{
return this.stores.First().FindByNameAsync(userName);
}
private Task ExecuteOnAll(Func<UserStore<T>, Task> function)
{
return Task.WhenAll(this.stores.Select(function));
}
}
它是这样集成的:
var store = new MultiDatabaseUserStore<IdentityUser>(new MasterDb(), new Customer1DB());
var userManager = new UserManager<IdentityUser>(store);
这可行,但根据您的需要,您将有更多接口要实现:
- IUserLoginStore
- IUserClaimStore
- IUserRoleStore
- IUserPasswordStore
- IUserSecurityStampStore
- IQueryableUserStore
- IUserEmailStore
- IUserPhoneNumberStore
- IUserTwoFactorStore
- IUserLockoutStore
2
示例主 DbContext:
class MasterDb : IdentityDbContext
{
public MasterDb()
{
}
public override int SaveChanges()
{
var changes = this.GetChangesToReplicate();
var i = base.SaveChanges();
this.SaveReplicatedChanges(changes);
return i;
}
public override Task<int> SaveChangesAsync()
{
return Task.Run(async () =>
{
var changes = this.GetChangesToReplicate();
var i = await base.SaveChangesAsync();
this.SaveReplicatedChanges(changes);
return i;
});
}
private void SaveReplicatedChanges(IEnumerable<Action<IdentityDbContext>> changes)
{
if (changes != null)
{
using (var db = new Customer1DB())
{
foreach (var change in changes)
{
change(db);
}
db.SaveChanges();
}
}
}
private IEnumerable<Action<IdentityDbContext>> GetChangesToReplicate()
{
var actions = new List<Action<IdentityDbContext>>();
var userTye = typeof(IdentityUser);
var changedEntries = this.ChangeTracker.Entries();
var users = changedEntries.Where(x => x.Entity.GetType() == userTye).ToList();
foreach (var u in users)
{
switch (u.State)
{
case EntityState.Added:
var userToAdd = (IdentityUser)u.Entity;
actions.Add(db => db.Users.Add(userToAdd));
break;
case EntityState.Modified:
var userToUpdate = (IdentityUser)u.Entity; ;
actions.Add(db =>
{
db.Users.Attach(userToUpdate);
db.Entry(userToUpdate).State = EntityState.Modified;
});
break;
case EntityState.Deleted:
var userToDelete = (IdentityUser)u.Entity; ;
actions.Add(db =>
{
db.Users.Attach(userToDelete);
db.Entry(userToDelete).State = EntityState.Deleted;
});
break;
}
}
return actions;
}
}
这也有效,但仅限于用户实体。您将必须根据需要添加角色、声明等的复制。
2 改进
考虑一下,您可以跳过类型检查,直接将所有更改复制到另一个 DbContext。这大大简化了代码,并且适用于 roles/claims。您还需要添加一个 bool 属性 IsReplicatingChanges
以仅在必要时启用复制。
您必须仅为身份存储创建一个实例并将标志设置为 true
:
var dbContext = new MasterDb { IsReplicatingChanges = true };
var userManager = new UserManager<IdentityUser>(new UserStore<IdentityUser>(dbContext));
var roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(dbContext));
并且 GetChangesToReplicate()
变为:
private IEnumerable<Action<IdentityDbContext>> GetChangesToReplicate()
{
if (!this.IsReplicatingChanges) // Flag
{
// Return null when not activated
return null;
}
var actions = new List<Action<IdentityDbContext>>();
var changedEntries = this.ChangeTracker.Entries().Where(x => x.State != EntityState.Unchanged);
foreach (var u in changedEntries)
{
var entity = u.Entity;
var state = u.State;
actions.Add(db =>
{
db.Set(entity.GetType()).Attach(entity);
db.Entry(entity).State = state;
});
}
return actions;
}
并且不要忘记将空检查添加到 SaveReplicatedChanges()
。
我们有两个数据库,其中 User table 如下所示:
- MasterDB.AspNetUsers
- Customer1DB.AspNetUsers
我们使用 MasterDb 登录用户,然后将他们连接到客户数据库,例如 Customer1DB(或一些其他客户数据库取决于用户)。两个用户 table 都具有基于 ASP.NET Identity Framework 的相同模式,我们可以使用 IdentityManager 来管理 MasterDb.
上的用户现在我想要的是对 MasterDb 用户记录执行的任何更改,将其镜像到客户数据库中的用户 table(具有相同的用户 ID)。我想知道最好的方法是什么?我需要修改UserStore
、RoleStore
、UserManager
、RoleManager
上的所有操作吗?我有一个获取 UserId 并将其从第一个数据库添加或更新到第二个数据库的函数,但我不确定我应该如何将它准确地集成到身份框架实现中。
谢谢!
我想你有两个选择:
为您需要的商店和功能创建适配器。适配器将在两个数据库上调用相同的方法。
在主 DbContext 中,当您保存更改时,您查看 ChangeTracker 并将更改复制到另一个 DbContext。
每种方法各有优缺点。
1
示例适配器:
class MultiDatabaseUserStore<T> : IUserStore<T>
where T : IdentityUser
{
private readonly UserStore<T>[] stores;
public MultiDatabaseUserStore(params IdentityDbContext[] dbContexts)
{
if (dbContexts == null || dbContexts.Length <= 0)
{
throw new ArgumentException("At least one db context is required.", "dbContexts");
}
this.stores = dbContexts.Select(x => new UserStore<T>(x)).ToArray();
}
public void Dispose()
{
foreach (var store in this.stores)
{
store.Dispose();
}
}
public Task CreateAsync(T user)
{
return this.ExecuteOnAll(x => x.CreateAsync(user));
}
public Task UpdateAsync(T user)
{
return this.ExecuteOnAll(x => x.UpdateAsync(user));
}
public Task DeleteAsync(T user)
{
return this.ExecuteOnAll(x => x.DeleteAsync(user));
}
public Task<T> FindByIdAsync(string userId)
{
return this.stores.First().FindByIdAsync(userId);
}
public Task<T> FindByNameAsync(string userName)
{
return this.stores.First().FindByNameAsync(userName);
}
private Task ExecuteOnAll(Func<UserStore<T>, Task> function)
{
return Task.WhenAll(this.stores.Select(function));
}
}
它是这样集成的:
var store = new MultiDatabaseUserStore<IdentityUser>(new MasterDb(), new Customer1DB());
var userManager = new UserManager<IdentityUser>(store);
这可行,但根据您的需要,您将有更多接口要实现:
- IUserLoginStore
- IUserClaimStore
- IUserRoleStore
- IUserPasswordStore
- IUserSecurityStampStore
- IQueryableUserStore
- IUserEmailStore
- IUserPhoneNumberStore
- IUserTwoFactorStore
- IUserLockoutStore
2
示例主 DbContext:
class MasterDb : IdentityDbContext
{
public MasterDb()
{
}
public override int SaveChanges()
{
var changes = this.GetChangesToReplicate();
var i = base.SaveChanges();
this.SaveReplicatedChanges(changes);
return i;
}
public override Task<int> SaveChangesAsync()
{
return Task.Run(async () =>
{
var changes = this.GetChangesToReplicate();
var i = await base.SaveChangesAsync();
this.SaveReplicatedChanges(changes);
return i;
});
}
private void SaveReplicatedChanges(IEnumerable<Action<IdentityDbContext>> changes)
{
if (changes != null)
{
using (var db = new Customer1DB())
{
foreach (var change in changes)
{
change(db);
}
db.SaveChanges();
}
}
}
private IEnumerable<Action<IdentityDbContext>> GetChangesToReplicate()
{
var actions = new List<Action<IdentityDbContext>>();
var userTye = typeof(IdentityUser);
var changedEntries = this.ChangeTracker.Entries();
var users = changedEntries.Where(x => x.Entity.GetType() == userTye).ToList();
foreach (var u in users)
{
switch (u.State)
{
case EntityState.Added:
var userToAdd = (IdentityUser)u.Entity;
actions.Add(db => db.Users.Add(userToAdd));
break;
case EntityState.Modified:
var userToUpdate = (IdentityUser)u.Entity; ;
actions.Add(db =>
{
db.Users.Attach(userToUpdate);
db.Entry(userToUpdate).State = EntityState.Modified;
});
break;
case EntityState.Deleted:
var userToDelete = (IdentityUser)u.Entity; ;
actions.Add(db =>
{
db.Users.Attach(userToDelete);
db.Entry(userToDelete).State = EntityState.Deleted;
});
break;
}
}
return actions;
}
}
这也有效,但仅限于用户实体。您将必须根据需要添加角色、声明等的复制。
2 改进
考虑一下,您可以跳过类型检查,直接将所有更改复制到另一个 DbContext。这大大简化了代码,并且适用于 roles/claims。您还需要添加一个 bool 属性 IsReplicatingChanges
以仅在必要时启用复制。
您必须仅为身份存储创建一个实例并将标志设置为 true
:
var dbContext = new MasterDb { IsReplicatingChanges = true };
var userManager = new UserManager<IdentityUser>(new UserStore<IdentityUser>(dbContext));
var roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(dbContext));
并且 GetChangesToReplicate()
变为:
private IEnumerable<Action<IdentityDbContext>> GetChangesToReplicate()
{
if (!this.IsReplicatingChanges) // Flag
{
// Return null when not activated
return null;
}
var actions = new List<Action<IdentityDbContext>>();
var changedEntries = this.ChangeTracker.Entries().Where(x => x.State != EntityState.Unchanged);
foreach (var u in changedEntries)
{
var entity = u.Entity;
var state = u.State;
actions.Add(db =>
{
db.Set(entity.GetType()).Attach(entity);
db.Entry(entity).State = state;
});
}
return actions;
}
并且不要忘记将空检查添加到 SaveReplicatedChanges()
。