using AutoMapper; using Bit.Infrastructure.EntityFramework.Repositories; using LinqToDB.Data; using LinqToDB.EntityFrameworkCore; using EfCollection = Bit.Infrastructure.EntityFramework.Models.Collection; using EfCollectionGroup = Bit.Infrastructure.EntityFramework.Models.CollectionGroup; using EfCollectionUser = Bit.Infrastructure.EntityFramework.Models.CollectionUser; using EfFolder = Bit.Infrastructure.EntityFramework.Vault.Models.Folder; using EfGroup = Bit.Infrastructure.EntityFramework.Models.Group; using EfGroupUser = Bit.Infrastructure.EntityFramework.Models.GroupUser; using EfOrganization = Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization; using EfOrganizationUser = Bit.Infrastructure.EntityFramework.Models.OrganizationUser; using EfUser = Bit.Infrastructure.EntityFramework.Models.User; namespace Bit.Seeder.Pipeline; /// /// Flushes accumulated entities from to the database via BulkCopy. /// /// /// Entities are committed in foreign-key-safe order (Organizations → Users → OrgUsers → … → Folders → Ciphers). /// Most Core entities require AutoMapper conversion to their EF counterparts before insert; /// a few (Cipher, CollectionCipher) share the same type across layers and copy directly. /// Each list is cleared after insert so the context is ready for the next pipeline run. /// /// CollectionUser and CollectionGroup require an explicit table name in BulkCopyOptions because /// they lack both IEntityTypeConfiguration and .ToTable() mappings in DatabaseContext, so LinqToDB /// cannot resolve their table names automatically. /// /// /// internal sealed class BulkCommitter(DatabaseContext db, IMapper mapper) { internal void Commit(SeederContext context) { MapCopyAndClear(context.Organizations); MapCopyAndClear(context.Users); MapCopyAndClear(context.OrganizationUsers); MapCopyAndClear(context.Groups); MapCopyAndClear(context.GroupUsers); MapCopyAndClear(context.Collections); MapCopyAndClear(context.CollectionUsers, nameof(Core.Entities.CollectionUser)); MapCopyAndClear(context.CollectionGroups, nameof(Core.Entities.CollectionGroup)); MapCopyAndClear(context.Folders); CopyAndClear(context.Ciphers); CopyAndClear(context.CollectionCiphers); } private void MapCopyAndClear(List entities, string? tableName = null) where TEf : class { if (entities.Count is 0) { return; } var mapped = entities.Select(e => mapper.Map(e)); if (tableName is not null) { db.BulkCopy(new BulkCopyOptions { TableName = tableName }, mapped); } else { db.BulkCopy(mapped); } entities.Clear(); } private void CopyAndClear(List entities) where T : class { if (entities.Count is 0) { return; } db.BulkCopy(entities); entities.Clear(); } }