Merge pull request #840 from nolt/fix-admin-panel-account-search

Search accounts in the database instead of loading all of them

(cherry picked from commit 4e806cbe64f855a765e864c0c2ff45238f487e36)
This commit is contained in:
sven-n
2026-07-22 21:57:18 +02:00
committed by Acentech Dev
parent e910845383
commit 67cc57cf31
4 changed files with 50 additions and 9 deletions

View File

@@ -100,6 +100,26 @@ internal class PlayerContext : CachingEntityFrameworkContext, IPlayerContext
}
}
/// <inheritdoc />
public async ValueTask<IEnumerable<DataModel.Entities.Account>> SearchAccountsAsync(string searchTerm, int skip, int count, CancellationToken cancellationToken = default)
{
using (this.RepositoryProvider.ContextStack.UseContext(this))
{
// Invariant: this one runs in .NET, so it must not depend on the server's locale - in a
// Turkish one, "I".ToLower() is a dotless "ı" and the term would match nothing. The
// ToLower() calls inside the query below are a different matter: they are translated to
// the database's own lower(), which is why they cannot take a culture.
var term = searchTerm.ToLowerInvariant();
return await this.Context.Set<Account>().AsNoTracking()
.Where(a => a.LoginName.ToLower().Contains(term)
|| a.RawCharacters.Any(c => c.Name.ToLower().Contains(term)))
.OrderBy(a => a.LoginName)
.Skip(skip)
.Take(count)
.ToListAsync(cancellationToken).ConfigureAwait(false);
}
}
/// <inheritdoc />
public async ValueTask<DataModel.Entities.Account?> GetAccountByCharacterNameAsync(string characterName, CancellationToken cancellationToken = default)
{

View File

@@ -78,6 +78,19 @@ public interface IPlayerContext : IContext
/// </returns>
ValueTask<IEnumerable<Account>> GetAccountsOrderedByLoginNameAsync(int skip, int count, CancellationToken cancellationToken = default);
/// <summary>
/// Gets the accounts which contain the search term in their login name or in one of
/// their character names, ordered by login name.
/// </summary>
/// <param name="searchTerm">The search term.</param>
/// <param name="skip">The skip count.</param>
/// <param name="count">The count.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>
/// The account objects, without dependent data.
/// </returns>
ValueTask<IEnumerable<Account>> SearchAccountsAsync(string searchTerm, int skip, int count, CancellationToken cancellationToken = default);
/// <summary>
/// Gets the account by character name.
/// </summary>

View File

@@ -57,6 +57,18 @@ public class PlayerInMemoryContext : InMemoryContext, IPlayerContext
return allAccounts.OrderBy(a => a.LoginName).Skip(skip).Take(count);
}
/// <inheritdoc/>
public async ValueTask<IEnumerable<MUnique.OpenMU.DataModel.Entities.Account>> SearchAccountsAsync(string searchTerm, int skip, int count, CancellationToken cancellationToken = default)
{
var allAccounts = await this.Provider.GetRepository<Account>().GetAllAsync(cancellationToken).ConfigureAwait(false);
return allAccounts
.Where(a => a.LoginName.Contains(searchTerm, StringComparison.InvariantCultureIgnoreCase)
|| a.Characters.Any(c => c.Name.Contains(searchTerm, StringComparison.InvariantCultureIgnoreCase)))
.OrderBy(a => a.LoginName)
.Skip(skip)
.Take(count);
}
/// <inheritdoc/>
public async ValueTask<bool> CanSaveLetterAsync(Interfaces.LetterHeader letterHeader, CancellationToken cancellationToken = default)
{

View File

@@ -80,18 +80,14 @@ public class AccountService : IDataService<Account>, ISupportDataChangedNotifica
return (await playerContext.GetAccountsOrderedByLoginNameAsync(offset, count).ConfigureAwait(false)).ToList();
}
var allAccounts = await playerContext.GetAsync<Account>().ConfigureAwait(false);
var results = allAccounts.Where(account =>
(account.LoginName != null && account.LoginName.Contains(filter, StringComparison.InvariantCultureIgnoreCase))
|| account.Characters.Any(c => c.Name != null && c.Name.Contains(filter, StringComparison.InvariantCultureIgnoreCase))
).ToList();
if (offset >= results.Count)
var results = (await playerContext.SearchAccountsAsync(filter, offset, count).ConfigureAwait(false)).ToList();
if (results.Count == 0 && offset > 0)
{
return results.Take(count).ToList();
// The filter narrowed the result set down to less entries than the current page offset - show the first page instead.
results = (await playerContext.SearchAccountsAsync(filter, 0, count).ConfigureAwait(false)).ToList();
}
return results.Skip(offset).Take(count).ToList();
return results;
}
catch
{