2040d4abce
* perf: batch-load folders, accounts, and contacts in FetchMailsAsync Replace the sequential per-mail property-loading loop with a three-step batch pre-load strategy, eliminating the N+1 DB call pattern that was the main bottleneck when building the mail list with threading enabled. Changes: - Pre-seed the folder cache from MailListInitializationOptions.Folders so that the most common folders (inbox, sent, etc.) never trigger a DB lookup at all. - Load all accounts in a single GetAccountsAsync() call instead of one GetAccountAsync() call per mail (typically 1–5 accounts total). - Fetch all sender contacts in a single SQL IN(...) query via the new GetContactsByAddressesAsync() method instead of one query per address. - Property assignment is now fully synchronous (no awaits in the loop) since all data is pre-loaded into plain Dictionary<K,V>. - Thread-expansion follows the same pattern: new folder IDs are loaded in parallel via Task.WhenAll; new contact addresses are batch-fetched with a second IN(...) query. - Also apply batch pre-loading to GetMailItemsAsync (used by merge-inbox sync path) which had the same sequential issue. - Remove the now-unused LoadAssignedPropertiesWithCacheAsync helper and the ConcurrentDictionary dependency it required. - Tighten GetMailsByThreadIdsAsync to skip the Id NOT IN clause entirely when the exclusion set is empty. https://claude.ai/code/session_018bqahGc6zi95JJhc2aARKS * test: add MailFetchingTests with correctness and performance coverage Adds integration tests for MailService.FetchMailsAsync that exercise the full real-service stack (MailService → FolderService / AccountService / ContactService) backed by the shared in-memory SQLite helper. Four tests are included: • ExpandsSiblingsOutsidePage – proves thread expansion fetches mails that fall beyond the initial SQL page (6 mails, page=4, expects 6 returned). • NeverExpandsSiblings – proves threading is truly opt-in; with CreateThreads=false the result exactly matches the raw page size. • ResolvesFromAllThreeSources – verifies contact resolution for a known contact (from the AccountContact table), an unknown sender (ad-hoc fallback), and a self-sent mail (built from account metadata). • 1000Mails_70Threads_CompletesWithinBudget – the performance scenario: 1 000 mails (70 threads × 7 + 510 standalone), 40 rotating sender addresses (20 with DB contacts). Times and reports two scenarios: - Default first-page fetch (100 mails) + expansion of one partial thread (expects > 100 mails returned). - Full load of all 1 000 mails with threading enabled (expects exactly 1 000 mails returned, all 70 threads intact, < 5 s). Elapsed times for both scenarios are written to xUnit test output so they appear in CI logs and can be tracked across builds. https://claude.ai/code/session_018bqahGc6zi95JJhc2aARKS --------- Co-authored-by: Claude <noreply@anthropic.com>
27 lines
1.2 KiB
C#
27 lines
1.2 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using MimeKit;
|
|
using Wino.Core.Domain.Entities.Shared;
|
|
using Wino.Core.Domain.Models.Contacts;
|
|
|
|
namespace Wino.Core.Domain.Interfaces;
|
|
|
|
public interface IContactService
|
|
{
|
|
Task<List<AccountContact>> GetAddressInformationAsync(string queryText);
|
|
Task<AccountContact> GetAddressInformationByAddressAsync(string address);
|
|
Task<List<AccountContact>> GetContactsByAddressesAsync(IEnumerable<string> addresses);
|
|
Task SaveAddressInformationAsync(MimeMessage message);
|
|
Task SaveAddressInformationAsync(IEnumerable<AccountContact> contacts);
|
|
Task<AccountContact> CreateNewContactAsync(string address, string displayName);
|
|
|
|
// New methods for ContactsPage
|
|
Task<List<AccountContact>> GetAllContactsAsync();
|
|
Task<List<AccountContact>> SearchContactsAsync(string searchQuery);
|
|
Task<PagedContactsResult> GetContactsPageAsync(int offset, int pageSize, string searchQuery = null, bool excludeRootContacts = false);
|
|
Task<AccountContact> UpdateContactAsync(AccountContact contact);
|
|
Task DeleteContactAsync(string address);
|
|
Task DeleteContactsAsync(IEnumerable<string> addresses);
|
|
}
|