Использование .NET 8 с EF Core и Identity. Какой DbContext использует UserManagerUse? Странная ошибка с UserManagerC#

Место общения программистов C#
Ответить Пред. темаСлед. тема
Anonymous
 Использование .NET 8 с EF Core и Identity. Какой DbContext использует UserManagerUse? Странная ошибка с UserManager

Сообщение Anonymous »

У меня есть проект Blazor, который изначально был написан для .NET 6 и использовал WebAssembly.
Я перенес его на .NET 8 – все еще с WebAssembly, но решил переместить Многие мои функции «Администратора» возвращаются на сервер с использованием нового режима рендеринга «InteractiveServer». Что подойдет и прекрасно работает.
У меня есть куча сервисов, которые взаимодействуют с сервером (стандартные контроллеры), но чтобы это работало корректно И для интерактивного сервера, и для веб-сборки, необходимо было превратить службы в интерфейсы и разместить аналогичные (интерфейсные) службы на сервере, которые в обход контроллера переходят непосредственно на уровень доступа к данным.
В основном я выполнил эту работу. Но мне пришлось преобразовать приложение DbContext для использования DbContextFactory. Это хорошо описано в различных сообщениях Blazor в этом и других блогах. Чтобы избежать подобных ошибок:

Вторая операция была запущена в этом экземпляре контекста до завершения предыдущей операции. Обычно это вызвано тем, что разные потоки одновременно используют один и тот же экземпляр DbContext.

Теперь я получаю такие ошибки отслеживания EF Core:

Экземпляр типа сущности «ApplicationUser» невозможно отследить, поскольку другой экземпляр с тем же значением ключа для {'Id'} уже отслеживается. При присоединении существующих сущностей убедитесь, что присоединен только один экземпляр сущности с заданным значением ключа. Рассмотрите возможность использования DbContextOptionsBuilder.EnableSensitiveDataLogging, чтобы увидеть конфликтующие значения ключей.

Странно, что я вызываю DAL из службы следующим образом:
public async Task UpdateUser(ApplicationUserDTO user)
{
using var _dbContext = _dbContextFactory.CreateDbContext();

bool updatingcurrrentuser = false;

if (user.Id == CurrentUser.Id)
updatingcurrrentuser = true;

InfoBool? result = new();
bool userisAdmin = false;

ApplicationUser? loggedinUser = await _userLib.GetLoggedInUser(_dbContext);

if (_userLib.UserIsAdmin(loggedinUser))
{
userisAdmin = true; // This is to allow admin users to add paper tickets which can override the ticket price check
}

if (userisAdmin || (user.Id == loggedinUser.Id)) // If the logged in user is an admin or the person edited then allow the update
{
// ApplicationUser sentUser = new ApplicationUser();
// _mapper.Map(sentUserDTO, sentUser);
result = await _userLib.UpdateUser(_userManager, _dbContext, user, user.Roles, userisAdmin);
}

// ... more stuff
}

Функция UpdateUser довольно большая, но обрабатывает множество вещей:
public async Task UpdateUser(
UserManager userManager,
RGDbContext context,
ApplicationUserDTO user,
List Roles = null,
bool userisadmin = false)
{
// First check if its there
// ApplicationUser founduser = await userManager.FindByNameAsync(user.UserName);

ApplicationUser? founduser = await context.Users
.Include(x => x.Address)
//.Include(x => x.Player) // Don't need the player
//.ThenInclude(y => y.Coins)
.SingleOrDefaultAsync(x => x.UserName == user.UserName);

if (founduser == null)
{
return new InfoBool(false, "User to update not found");
}

if (founduser.Address != null && user.Address != null && user.Address.Id == 0)
{
// If there is an address but the client sends it with a zero id - to detect a client error
return new InfoBool(false, "Address Id mismatch Error");
}

_mapper.Map(user, founduser);
_mapper.Map(user.Address, founduser.Address);

IList currentroles = await GetRolesAsync(context, founduser.Id);

// First check for changed roles
// Creates a list of the roles in Roles to add not in current roles
List newrolestoadd = Roles.Except(currentroles).ToList();

// Create a list of the roles in current roles to remove not in roles
List rolestoremove = currentroles.Except(Roles).ToList();

if (newrolestoadd.Count() > 0) // There are some
{
// If a player role has been added
if (!currentroles.Contains(Enums.AllowedRoles.Player.ToString())
&& Roles.Contains(Enums.AllowedRoles.Player.ToString()))
{
founduser.IsPlayer = true;
}

if (!userisadmin && newrolestoadd.Contains(Enums.AllowedRoles.Administrator.ToString()))
{
// Just a check that any user adding an adminrole is already an admin
newrolestoadd.Remove(Enums.AllowedRoles.Administrator.ToString()); //Not allowed
// TODO: add a log record here to indicate a possible security risk
}

await AddUsertoRoles(userManager, founduser, newrolestoadd);
}

// Remove roles
if (rolestoremove.Count() > 0)
{
if (!userisadmin && rolestoremove.Contains(Enums.AllowedRoles.Administrator.ToString()))
{
// Only an admin can remove an admin
rolestoremove.Remove(Enums.AllowedRoles.Administrator.ToString());
}

await RemoveUserFromRoles(userManager, founduser, rolestoremove);
}

// Now check the address (we don't check for changes IN the address, just if it exists.
try
{
context.Entry(founduser).State = EntityState.Modified;
await context.SaveChangesAsync();
}
catch (Exception e)
{
return new InfoBool(false, e.Message);
}

return new InfoBool(true, "User update successful");
}

Ошибка отслеживания возникает в недрах UserLib изначально здесь, при добавлении или удалении ролей:
private async Task RemoveUserFromRoles(
UserManager userManager,
ApplicationUser User,
IList RolesToRemove)
{
try
{
IdentityResult result = IdentityResult.Success;
foreach (string role in RolesToRemove)
{
if (await userManager.IsInRoleAsync(User, role))
{
result = await userManager.RemoveFromRoleAsync(User, role);
if (!result.Succeeded) //If not successful the bail with the error
{
return result;
}
}
}
return result;
}
catch (Exception ex)
{
Console.WriteLine( ex.Message );
return IdentityResult.Failed(new IdentityError() { Code="Exception", Description=$"{ex.Message}"});
}
}

Я использую здесь только userManager, и, как вы можете видеть для этих функций, и контекст, и userMananger передаются в верхнюю функцию (UpdateUser). СТРАННО то, что когда я вызываю ту же функцию из контроллера. Этого не происходит и работает нормально.
Для полноты кода контроллера:
public async Task UpdateUser(ApplicationUserDTO sentUserDTO)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
// Get the logged in user.
ApplicationUser loggedinUser = await _userManager.GetUserAsync(User).ConfigureAwait(false);

IList roles = await _userManager.GetRolesAsync(loggedinUser);
bool userisadmin = false;
if (roles.Contains(Enums.AllowedRoles.Administrator.ToString())) userisadmin = true;
if (userisadmin || (sentUserDTO.Id == loggedinUser.Id)) // If the logged in user is an admin or the person edited then allow the update
{
return await _userlib.UpdateUser(_userManager, _context, sentUserDTO, sentUserDTO.Roles, userisadmin);
}
return Unauthorized();
}

Я предполагаю, что тот факт, что я использую contextFactory в коде службы на стороне сервера, каким-то образом означает, что он отличается от контекста, используемого userManager.
Однако Контекст в контроллерах — это просто стандартный, создаваемый в файле program.cs при запуске. Чего мне здесь не хватает?
EDIT Чтобы добавить функцию GetLoggedInUser. Я не думаю, что вы можете выполнить поиск элемента AsNotTracked, но его отсоединение должно помочь, не так ли?
///
/// Uses the ihttpContext to get the current user without using user manager
/// DO NOT USE the value returned to update the DB as it has been detached from the context
///
public async Task GetLoggedInUser(RGDbContext context)
{

var claimslist = _httpContextAccessor.HttpContext.User.Claims.ToList();
string userId = claimslist.Find(x => x.Type.EndsWith("nameidentifier")).Value;
ApplicationUser? user = await context.Users.FindAsync(userId);
if (user == null) return null;
foreach (var claim in claimslist)
{
if (claim.Type.EndsWith("role"))
{
user.Roles.Add(claim.Value);
}
}
context.Entry(user).State = EntityState.Detached;
return user;
}


Подробнее здесь: https://stackoverflow.com/questions/791 ... geruse-str
Реклама
Ответить Пред. темаСлед. тема

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

  • Похожие темы
    Ответы
    Просмотры
    Последнее сообщение

Вернуться в «C#»