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.Identity;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using MySql.Data.MySqlClient;
|
||||||
|
|
||||||
namespace Jiaowu.Api.Controllers;
|
namespace Jiaowu.Api.Controllers;
|
||||||
|
|
||||||
@@ -33,6 +34,14 @@ public sealed class UsersController(
|
|||||||
if (page < 1 || pageSize is < 1 or > 100)
|
if (page < 1 || pageSize is < 1 or > 100)
|
||||||
return ValidationProblem("页码必须大于 0,且每页条数应在 1 至 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();
|
var query = userManager.Users.AsNoTracking();
|
||||||
if (!string.IsNullOrWhiteSpace(keyword))
|
if (!string.IsNullOrWhiteSpace(keyword))
|
||||||
{
|
{
|
||||||
@@ -111,6 +120,107 @@ public sealed class UsersController(
|
|||||||
return Ok(new PagedResult<UserListItem>(items, total, page, pageSize));
|
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")]
|
[HttpGet("roles")]
|
||||||
public async Task<ActionResult<object>> GetRoles(CancellationToken cancellationToken) =>
|
public async Task<ActionResult<object>> GetRoles(CancellationToken cancellationToken) =>
|
||||||
Ok(await roleManager.Roles.AsNoTracking()
|
Ok(await roleManager.Roles.AsNoTracking()
|
||||||
|
|||||||
Reference in new issue
Block a user