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>