Work around MySQL Identity user query failure
This commit is contained in:
1 parent
b483857421
commit
4f031d4b60
1 file changed
+110
@@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MySql.Data.MySqlClient;
|
||||
|
||||
namespace Jiaowu.Api.Controllers;
|
||||
|
||||
@@ -33,6 +34,14 @@ public sealed class UsersController(
|
||||
if (page < 1 || pageSize is < 1 or > 100)
|
||||
return ValidationProblem("页码必须大于 0,且每页条数应在 1 至 100 之间。");
|
||||
|
||||
// Connector/NET 10.0.9 currently has a provider-side command creation
|
||||
// failure with this Identity query on EF Core 10.0.11. Keep the normal
|
||||
// LINQ path for SQLite/tests and bypass only this affected MySQL read.
|
||||
if (db.Database.ProviderName?.Contains("MySql", StringComparison.OrdinalIgnoreCase) == true)
|
||||
return Ok(await GetUsersFromMySqlAsync(
|
||||
page, pageSize, keyword, roleName, collegeId, isEnabled,
|
||||
hasLoggedIn, cancellationToken));
|
||||
|
||||
var query = userManager.Users.AsNoTracking();
|
||||
if (!string.IsNullOrWhiteSpace(keyword))
|
||||
{
|
||||
@@ -111,6 +120,107 @@ public sealed class UsersController(
|
||||
return Ok(new PagedResult<UserListItem>(items, total, page, pageSize));
|
||||
}
|
||||
|
||||
private async Task<PagedResult<UserListItem>> GetUsersFromMySqlAsync(
|
||||
int page,
|
||||
int pageSize,
|
||||
string? keyword,
|
||||
string? roleName,
|
||||
Guid? collegeId,
|
||||
bool? isEnabled,
|
||||
bool? hasLoggedIn,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var clauses = new List<string>();
|
||||
if (!string.IsNullOrWhiteSpace(keyword))
|
||||
clauses.Add("(" +
|
||||
"u.`UserName` LIKE CONCAT('%', @keyword, '%') OR " +
|
||||
"u.`DisplayName` LIKE CONCAT('%', @keyword, '%') OR " +
|
||||
"u.`StaffNumber` LIKE CONCAT('%', @keyword, '%') OR " +
|
||||
"EXISTS (SELECT 1 FROM `AspNetUserRoles` ur " +
|
||||
"JOIN `AspNetRoles` r ON ur.`RoleId` = r.`Id` " +
|
||||
"WHERE ur.`UserId` = u.`Id` AND r.`Name` LIKE CONCAT('%', @keyword, '%')))" );
|
||||
if (!string.IsNullOrWhiteSpace(roleName))
|
||||
clauses.Add("EXISTS (SELECT 1 FROM `AspNetUserRoles` ur " +
|
||||
"JOIN `AspNetRoles` r ON ur.`RoleId` = r.`Id` " +
|
||||
"WHERE ur.`UserId` = u.`Id` AND r.`Name` = @roleName)");
|
||||
if (collegeId.HasValue) clauses.Add("u.`CollegeId` = @collegeId");
|
||||
if (isEnabled.HasValue) clauses.Add("u.`IsEnabled` = @isEnabled");
|
||||
if (hasLoggedIn.HasValue) clauses.Add(hasLoggedIn.Value
|
||||
? "u.`LastLoginAt` IS NOT NULL" : "u.`LastLoginAt` IS NULL");
|
||||
var where = clauses.Count == 0 ? string.Empty : " WHERE " + string.Join(" AND ", clauses);
|
||||
|
||||
var connection = (MySqlConnection)db.Database.GetDbConnection();
|
||||
await connection.OpenAsync(cancellationToken);
|
||||
try
|
||||
{
|
||||
await using var countCommand = CreateCommand(
|
||||
connection, "SELECT COUNT(*) FROM `AspNetUsers` u" + where,
|
||||
keyword, roleName, collegeId, isEnabled);
|
||||
var total = Convert.ToInt32(await countCommand.ExecuteScalarAsync(cancellationToken));
|
||||
|
||||
await using var usersCommand = CreateCommand(connection,
|
||||
"SELECT u.`Id`, u.`UserName`, u.`DisplayName`, u.`StaffNumber`, u.`CollegeId`, " +
|
||||
"u.`IsEnabled`, u.`LastLoginAt`, u.`CreatedAt` FROM `AspNetUsers` u" + where +
|
||||
" ORDER BY u.`UserName`, u.`Id` LIMIT @take OFFSET @skip",
|
||||
keyword, roleName, collegeId, isEnabled);
|
||||
usersCommand.Parameters.AddWithValue("@take", pageSize);
|
||||
usersCommand.Parameters.AddWithValue("@skip", (page - 1) * pageSize);
|
||||
var users = new List<UserListItem>();
|
||||
await using (var reader = await usersCommand.ExecuteReaderAsync(cancellationToken))
|
||||
{
|
||||
while (await reader.ReadAsync(cancellationToken))
|
||||
{
|
||||
var id = Guid.Parse(reader.GetString(0));
|
||||
users.Add(new UserListItem(id, reader.GetString(1), reader.GetString(2),
|
||||
reader.IsDBNull(3) ? null : reader.GetString(3),
|
||||
reader.IsDBNull(4) ? null : Guid.Parse(reader.GetString(4)),
|
||||
Convert.ToBoolean(reader.GetValue(5)),
|
||||
reader.IsDBNull(6) ? null : reader.GetDateTime(6),
|
||||
reader.GetDateTime(7), Array.Empty<string>()));
|
||||
}
|
||||
}
|
||||
|
||||
var rolesByUser = new Dictionary<Guid, List<string>>();
|
||||
if (users.Count > 0)
|
||||
{
|
||||
await using var rolesCommand = new MySqlCommand(
|
||||
"SELECT ur.`UserId`, r.`Name` FROM `AspNetUserRoles` ur " +
|
||||
"JOIN `AspNetRoles` r ON ur.`RoleId` = r.`Id` WHERE ur.`UserId` IN (" +
|
||||
string.Join(", ", users.Select((_, index) => "@user" + index)) + ")", connection);
|
||||
foreach (var (user, index) in users.Select((user, index) => (user, index)))
|
||||
rolesCommand.Parameters.AddWithValue("@user" + index, user.Id.ToString());
|
||||
await using var roleReader = await rolesCommand.ExecuteReaderAsync(cancellationToken);
|
||||
while (await roleReader.ReadAsync(cancellationToken))
|
||||
{
|
||||
var userId = Guid.Parse(roleReader.GetString(0));
|
||||
if (!rolesByUser.TryGetValue(userId, out var roles)) rolesByUser[userId] = roles = [];
|
||||
roles.Add(roleReader.GetString(1));
|
||||
}
|
||||
}
|
||||
var items = users.Select(user => user with
|
||||
{
|
||||
Roles = rolesByUser.GetValueOrDefault(user.Id, []).OrderBy(x => x).ToArray()
|
||||
}).ToArray();
|
||||
return new PagedResult<UserListItem>(items, total, page, pageSize);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await connection.CloseAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private static MySqlCommand CreateCommand(
|
||||
MySqlConnection connection, string sql, string? keyword, string? roleName,
|
||||
Guid? collegeId, bool? isEnabled)
|
||||
{
|
||||
var command = new MySqlCommand(sql, connection);
|
||||
if (!string.IsNullOrWhiteSpace(keyword)) command.Parameters.AddWithValue("@keyword", keyword.Trim());
|
||||
if (!string.IsNullOrWhiteSpace(roleName)) command.Parameters.AddWithValue("@roleName", roleName.Trim());
|
||||
if (collegeId.HasValue) command.Parameters.AddWithValue("@collegeId", collegeId.Value.ToString());
|
||||
if (isEnabled.HasValue) command.Parameters.AddWithValue("@isEnabled", isEnabled.Value);
|
||||
return command;
|
||||
}
|
||||
|
||||
[HttpGet("roles")]
|
||||
public async Task<ActionResult<object>> GetRoles(CancellationToken cancellationToken) =>
|
||||
Ok(await roleManager.Roles.AsNoTracking()
|
||||
|
||||
Reference in new issue
Block a user