diff --git a/src/Infrastructure.EntityFramework/Dirt/Repositories/EventRepository.cs b/src/Infrastructure.EntityFramework/Dirt/Repositories/EventRepository.cs index 57884478a07a..070cb5cf3902 100644 --- a/src/Infrastructure.EntityFramework/Dirt/Repositories/EventRepository.cs +++ b/src/Infrastructure.EntityFramework/Dirt/Repositories/EventRepository.cs @@ -48,6 +48,15 @@ public async Task CreateManyAsync(IEnumerable entities) var tableEvents = entities.Select(e => e as Core.Entities.Event ?? new Core.Entities.Event(e)); var entityEvents = Mapper.Map>(tableEvents); entityEvents.ForEach(e => e.SetNewId()); + + // SQLite deployments can fail to resolve the linq2db bulk copy provider adapter + if (dbContext.Database.IsSqlite()) + { + await dbContext.Events.AddRangeAsync(entityEvents); + await dbContext.SaveChangesAsync(); + return; + } + await dbContext.BulkCopyAsync(entityEvents); } } diff --git a/test/Infrastructure.IntegrationTest/Dirt/Repositories/EventRepositoryCreateManyTests.cs b/test/Infrastructure.IntegrationTest/Dirt/Repositories/EventRepositoryCreateManyTests.cs new file mode 100644 index 000000000000..b03856c3f77a --- /dev/null +++ b/test/Infrastructure.IntegrationTest/Dirt/Repositories/EventRepositoryCreateManyTests.cs @@ -0,0 +1,64 @@ +using Bit.Core.Enums; +using Bit.Core.Models.Data; +using Xunit; +using Event = Bit.Core.Entities.Event; +using IEventRepository = Bit.Core.Repositories.IEventRepository; + +namespace Bit.Infrastructure.IntegrationTest.Dirt.Repositories; + +/// +/// Covers CreateManyAsync on every configured provider. IEventRepository is only +/// registered for self-hosted deployments, hence SelfHosted = true; that resolves to the +/// Dapper implementation on SQL Server and the EF implementation on the other providers. +/// +public class EventRepositoryCreateManyTests +{ + [Theory, DatabaseData(SelfHosted = true)] + public async Task CreateManyAsync_MultipleEvents_PersistsEveryEvent(IEventRepository sut) + { + var organizationId = Guid.NewGuid(); + + await sut.CreateManyAsync(BuildEvents(organizationId, 3)); + + Assert.Equal(3, (await ReadEventsAsync(sut, organizationId)).Count); + } + + [Theory, DatabaseData(SelfHosted = true)] + public async Task CreateManyAsync_SingleEvent_PersistsThatEvent(IEventRepository sut) + { + var organizationId = Guid.NewGuid(); + + await sut.CreateManyAsync(BuildEvents(organizationId, 1)); + + Assert.Single(await ReadEventsAsync(sut, organizationId)); + } + + [Theory, DatabaseData(SelfHosted = true)] + public async Task CreateManyAsync_NoEvents_DoesNotThrow(IEventRepository sut) + { + var organizationId = Guid.NewGuid(); + + await sut.CreateManyAsync([]); + + Assert.Empty(await ReadEventsAsync(sut, organizationId)); + } + + private static List BuildEvents(Guid organizationId, int count) => + Enumerable.Range(0, count) + .Select(i => new Event + { + Type = EventType.Organization_Updated, + OrganizationId = organizationId, + Date = DateTime.UtcNow.AddMinutes(-i), + }) + .ToList(); + + private static async Task> ReadEventsAsync( + IEventRepository sut, Guid organizationId) + { + var result = await sut.GetManyByOrganizationAsync( + organizationId, DateTime.UtcNow.AddDays(-1), DateTime.UtcNow.AddDays(1), + new PageOptions { PageSize = 100 }); + return result.Data; + } +}