Fix Foundatio NRT nullable reference type errors#2202
Fix Foundatio NRT nullable reference type errors#2202
Conversation
- Update Foundatio libraries to the latest beta versions across the Core, Insulation, and Test projects.
…iles (batch 1/3) Foundatio packages upgraded to versions with nullable reference types enabled, causing 135 build errors. This batch fixes 102 errors across 30 files by: - Updating Dictionary<string, string> to Dictionary<string, string?> for connection string data - Changing ILock return types to ILock? for lock acquisition methods - Adding null guards for QueueEntry.Value and WorkItemContext.GetData<T>() which are now nullable - Adding diagnostic logging for null defensive guards to improve observability Remaining 33 errors in Repositories, Services, Billing, and Migrations will be addressed in subsequent batches.
There was a problem hiding this comment.
Pull request overview
Aligns Exceptionless with Foundatio’s updated nullable-reference-type (NRT) APIs, addressing build breaks caused by nullable connection-string parsing, nullable queue/work-item payloads, and nullable lock acquisition.
Changes:
- Updated Foundatio package references and adjusted affected project dependencies.
- Updated configuration option models to use
Dictionary<string, string?>for parsed connection-string data and added null-safe parsing. - Updated jobs and work item handlers for nullable queue/work-item payloads and nullable lock acquisition (
ILock?), adding defensive guards and logging.
Reviewed changes
Copilot reviewed 36 out of 36 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Exceptionless.Tests/Exceptionless.Tests.csproj | Bumps Foundatio Xunit package to a newer beta compatible with NRT changes. |
| src/Exceptionless.Insulation/Exceptionless.Insulation.csproj | Updates Foundatio provider packages to newer betas. |
| src/Exceptionless.Core/Exceptionless.Core.csproj | Updates Foundatio hosting/JsonNet packages and Elasticsearch repo package version. |
| src/Exceptionless.Core/Extensions/DictionaryExtensions.cs | Updates connection-string dictionary extension helpers to accept nullable values. |
| src/Exceptionless.Core/Configuration/StorageOptions.cs | Switches connection-string parsed Data to Dictionary<string, string?>. |
| src/Exceptionless.Core/Configuration/QueueOptions.cs | Switches connection-string parsed Data to Dictionary<string, string?>. |
| src/Exceptionless.Core/Configuration/CacheOptions.cs | Uses nullable-valued connection-string parsing and merges provider options safely. |
| src/Exceptionless.Core/Configuration/MessageBusOptions.cs | Uses nullable-valued connection-string parsing and merges provider options safely. |
| src/Exceptionless.Core/Configuration/MetricOptions.cs | Uses null-safe parsing for potentially-missing metrics connection string. |
| src/Exceptionless.Core/Configuration/ElasticsearchOptions.cs | Uses null-safe parsing for potentially-missing Elasticsearch connection string. |
| src/Exceptionless.Core/Configuration/AuthOptions.cs | Uses null-safe parsing for OAuth connection-string data. |
| src/Exceptionless.Core/Configuration/SlackOptions.cs | Uses null-safe parsing for OAuth connection-string data. |
| src/Exceptionless.Core/Configuration/IntercomOptions.cs | Uses null-safe parsing for OAuth connection-string data. |
| src/Exceptionless.Core/Jobs/WebHooksJob.cs | Adds null guards for queue payload and for nullable WebHook id handling. |
| src/Exceptionless.Core/Jobs/MailMessageJob.cs | Adds null guard for queue payload and avoids re-reading nullable entry value. |
| src/Exceptionless.Core/Jobs/EventUserDescriptionsJob.cs | Adds null guard for queue payload; initializes ev.Data before AddRange. |
| src/Exceptionless.Core/Jobs/EventNotificationsJob.cs | Adds null guard for queue payload. |
| src/Exceptionless.Core/Jobs/EventPostsJob.cs | Adds null guard for queue payload; avoids re-reading entry value after completion; improves logging around nullability. |
| src/Exceptionless.Core/Jobs/StackStatusJob.cs | Updates lock acquisition signature to Task<ILock?>. |
| src/Exceptionless.Core/Jobs/StackEventCountJob.cs | Updates lock acquisition signature to Task<ILock?>. |
| src/Exceptionless.Core/Jobs/EventUsageJob.cs | Updates lock acquisition signature to Task<ILock?>. |
| src/Exceptionless.Core/Jobs/DownloadGeoIPDatabaseJob.cs | Updates lock acquisition signature to Task<ILock?>. |
| src/Exceptionless.Core/Jobs/DailySummaryJob.cs | Updates lock acquisition signature to Task<ILock?>. |
| src/Exceptionless.Core/Jobs/CloseInactiveSessionsJob.cs | Updates lock acquisition signature to Task<ILock?>. |
| src/Exceptionless.Core/Jobs/CleanupOrphanedDataJob.cs | Updates lock acquisition signature to Task<ILock?>. |
| src/Exceptionless.Core/Jobs/CleanupDataJob.cs | Updates lock acquisition signature to Task<ILock?>; filters null hits elements before accessing ids. |
| src/Exceptionless.Core/Jobs/WorkItemHandlers/UserMaintenanceWorkItemHandler.cs | Updates lock acquisition signature to Task<ILock?>; adds null guard for work item data. |
| src/Exceptionless.Core/Jobs/WorkItemHandlers/UpdateProjectNotificationSettingsWorkItemHandler.cs | Updates lock acquisition signature to Task<ILock?>; adds null guard for work item data. |
| src/Exceptionless.Core/Jobs/WorkItemHandlers/SetProjectIsConfiguredWorkItemHandler.cs | Updates lock acquisition signature to Task<ILock?>; adds null guard for work item data. |
| src/Exceptionless.Core/Jobs/WorkItemHandlers/SetLocationFromGeoWorkItemHandler.cs | Updates lock acquisition signature to Task<ILock?>; adds null guard for work item data and Geo string. |
| src/Exceptionless.Core/Jobs/WorkItemHandlers/RemoveStacksWorkItemHandler.cs | Updates lock acquisition signature to Task<ILock?>; adds null guard for work item data. |
| src/Exceptionless.Core/Jobs/WorkItemHandlers/RemoveBotEventsWorkItemHandler.cs | Updates lock acquisition signature to Task<ILock?>; adds null guard for work item data. |
| src/Exceptionless.Core/Jobs/WorkItemHandlers/ProjectMaintenanceWorkItemHandler.cs | Updates lock acquisition signature to Task<ILock?>; adds null guard for work item data. |
| src/Exceptionless.Core/Jobs/WorkItemHandlers/OrganizationNotificationWorkItemHandler.cs | Adds null guard for work item data. |
| src/Exceptionless.Core/Jobs/WorkItemHandlers/OrganizationMaintenanceWorkItemHandler.cs | Updates lock acquisition signature to Task<ILock?>; adds null guard for work item data. |
| src/Exceptionless.Core/Jobs/WorkItemHandlers/FixStackStatsWorkItemHandler.cs | Updates lock acquisition signature to Task<ILock?>; adds null guard for work item data. |
Comments suppressed due to low confidence (3)
src/Exceptionless.Core/Jobs/WorkItemHandlers/SetLocationFromGeoWorkItemHandler.cs:31
AcquireAsync(..., new CancellationToken(true))passes a canceled token, which can prevent acquiring the lock for this work item. Pass through thecancellationTokenparameter instead.
src/Exceptionless.Core/Jobs/WorkItemHandlers/RemoveStacksWorkItemHandler.cs:27new CancellationToken(true)is an already-canceled token; using it with_lockProvider.AcquireAsynccan cause immediate cancellation and prevent lock acquisition. Use the method'scancellationTokenparameter instead.
public override Task<ILock?> GetWorkItemLockAsync(object workItem, CancellationToken cancellationToken = new())
{
string cacheKey = $"{nameof(RemoveStacksWorkItem)}:{((RemoveStacksWorkItem)workItem).ProjectId}";
return _lockProvider.AcquireAsync(cacheKey, TimeSpan.FromMinutes(15), new CancellationToken(true));
}
src/Exceptionless.Core/Jobs/WorkItemHandlers/SetProjectIsConfiguredWorkItemHandler.cs:28
- This passes
new CancellationToken(true)(already canceled) to_lockProvider.AcquireAsync, which can cancel lock acquisition immediately and keep this handler from running under lock. Use the providedcancellationTokeninstead.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| public override Task<ILock> GetWorkItemLockAsync(object workItem, CancellationToken cancellationToken = new()) | ||
| public override Task<ILock?> GetWorkItemLockAsync(object workItem, CancellationToken cancellationToken = new()) | ||
| { | ||
| return _lockProvider.AcquireAsync(nameof(ProjectMaintenanceWorkItemHandler), TimeSpan.FromMinutes(15), new CancellationToken(true)); |
There was a problem hiding this comment.
new CancellationToken(true) is pre-canceled; if _lockProvider.AcquireAsync observes it, this handler won't acquire its work-item lock. Pass the cancellationToken parameter (or CancellationToken.None) instead.
| return _lockProvider.AcquireAsync(nameof(ProjectMaintenanceWorkItemHandler), TimeSpan.FromMinutes(15), new CancellationToken(true)); | |
| return _lockProvider.AcquireAsync(nameof(ProjectMaintenanceWorkItemHandler), TimeSpan.FromMinutes(15), cancellationToken); |
| public override Task<ILock?> GetWorkItemLockAsync(object workItem, CancellationToken cancellationToken = new()) | ||
| { | ||
| return _lockProvider.AcquireAsync(nameof(UserMaintenanceWorkItemHandler), TimeSpan.FromMinutes(15), new CancellationToken(true)); | ||
| } |
There was a problem hiding this comment.
This uses new CancellationToken(true) (already canceled) for lock acquisition, which can cause acquisition to immediately cancel and the handler to run unlocked / not run at all (depending on base behavior). Use the provided cancellationToken instead.
|
|
||
| string cacheKey = $"{nameof(OrganizationNotificationWorkItemHandler)}:{wi.OrganizationId}"; | ||
|
|
||
| return _lockProvider.TryUsingAsync(cacheKey, async () => |
There was a problem hiding this comment.
In this TryUsingAsync call, the CancellationToken argument passed later is new CancellationToken(true), which is already canceled and can cause the lock usage to be canceled immediately (skipping the work). Pass through the caller's cancellation token (or CancellationToken.None) instead.
Foundatio 11.x introduces nullable reference types across infrastructure APIs (ILockProvider, ICacheClient, FindResults, QueueEntry, etc.). Batch 2 adapts Repositories, Services, Extensions, Models, Plugins, Mail, Migrations, Billing, and Utility files to handle nullable returns and stricter nullability contracts. Key changes: - Add null guards after GetByIdAsync (returns T? now) - Use .OfType<T>() to filter null collection elements - Guard nullable keys before ICacheClient.GetAsync - Handle DateRange init-only properties via Remove/re-add pattern - Add logging when operations silently skip missing entities - Change return types where Foundatio APIs now return nullable Brings Exceptionless.Core to 0 build errors. Tests blocked by 8 pre-existing Insulation errors (batch 3).
Copilot review identified 11 instances where \ ew CancellationToken(true)\ (already canceled) was passed to lock acquisition methods. This caused locks to be immediately canceled, preventing jobs/handlers from running. Changed all lock acquisition calls to pass through the method's \cancellationToken\ parameter instead, allowing proper lock acquisition and job execution.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 56 out of 56 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (2)
src/Exceptionless.Core/Jobs/WorkItemHandlers/SetLocationFromGeoWorkItemHandler.cs:31
GetWorkItemLockAsyncpassesnew CancellationToken(true)(already canceled) into_lockProvider.AcquireAsync, which can prevent the lock from ever being acquired. Use thecancellationTokenparameter passed into the method (orcontext.CancellationToken) so lock acquisition can proceed and be canceled correctly.
src/Exceptionless.Core/Jobs/WorkItemHandlers/RemoveStacksWorkItemHandler.cs:27GetWorkItemLockAsyncusesnew CancellationToken(true)when acquiring the lock, which is an already-canceled token and can causeAcquireAsyncto immediately cancel and return no lock. Pass through thecancellationTokenargument instead.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| public override Task<ILock?> GetWorkItemLockAsync(object workItem, CancellationToken cancellationToken = new()) | ||
| { | ||
| string cacheKey = $"{nameof(SetProjectIsConfiguredWorkItemHandler)}:{((SetProjectIsConfiguredWorkItem)workItem).ProjectId}"; | ||
| return _lockProvider.AcquireAsync(cacheKey, TimeSpan.FromMinutes(15), new CancellationToken(true)); |
There was a problem hiding this comment.
GetWorkItemLockAsync ignores the provided cancellationToken and passes new CancellationToken(true) (already-canceled) to AcquireAsync, which can cause lock acquisition to be immediately canceled and the handler to run without a lock (or not run as intended). Pass through the cancellationToken parameter (or context.CancellationToken) instead of creating an always-canceled token.
Foundatio now exposes NRT annotations. This commit adapts all consuming code: - Insulation: update GetAWSCredentials/GetAWSRegionEndpoint parameter types from IDictionary<string,string> to IDictionary<string,string?>, add null check on GeoIP database stream - Web controllers: use null-conditional on aggregation results (Min/Max/Sum/Cardinality/Terms), guard SearchBeforeToken/AfterToken and AggregationsExpression against null, fix OverageMiddleware null dereference, add null guard in MessageBusBroker - Tests: adapt all test assertions to handle nullable returns from repository and aggregation APIs
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 79 out of 79 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (2)
src/Exceptionless.Core/Jobs/WorkItemHandlers/SetLocationFromGeoWorkItemHandler.cs:31
- GetWorkItemLockAsync currently passes new CancellationToken(true) to AcquireAsync instead of the provided cancellationToken. Because the token is already canceled, lock acquisition can be skipped/canceled immediately, defeating the purpose of the work-item lock. Use the cancellationToken parameter (or CancellationToken.None) here.
src/Exceptionless.Core/Jobs/WorkItemHandlers/RemoveStacksWorkItemHandler.cs:27 - GetWorkItemLockAsync passes a pre-canceled CancellationToken (new CancellationToken(true)) to AcquireAsync, which can prevent the lock from being acquired and allow concurrent executions. Use the provided cancellationToken (or CancellationToken.None) instead.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| FirstOccurrence = term.Aggregations.Min<DateTime>("min_date")?.Value ?? DateTime.MinValue, | ||
| LastOccurrence = term.Aggregations.Max<DateTime>("max_date")?.Value ?? DateTime.MinValue, | ||
| Total = (long)(term.Aggregations.Sum("sum_count")?.Value ?? term.Total.GetValueOrDefault()), |
There was a problem hiding this comment.
Falling back to DateTime.MinValue when the min/max aggregations are missing can leak a year-0001 timestamp to API consumers. Since you already have the Stack model, prefer a meaningful fallback (e.g., stack.FirstOccurrence / stack.LastOccurrence) or handle the missing aggregation case explicitly (return 0 results / error).
| FirstOccurrence = term.Aggregations.Min<DateTime>("min_date")?.Value ?? DateTime.MinValue, | ||
| LastOccurrence = term.Aggregations.Max<DateTime>("max_date")?.Value ?? DateTime.MinValue, | ||
| Total = (long)(term.Aggregations.Sum("sum_count")?.Value ?? term.Total.GetValueOrDefault()), |
There was a problem hiding this comment.
Using DateTime.MinValue as a fallback for missing min/max aggregations can return an invalid/meaningless timestamp to clients. Consider falling back to the stack entity’s FirstOccurrence/LastOccurrence (or explicitly handling the missing aggregation case) rather than emitting 0001-01-01.
| var dateRanges = systemFilterQuery.GetDateRanges(); | ||
| var rangesToReplace = dateRanges | ||
| .Where(range => range.Field == _inferredEventDateField || range.Field == "date") | ||
| .ToList(); | ||
|
|
||
| // TODO: Verify remove+add ordering change is functionally equivalent via EventStackFilterQueryTests | ||
| // after Insulation blocker is resolved (was previously in-place mutation). | ||
| foreach (var range in rangesToReplace) | ||
| { | ||
| if (range.Field == _inferredEventDateField || range.Field == "date") | ||
| { | ||
| range.Field = _inferredStackLastOccurrenceField; | ||
| if (isStackIdsNegated) // don't apply retention date filter on inverted stack queries | ||
| range.StartDate = null; | ||
| range.EndDate = null; | ||
| } | ||
| dateRanges.Remove(range); | ||
| systemFilterQuery.DateRange( | ||
| isStackIdsNegated ? null : range.StartDate, // don't apply retention date filter on inverted stack queries | ||
| null, | ||
| _inferredStackLastOccurrenceField, | ||
| range.TimeZone); | ||
| } |
There was a problem hiding this comment.
This hunk changes how date ranges are rewritten (remove+add) and includes a TODO noting the ordering/behavior hasn’t been verified. Given the PR description says “no functional changes”, please either add a focused test (e.g., covering date range rewriting and inverted stack queries) or adjust the implementation to preserve the previous behavior without leaving an unverified TODO in production code.
- Add .keyword sub-field to ip copy-to field in EventIndex so FieldEquals validation passes (Foundatio now rejects TermQuery on analyzed text fields without a keyword sub-field) - Regenerate OpenAPI baseline to reflect updated CountResult schema
- Fix 3 work item handlers that still passed new CancellationToken(true) to AcquireAsync, which would immediately cancel lock acquisition - Replace unverified TODO with descriptive comment (tests pass)
Address Copilot review: fall back to stack.FirstOccurrence and stack.LastOccurrence when aggregation metrics are null, instead of leaking year-0001 timestamps to API consumers.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 81 out of 81 changed files in this pull request and generated 11 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| context.Organization = await _organizationRepository.GetByIdAsync(organizationId, o => o.Cache()); | ||
|
|
||
| if (context.Organization is null) | ||
| throw new InvalidOperationException($"Organization '{organizationId}' not found."); | ||
|
|
There was a problem hiding this comment.
GetEventsLeftAsync now throws when the organization cannot be loaded from the repository. This method is called from request-path middleware (e.g., OverageMiddleware) and background jobs; throwing here will surface as a 500 instead of failing closed (block/402) or safely skipping usage checks. Consider returning 0 (or another fail-closed value) when the org is missing (and optionally logging), or ensure all callers translate this into an appropriate HTTP response/result.
| public override Task<ILock?> GetWorkItemLockAsync(object workItem, CancellationToken cancellationToken = new()) | ||
| { | ||
| string cacheKey = $"{nameof(SetProjectIsConfiguredWorkItemHandler)}:{((SetProjectIsConfiguredWorkItem)workItem).ProjectId}"; | ||
| return _lockProvider.AcquireAsync(cacheKey, TimeSpan.FromMinutes(15), new CancellationToken(true)); | ||
| return _lockProvider.AcquireAsync(cacheKey, TimeSpan.FromMinutes(15), cancellationToken); | ||
| } |
There was a problem hiding this comment.
AcquireAsync(..., new CancellationToken(true)) passes an already-cancelled token, so lock acquisition will be cancelled immediately (often returning null/throwing) and the handler may run without the intended lock protection. Use the provided cancellationToken (or CancellationToken.None) instead of creating a cancelled token.
| public override Task<ILock?> GetWorkItemLockAsync(object workItem, CancellationToken cancellationToken = new()) | ||
| { | ||
| string cacheKey = $"{nameof(SetLocationFromGeoWorkItemHandler)}:{((SetLocationFromGeoWorkItem)workItem).EventId}"; | ||
| return _lockProvider.AcquireAsync(cacheKey, TimeSpan.FromMinutes(15), new CancellationToken(true)); | ||
| return _lockProvider.AcquireAsync(cacheKey, TimeSpan.FromMinutes(15), cancellationToken); | ||
| } |
There was a problem hiding this comment.
AcquireAsync(..., new CancellationToken(true)) uses an already-cancelled token, which can prevent the lock from being acquired and lead to concurrent handlers for the same event. Use the cancellationToken parameter (or CancellationToken.None) instead so the lock reliably protects this work item.
| public override Task<ILock?> GetWorkItemLockAsync(object workItem, CancellationToken cancellationToken = new()) | ||
| { | ||
| string cacheKey = $"{nameof(RemoveStacksWorkItem)}:{((RemoveStacksWorkItem)workItem).ProjectId}"; | ||
| return _lockProvider.AcquireAsync(cacheKey, TimeSpan.FromMinutes(15), new CancellationToken(true)); | ||
| return _lockProvider.AcquireAsync(cacheKey, TimeSpan.FromMinutes(15), cancellationToken); | ||
| } |
There was a problem hiding this comment.
AcquireAsync(..., new CancellationToken(true)) passes a cancelled token, so the lock is likely never acquired and this handler may run concurrently (which is risky for bulk stack deletion + cache invalidation). Use the supplied cancellationToken (or CancellationToken.None) instead of a pre-cancelled token.
| Assert.Equal(eventCount, (result.Aggregations.DateHistogram("date_date")?.Buckets ?? []).Sum(t => t.Total)); | ||
| Assert.Single((result.Aggregations.DateHistogram("date_date")?.Buckets ?? []).First().Aggregations); | ||
| Assert.Equal(eventCount, result.Aggregations.Cardinality("cardinality_id")?.Value.GetValueOrDefault() ?? 0); | ||
| Assert.Equal(eventCount, (result.Aggregations.DateHistogram("date_date")?.Buckets ?? []).Sum(t => t.Aggregations.Cardinality("cardinality_id")?.Value.GetValueOrDefault() ?? 0)); |
There was a problem hiding this comment.
The null-coalescing to [] followed by .First() can still throw when the date histogram aggregation/buckets are missing or empty, but the exception will be less informative than an assertion failure. Consider asserting the histogram and bucket collection is not null/empty before accessing First(), or use Single()/FirstOrDefault() with an explicit assert message.
| Assert.Equal(8, ev.Data.Count); | ||
| Assert.Equal("Hi", ev.Data.GetString("SomeString")); | ||
| Assert.False(ev.Data.GetBoolean("SomeBool")); | ||
| Assert.Equal(false, ev.Data["SomeBool"]); | ||
| Assert.Equal(1L, ev.Data["SomeNum"]); |
There was a problem hiding this comment.
Assert.Equal(false, ev.Data["SomeBool"]) depends on the deserializer storing a boxed bool. If the converter ever yields a string/JValue/JsonElement, this will fail even though the logical value is false. Consider using a typed accessor/conversion (e.g., retrieving a bool via the same conversion path used in production) so the test validates behavior rather than the internal representation.
| var organization = await _organizationRepository.GetByIdAsync(organizationId, o => o.Cache()); | ||
| if (organization is null) | ||
| throw new InvalidOperationException($"Organization '{organizationId}' not found."); |
There was a problem hiding this comment.
Throwing InvalidOperationException when the organization/project is missing changes the behavior of GetUsageAsync from a query-style method into an exception-throwing method, which can bubble up as a 500 in controller mapping paths. Consider returning a zeroed UsageInfoResponse (or a null/Try* result) and letting API layers decide whether to return 404 vs. default usage.
| var project = await _projectRepository.GetByIdAsync(projectId, o => o.Cache()); | ||
| if (project is null) | ||
| throw new InvalidOperationException($"Project '{projectId}' not found."); |
There was a problem hiding this comment.
Same concern as the organization branch: throwing when the project is missing can cause unhandled exceptions in callers that expect a best-effort usage calculation. Prefer returning a default/empty usage response (or a nullable/Try* result) so API layers can decide how to handle missing projects.
| var termsAggregation = result.Aggregations.Terms<string>("terms_stack_id"); | ||
| var largestStackBucket = termsAggregation.Buckets.First(); | ||
| var largestStackBucket = (termsAggregation?.Buckets ?? []).First(); | ||
|
|
There was a problem hiding this comment.
(termsAggregation?.Buckets ?? []).First() will throw if the aggregation is missing or returns no buckets, but the ?? [] makes it harder to tell whether the failure was due to missing aggregations vs. missing test data. Consider asserting termsAggregation and Buckets are present/non-empty before calling First() so failures are clearer.
| return descriptor | ||
| .Text(f => f.Name(EventIndex.Alias.IpAddress).Analyzer(EventIndex.COMMA_WHITESPACE_ANALYZER)) | ||
| .Text(f => f.Name(EventIndex.Alias.IpAddress).Analyzer(EventIndex.COMMA_WHITESPACE_ANALYZER).AddKeywordField()) | ||
| .Text(f => f.Name(EventIndex.Alias.OperatingSystem).Analyzer(EventIndex.WHITESPACE_LOWERCASE_ANALYZER).AddKeywordField()) |
There was a problem hiding this comment.
Adding .AddKeywordField() to the IpAddress alias mapping changes the Elasticsearch index mapping (introduces a new multi-field) and may require an index version bump/reindex depending on how indexes are managed. This appears to be a functional change, so it should be called out in the PR description and verified for rollout impact.
Summary
Adapts all consuming code to Foundatio new NRT annotations. No functional changes — only null-safety adaptations.
What Changed and Why
Insulation (2 files)
Web (4 files)
Tests (17 files)
Test Plan