using ErsatzTV.Core.Domain; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; namespace ErsatzTV.Infrastructure.Data.Configurations; public class LibraryFolderConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ToTable("LibraryFolder"); // ersatztv#491: GetOrAddFolder is a check-then-insert, so two callers racing the same folder both // miss the lookup and both insert. The unique index makes the duplicate impossible at the storage // layer (the loser gets a constraint violation the repository catches and re-reads). // Indexed on PathHash rather than Path because Path is unbounded (MySQL longtext, which cannot be // indexed without a prefix length, and whose default collation is case-INsensitive — a prefix // index would also false-collide sibling folders differing only in case on a case-sensitive // filesystem). This mirrors the existing MediaFile.Path/PathHash pair. builder.Property(f => f.PathHash) .HasMaxLength(64); builder.HasIndex(f => new { f.LibraryPathId, f.PathHash }) .IsUnique(); builder.HasOne(f => f.Parent) .WithMany(p => p.Children) .HasForeignKey(f => f.ParentId) .IsRequired(false) .OnDelete(DeleteBehavior.Restrict); builder.HasOne(f => f.ImageFolderDuration) .WithOne(ifd => ifd.LibraryFolder) .HasForeignKey(ifd => ifd.LibraryFolderId) .IsRequired(false) .OnDelete(DeleteBehavior.Cascade); } }