public async Task WillMergeDuplicatedStacks() { var utcNow = SystemClock.UtcNow; var originalStack = StackData.GenerateStack(); originalStack.Id = ObjectId.GenerateNewId().ToString(); originalStack.TotalOccurrences = 100; var duplicateStack = originalStack.DeepClone(); duplicateStack.Id = ObjectId.GenerateNewId().ToString(); duplicateStack.Status = StackStatus.Fixed; duplicateStack.TotalOccurrences = 10; duplicateStack.LastOccurrence = originalStack.LastOccurrence.AddMinutes(1); duplicateStack.SnoozeUntilUtc = originalStack.SnoozeUntilUtc = null; duplicateStack.DateFixed = duplicateStack.LastOccurrence.AddMinutes(1); duplicateStack.Tags.Add("stack2"); duplicateStack.References.Add("stack2"); duplicateStack.OccurrencesAreCritical = true; originalStack = await _stackRepository.AddAsync(originalStack, o => o.ImmediateConsistency()); duplicateStack = await _stackRepository.AddAsync(duplicateStack, o => o.ImmediateConsistency()); await _eventRepository.AddAsync(EventData.GenerateEvents(count: 100, stackId: originalStack.Id), o => o.ImmediateConsistency()); await _eventRepository.AddAsync(EventData.GenerateEvents(count: 10, stackId: duplicateStack.Id), o => o.ImmediateConsistency()); var results = await _stackRepository.FindAsync(q => q.ElasticFilter(Query <Stack> .Term(s => s.DuplicateSignature, originalStack.DuplicateSignature))); Assert.Equal(2, results.Total); var migration = GetService <FixDuplicateStacks>(); var context = new MigrationContext(GetService <ILock>(), _logger, CancellationToken.None); await migration.RunAsync(context); await RefreshDataAsync(); results = await _stackRepository.FindAsync(q => q.ElasticFilter(Query <Stack> .Term(s => s.DuplicateSignature, originalStack.DuplicateSignature))); Assert.Single(results.Documents); var updatedOriginalStack = await _stackRepository.GetByIdAsync(originalStack.Id, o => o.IncludeSoftDeletes()); Assert.False(updatedOriginalStack.IsDeleted); var updatedDuplicateStack = await _stackRepository.GetByIdAsync(duplicateStack.Id, o => o.IncludeSoftDeletes()); Assert.True(updatedDuplicateStack.IsDeleted); Assert.Equal(originalStack.CreatedUtc, updatedOriginalStack.CreatedUtc); Assert.Equal(110, updatedOriginalStack.TotalOccurrences); Assert.Equal(StackStatus.Fixed, updatedOriginalStack.Status); Assert.Equal(duplicateStack.LastOccurrence, updatedOriginalStack.LastOccurrence); Assert.Null(updatedOriginalStack.SnoozeUntilUtc); Assert.Equal(duplicateStack.DateFixed, updatedOriginalStack.DateFixed); Assert.Equal(originalStack.Tags.Count + 1, updatedOriginalStack.Tags.Count); Assert.Contains("stack2", updatedOriginalStack.Tags); Assert.Equal(originalStack.References.Count + 1, updatedOriginalStack.References.Count); Assert.Contains("stack2", updatedOriginalStack.References); Assert.True(updatedOriginalStack.OccurrencesAreCritical); }
public async Task CanCreateUpdateRemoveAsync() { await ResetAsync(); await _repository.RemoveAllAsync(); await _client.RefreshAsync(); Assert.Equal(0, await _repository.CountAsync()); var stack = StackData.GenerateStack(projectId: TestConstants.ProjectId, organizationId: TestConstants.OrganizationId); Assert.Null(stack.Id); await _repository.AddAsync(stack); Assert.NotNull(stack.Id); await _client.RefreshAsync(); stack = await _repository.GetByIdAsync(stack.Id); Assert.NotNull(stack); stack.Description = "New Description"; await _repository.SaveAsync(stack); await _repository.RemoveAsync(stack.Id); }
public async Task IncrementUsage_OnlyChangeCache() { var stack = await _stackRepository.AddAsync(StackData.GenerateStack(id: TestConstants.StackId, projectId: TestConstants.ProjectId, organizationId: TestConstants.OrganizationId), o => o.ImmediateConsistency()); // Assert stack state in elasticsearch before increment usage Assert.Equal(0, stack.TotalOccurrences); Assert.True(stack.FirstOccurrence <= SystemClock.UtcNow); Assert.True(stack.LastOccurrence <= SystemClock.UtcNow); // Assert state in cache before increment usage Assert.Equal(DateTime.MinValue, await _cache.GetUnixTimeMillisecondsAsync(_stackService.GetStackOccurrenceMinDateCacheKey(stack.Id))); Assert.Equal(DateTime.MinValue, await _cache.GetUnixTimeMillisecondsAsync(_stackService.GetStackOccurrenceMaxDateCacheKey(stack.Id))); Assert.Equal(0, await _cache.GetAsync <long>(_stackService.GetStackOccurrenceCountCacheKey(stack.Id), 0)); var occurrenceSet = await _cache.GetListAsync <(string OrganizationId, string ProjectId, string StackId)>(_stackService.GetStackOccurrenceSetCacheKey()); Assert.True(occurrenceSet.IsNull || !occurrenceSet.HasValue || occurrenceSet.Value.Count == 0); var firstUtcNow = SystemClock.UtcNow.Floor(TimeSpan.FromMilliseconds(1)); await RefreshDataAsync(); await _stackService.IncrementStackUsageAsync(TestConstants.OrganizationId, TestConstants.ProjectId, stack.Id, firstUtcNow, firstUtcNow, 1); // Assert stack state has no change after increment usage stack = await _stackRepository.GetByIdAsync(TestConstants.StackId); Assert.Equal(0, stack.TotalOccurrences); Assert.True(stack.FirstOccurrence <= SystemClock.UtcNow); Assert.True(stack.LastOccurrence <= SystemClock.UtcNow); // Assert state in cache has been changed after increment usage Assert.Equal(firstUtcNow, await _cache.GetUnixTimeMillisecondsAsync(_stackService.GetStackOccurrenceMinDateCacheKey(stack.Id))); Assert.Equal(firstUtcNow, await _cache.GetUnixTimeMillisecondsAsync(_stackService.GetStackOccurrenceMaxDateCacheKey(stack.Id))); Assert.Equal(1, await _cache.GetAsync <long>(_stackService.GetStackOccurrenceCountCacheKey(stack.Id), 0)); occurrenceSet = await _cache.GetListAsync <(string OrganizationId, string ProjectId, string StackId)>(_stackService.GetStackOccurrenceSetCacheKey()); Assert.Single(occurrenceSet.Value); var secondUtcNow = SystemClock.UtcNow.Floor(TimeSpan.FromMilliseconds(1)); await RefreshDataAsync(); await _stackService.IncrementStackUsageAsync(TestConstants.OrganizationId, TestConstants.ProjectId, stack.Id, secondUtcNow, secondUtcNow, 2); // Assert state in cache has been changed after increment usage again Assert.Equal(firstUtcNow, await _cache.GetUnixTimeMillisecondsAsync(_stackService.GetStackOccurrenceMinDateCacheKey(stack.Id))); Assert.Equal(secondUtcNow, await _cache.GetUnixTimeMillisecondsAsync(_stackService.GetStackOccurrenceMaxDateCacheKey(stack.Id))); Assert.Equal(3, await _cache.GetAsync <long>(_stackService.GetStackOccurrenceCountCacheKey(stack.Id), 0)); occurrenceSet = await _cache.GetListAsync <(string OrganizationId, string ProjectId, string StackId)>(_stackService.GetStackOccurrenceSetCacheKey()); Assert.Single(occurrenceSet.Value); }
private async Task CreateDataAsync() { var baseDate = SystemClock.UtcNow.SubtractHours(1); var occurrenceDateStart = baseDate.AddMinutes(-30); var occurrenceDateMid = baseDate; var occurrenceDateEnd = baseDate.AddMinutes(30); await _stackRepository.AddAsync(StackData.GenerateStack(id: TestConstants.StackId, organizationId: TestConstants.OrganizationId, projectId: TestConstants.ProjectId)); var occurrenceDates = new List <DateTime> { occurrenceDateStart, occurrenceDateEnd, baseDate.AddMinutes(-10), baseDate.AddMinutes(-20), occurrenceDateMid, occurrenceDateMid, occurrenceDateMid, baseDate.AddMinutes(20), baseDate.AddMinutes(10), baseDate.AddSeconds(1), occurrenceDateEnd, occurrenceDateStart }; foreach (var date in occurrenceDates) { var ev = await _repository.AddAsync(EventData.GenerateEvent(projectId: TestConstants.ProjectId, organizationId: TestConstants.OrganizationId, stackId: TestConstants.StackId, occurrenceDate: date)); _ids.Add(Tuple.Create(ev.Id, date)); } await RefreshDataAsync(); }
public static async Task CreateSearchDataAsync(IStackRepository stackRepository, JsonSerializer serializer, bool updateDates = false) { string path = Path.Combine("..", "..", "..", "Search", "Data"); foreach (string file in Directory.GetFiles(path, "stack*.json", SearchOption.AllDirectories)) { if (file.EndsWith("summary.json")) { continue; } using (var stream = new FileStream(file, FileMode.Open)) { using (var streamReader = new StreamReader(stream)) { var stack = serializer.Deserialize(streamReader, typeof(Stack)) as Stack; Assert.NotNull(stack); if (updateDates) { stack.CreatedUtc = stack.FirstOccurrence = SystemClock.UtcNow.SubtractDays(1); stack.LastOccurrence = SystemClock.UtcNow; } await stackRepository.AddAsync(stack, o => o.ImmediateConsistency()); } } } }
private async Task CreateStacksAsync() { _configuration.DeleteIndexes(_client); _configuration.ConfigureIndexes(_client); var serializer = IoC.GetInstance <JsonSerializer>(); foreach (var file in Directory.GetFiles(@"..\..\Search\Data\", "stack*.json", SearchOption.AllDirectories)) { if (file.EndsWith("summary.json")) { continue; } using (var stream = new FileStream(file, FileMode.Open)) { using (var streamReader = new StreamReader(stream)) { var stack = serializer.Deserialize(streamReader, typeof(Stack)) as Stack; Assert.NotNull(stack); await _repository.AddAsync(stack); } } } await _client.RefreshAsync(); }
public async Task WillSetStackDuplicateSignature() { var stack = StackData.GenerateStack(); stack.DuplicateSignature = null; stack = await _repository.AddAsync(stack, o => o.ImmediateConsistency()); Assert.NotEmpty(stack.ProjectId); Assert.NotEmpty(stack.SignatureHash); Assert.Null(stack.DuplicateSignature); var migration = GetService <SetStackDuplicateSignature>(); var context = new MigrationContext(GetService <ILock>(), _logger, CancellationToken.None); await migration.RunAsync(context); string expectedDuplicateSignature = $"{stack.ProjectId}:{stack.SignatureHash}"; var actualStack = await _repository.GetByIdAsync(stack.Id); Assert.NotEmpty(actualStack.ProjectId); Assert.NotEmpty(actualStack.SignatureHash); Assert.Equal($"{actualStack.ProjectId}:{actualStack.SignatureHash}", actualStack.DuplicateSignature); var results = await _repository.FindAsync(q => q.ElasticFilter(Query <Stack> .Term(s => s.DuplicateSignature, expectedDuplicateSignature))); Assert.Single(results.Documents); }
public async Task CanGetByStackHashAsync() { long count = _cache.Count; long hits = _cache.Hits; long misses = _cache.Misses; var stack = await _repository.AddAsync(StackData.GenerateStack(id: TestConstants.StackId, projectId: TestConstants.ProjectId, organizationId: TestConstants.OrganizationId, dateFixed: SystemClock.UtcNow.SubtractMonths(1)), o => o.Cache()); Assert.NotNull(stack?.Id); Assert.Equal(count + 2, _cache.Count); Assert.Equal(hits, _cache.Hits); Assert.Equal(misses, _cache.Misses); var result = await _repository.GetStackBySignatureHashAsync(stack.ProjectId, stack.SignatureHash); Assert.Equal(stack.ToJson(), result.ToJson()); Assert.Equal(count + 2, _cache.Count); Assert.Equal(hits + 1, _cache.Hits); Assert.Equal(misses, _cache.Misses); }
public async Task CanCleanupSoftDeletedOrganization() { var organization = OrganizationData.GenerateSampleOrganization(_billingManager, _plans); organization.IsDeleted = true; await _organizationRepository.AddAsync(organization, o => o.ImmediateConsistency()); var project = await _projectRepository.AddAsync(ProjectData.GenerateSampleProject(), o => o.ImmediateConsistency()); var stack = await _stackRepository.AddAsync(StackData.GenerateSampleStack(), o => o.ImmediateConsistency()); var persistentEvent = await _eventRepository.AddAsync(EventData.GenerateEvent(organization.Id, project.Id, stack.Id), o => o.ImmediateConsistency()); await _job.RunAsync(); Assert.Null(await _organizationRepository.GetByIdAsync(organization.Id, o => o.IncludeSoftDeletes())); Assert.Null(await _projectRepository.GetByIdAsync(project.Id, o => o.IncludeSoftDeletes())); Assert.Null(await _stackRepository.GetByIdAsync(stack.Id, o => o.IncludeSoftDeletes())); Assert.Null(await _eventRepository.GetByIdAsync(persistentEvent.Id, o => o.IncludeSoftDeletes())); }
public async Task MarkAsRegressedTestAsync() { await RemoveDataAsync(); await _repository.AddAsync(StackData.GenerateStack(id: TestConstants.StackId, projectId: TestConstants.ProjectId, organizationId: TestConstants.OrganizationId, dateFixed: DateTime.Now.SubtractMonths(1))); await _client.RefreshAsync(); var stack = await _repository.GetByIdAsync(TestConstants.StackId); Assert.NotNull(stack); Assert.False(stack.IsRegressed); Assert.NotNull(stack.DateFixed); await _repository.MarkAsRegressedAsync(TestConstants.StackId); await _client.RefreshAsync(); stack = await _repository.GetByIdAsync(TestConstants.StackId); Assert.NotNull(stack); Assert.True(stack.IsRegressed); Assert.Null(stack.DateFixed); }
private async Task CreateDataAsync() { string path = Path.Combine("..", "..", "..", "Search", "Data"); var serializer = GetService <JsonSerializer>(); foreach (string file in Directory.GetFiles(path, "stack*.json", SearchOption.AllDirectories)) { if (file.EndsWith("summary.json")) { continue; } using (var stream = new FileStream(file, FileMode.Open)) { using (var streamReader = new StreamReader(stream)) { var stack = serializer.Deserialize(streamReader, typeof(Stack)) as Stack; Assert.NotNull(stack); await _repository.AddAsync(stack, o => o.ImmediateConsistency()); } } } }
private async Task CreateDataAsync(IServiceProvider serviceProvider) { string path = Path.Combine("..", "..", "..", "Search", "Data"); var serializer = serviceProvider.GetService <JsonSerializer>(); foreach (string file in Directory.GetFiles(path, "stack*.json", SearchOption.AllDirectories)) { if (file.EndsWith("summary.json")) { continue; } using (var stream = new FileStream(file, FileMode.Open)) { using (var streamReader = new StreamReader(stream)) { var stack = serializer.Deserialize(streamReader, typeof(Stack)) as Stack; Assert.NotNull(stack); await _repository.AddAsync(stack); } } } await _configuration.Client.RefreshAsync(Indices.All); }
public override async Task ProcessBatchAsync(ICollection <EventContext> contexts) { var stacks = new Dictionary <string, Tuple <bool, Stack> >(); foreach (var ctx in contexts) { if (String.IsNullOrEmpty(ctx.Event.StackId)) { // only add default signature info if no other signature info has been added if (ctx.StackSignatureData.Count == 0) { ctx.StackSignatureData.AddItemIfNotEmpty("Type", ctx.Event.Type); ctx.StackSignatureData.AddItemIfNotEmpty("Source", ctx.Event.Source); } string signatureHash = ctx.StackSignatureData.Values.ToSHA1(); ctx.SignatureHash = signatureHash; Tuple <bool, Stack> value; if (stacks.TryGetValue(signatureHash, out value)) { ctx.Stack = value.Item2; } else { ctx.Stack = await _stackRepository.GetStackBySignatureHashAsync(ctx.Event.ProjectId, signatureHash).AnyContext(); if (ctx.Stack != null) { stacks.Add(signatureHash, Tuple.Create(false, ctx.Stack)); } } if (ctx.Stack == null) { Logger.Trace().Message("Creating new event stack.").Write(); ctx.IsNew = true; string title = _formattingPluginManager.GetStackTitle(ctx.Event); var stack = new Stack { OrganizationId = ctx.Event.OrganizationId, ProjectId = ctx.Event.ProjectId, SignatureInfo = new SettingsDictionary(ctx.StackSignatureData), SignatureHash = signatureHash, Title = title?.Truncate(1000), Tags = ctx.Event.Tags ?? new TagSet(), Type = ctx.Event.Type, TotalOccurrences = 1, FirstOccurrence = ctx.Event.Date.UtcDateTime, LastOccurrence = ctx.Event.Date.UtcDateTime }; ctx.Stack = stack; stacks.Add(signatureHash, Tuple.Create(true, ctx.Stack)); } } else { ctx.Stack = await _stackRepository.GetByIdAsync(ctx.Event.StackId, true).AnyContext(); if (ctx.Stack == null || ctx.Stack.ProjectId != ctx.Event.ProjectId) { ctx.SetError("Invalid StackId."); continue; } ctx.SignatureHash = ctx.Stack.SignatureHash; if (!stacks.ContainsKey(ctx.Stack.SignatureHash)) { stacks.Add(ctx.Stack.SignatureHash, Tuple.Create(false, ctx.Stack)); } else { stacks[ctx.Stack.SignatureHash] = Tuple.Create(false, ctx.Stack); } } if (!ctx.IsNew && ctx.Event.Tags != null && ctx.Event.Tags.Count > 0) { if (ctx.Stack.Tags == null) { ctx.Stack.Tags = new TagSet(); } List <string> newTags = ctx.Event.Tags.Where(t => !ctx.Stack.Tags.Contains(t)).ToList(); if (newTags.Count > 0) { ctx.Stack.Tags.AddRange(newTags); // make sure the stack gets saved if (!stacks.ContainsKey(ctx.Stack.SignatureHash)) { stacks.Add(ctx.Stack.SignatureHash, Tuple.Create(true, ctx.Stack)); } else { stacks[ctx.Stack.SignatureHash] = Tuple.Create(true, stacks[ctx.Stack.SignatureHash].Item2); } } } ctx.Event.IsFirstOccurrence = ctx.IsNew; // sync the fixed and hidden flags to the error occurrence ctx.Event.IsFixed = ctx.Stack.DateFixed.HasValue; ctx.Event.IsHidden = ctx.Stack.IsHidden; } var stacksToAdd = stacks.Where(kvp => kvp.Value.Item1 && String.IsNullOrEmpty(kvp.Value.Item2.Id)).Select(kvp => kvp.Value.Item2).ToList(); if (stacksToAdd.Count > 0) { await _stackRepository.AddAsync(stacksToAdd, true, sendNotification : stacksToAdd.Count == 1).AnyContext(); if (stacksToAdd.Count > 1) { await _publisher.PublishAsync(new ExtendedEntityChanged { ChangeType = ChangeType.Added, Type = typeof(Stack).Name, OrganizationId = contexts.First().Organization.Id, ProjectId = contexts.First().Project.Id }).AnyContext(); } } var stacksToSave = stacks.Where(kvp => kvp.Value.Item1 && !String.IsNullOrEmpty(kvp.Value.Item2.Id)).Select(kvp => kvp.Value.Item2).ToList(); if (stacksToSave.Count > 0) { await _stackRepository.SaveAsync(stacksToSave, true, sendNotification : false).AnyContext(); // notification will get sent later in the update stats step } // Set stack ids after they have been saved and created contexts.ForEach(ctx => { ctx.Event.StackId = ctx.Stack?.Id; }); }
public override async Task ProcessBatchAsync(ICollection <EventContext> contexts) { var stacks = new Dictionary <string, Tuple <bool, Stack> >(); foreach (var ctx in contexts) { if (String.IsNullOrEmpty(ctx.Event.StackId)) { // only add default signature info if no other signature info has been added if (ctx.StackSignatureData.Count == 0) { ctx.StackSignatureData.AddItemIfNotEmpty("Type", ctx.Event.Type); ctx.StackSignatureData.AddItemIfNotEmpty("Source", ctx.Event.Source); } string signatureHash = ctx.StackSignatureData.Values.ToSHA1(); ctx.SignatureHash = signatureHash; if (stacks.TryGetValue(signatureHash, out var value)) { ctx.Stack = value.Item2; } else { ctx.Stack = await _stackRepository.GetStackBySignatureHashAsync(ctx.Event.ProjectId, signatureHash).AnyContext(); if (ctx.Stack != null) { stacks.Add(signatureHash, Tuple.Create(false, ctx.Stack)); } } if (ctx.Stack == null) { _logger.LogTrace("Creating new event stack."); ctx.IsNew = true; string title = _formattingPluginManager.GetStackTitle(ctx.Event); var stack = new Stack { OrganizationId = ctx.Event.OrganizationId, ProjectId = ctx.Event.ProjectId, SignatureInfo = new SettingsDictionary(ctx.StackSignatureData), SignatureHash = signatureHash, DuplicateSignature = ctx.Event.ProjectId + ":" + signatureHash, Title = title?.Truncate(1000), Tags = ctx.Event.Tags ?? new TagSet(), Type = ctx.Event.Type, TotalOccurrences = 1, FirstOccurrence = ctx.Event.Date.UtcDateTime, LastOccurrence = ctx.Event.Date.UtcDateTime }; if (ctx.Event.Type == Event.KnownTypes.Session) { stack.Status = StackStatus.Ignored; } ctx.Stack = stack; stacks.Add(signatureHash, Tuple.Create(true, ctx.Stack)); } } else { ctx.Stack = await _stackRepository.GetByIdAsync(ctx.Event.StackId, o => o.Cache()).AnyContext(); if (ctx.Stack == null || ctx.Stack.ProjectId != ctx.Event.ProjectId) { ctx.SetError("Invalid StackId."); continue; } ctx.SignatureHash = ctx.Stack.SignatureHash; if (!stacks.ContainsKey(ctx.Stack.SignatureHash)) { stacks.Add(ctx.Stack.SignatureHash, Tuple.Create(false, ctx.Stack)); } else { stacks[ctx.Stack.SignatureHash] = Tuple.Create(false, ctx.Stack); } if (ctx.Stack.Status == StackStatus.Discarded) { ctx.IsDiscarded = true; ctx.IsCancelled = true; } } if (!ctx.IsNew && ctx.Event.Tags != null && ctx.Event.Tags.Count > 0) { if (ctx.Stack.Tags == null) { ctx.Stack.Tags = new TagSet(); } var newTags = ctx.Event.Tags.Where(t => !ctx.Stack.Tags.Contains(t)).ToList(); if (newTags.Count > 0 || ctx.Stack.Tags.Count > 50 || ctx.Stack.Tags.Any(t => t.Length > 100)) { ctx.Stack.Tags.AddRange(newTags); ctx.Stack.Tags.RemoveExcessTags(); // make sure the stack gets saved if (!stacks.ContainsKey(ctx.Stack.SignatureHash)) { stacks.Add(ctx.Stack.SignatureHash, Tuple.Create(true, ctx.Stack)); } else { stacks[ctx.Stack.SignatureHash] = Tuple.Create(true, stacks[ctx.Stack.SignatureHash].Item2); } } } ctx.Event.IsFirstOccurrence = ctx.IsNew; } var stacksToAdd = stacks.Where(kvp => kvp.Value.Item1 && String.IsNullOrEmpty(kvp.Value.Item2.Id)).Select(kvp => kvp.Value.Item2).ToList(); if (stacksToAdd.Count > 0) { await _stackRepository.AddAsync(stacksToAdd, o => o.Cache().Notifications(stacksToAdd.Count == 1)).AnyContext(); if (stacksToAdd.Count > 1) { await _publisher.PublishAsync(new EntityChanged { ChangeType = ChangeType.Added, Type = StackTypeName, Data = { { ExtendedEntityChanged.KnownKeys.OrganizationId, contexts.First().Organization.Id }, { ExtendedEntityChanged.KnownKeys.ProjectId, contexts.First().Project.Id } } }).AnyContext(); } } var stacksToSave = stacks.Where(kvp => kvp.Value.Item1 && !String.IsNullOrEmpty(kvp.Value.Item2.Id)).Select(kvp => kvp.Value.Item2).ToList(); if (stacksToSave.Count > 0) { await _stackRepository.SaveAsync(stacksToSave, o => o.Cache().Notifications(false)).AnyContext(); // notification will get sent later in the update stats step } // Set stack ids after they have been saved and created contexts.ForEach(ctx => { ctx.Event.StackId = ctx.Stack?.Id; }); }