fix db model

This commit is contained in:
beo3000 2025-12-21 12:03:46 +01:00
parent d653225c5f
commit 3024ed85d2
12 changed files with 67 additions and 26 deletions

View File

@ -30,6 +30,11 @@ public class PersonExpense : BaseEntity
/// </summary> /// </summary>
public Guid? GameId { get; set; } // Foreign Key public Guid? GameId { get; set; } // Foreign Key
/// <summary>
/// Id of a club.
/// </summary>
public Guid ClubId { get; set; }
// Stammdaten // Stammdaten
/// <summary> /// <summary>
@ -57,11 +62,6 @@ public class PersonExpense : BaseEntity
// Navigation Properties // Navigation Properties
/// <summary>
/// Id of a club.
/// </summary>
public Guid ClubId { get; set; }
/// <summary> /// <summary>
/// ID des Benutzers, der die Zuordnung vorgenommen hat. /// ID des Benutzers, der die Zuordnung vorgenommen hat.
/// </summary> /// </summary>

View File

@ -42,4 +42,33 @@ public class AppDbContext : DbContext
// Deine IEntityTypeConfiguration<> Klassen automatisch anwenden // Deine IEntityTypeConfiguration<> Klassen automatisch anwenden
modelBuilder.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly); modelBuilder.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly);
} }
public override int SaveChanges()
{
ConvertDeletesToSoftDeletes();
return base.SaveChanges();
}
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
ConvertDeletesToSoftDeletes();
return base.SaveChangesAsync(cancellationToken);
}
private void ConvertDeletesToSoftDeletes()
{
foreach (var entry in ChangeTracker.Entries<BaseEntity>())
{
if (entry.State == EntityState.Deleted)
{
entry.State = EntityState.Modified;
entry.Entity.IsDeleted = true;
entry.Entity.ModifiedAt = DateTime.UtcNow;
}
}
}
} }

View File

@ -21,6 +21,8 @@ public class ClubConfiguration : IEntityTypeConfiguration<Club>
.HasConversion<int>() .HasConversion<int>()
.IsRequired(); .IsRequired();
builder.HasIndex(x => x.Name);
builder.HasQueryFilter(x => !x.IsDeleted); builder.HasQueryFilter(x => !x.IsDeleted);
} }
} }

View File

@ -32,14 +32,14 @@ public class DayPersonConfiguration : IEntityTypeConfiguration<DayPerson>
.WithMany(x => x.DayPersons) .WithMany(x => x.DayPersons)
.HasForeignKey(x => new { x.DayId, x.ClubId }) .HasForeignKey(x => new { x.DayId, x.ClubId })
.HasPrincipalKey(d => new { d.Id, d.ClubId }) .HasPrincipalKey(d => new { d.Id, d.ClubId })
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.NoAction);
// Club-Konsistenz: (PersonId, ClubId) muss in Person(Id, ClubId) existieren // Club-Konsistenz: (PersonId, ClubId) muss in Person(Id, ClubId) existieren
builder.HasOne(x => x.Person) builder.HasOne(x => x.Person)
.WithMany(x => x.DayPersons) .WithMany(x => x.DayPersons)
.HasForeignKey(x => new { x.PersonId, x.ClubId }) .HasForeignKey(x => new { x.PersonId, x.ClubId })
.HasPrincipalKey(p => new { p.Id, p.ClubId }) .HasPrincipalKey(p => new { p.Id, p.ClubId })
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.NoAction);
@ -58,10 +58,6 @@ public class DayPersonConfiguration : IEntityTypeConfiguration<DayPerson>
builder.HasIndex(x => x.ClubId);
builder.HasIndex(x => x.DayId);
builder.HasIndex(x => x.PersonId);
} }
} }

View File

@ -42,12 +42,13 @@ public class ExpenseConfiguration : IEntityTypeConfiguration<Expense>
builder.HasOne(x => x.Club) builder.HasOne(x => x.Club)
.WithMany() .WithMany()
.HasForeignKey(x => x.ClubId) .HasForeignKey(x => x.ClubId)
.OnDelete(DeleteBehavior.Restrict); .OnDelete(DeleteBehavior.NoAction);
// Alternate Key für Composite-FK in ExpenseTrigger: (Id, ClubId) // Alternate Key für Composite-FK in ExpenseTrigger: (Id, ClubId)
builder.HasAlternateKey(x => new { x.Id, x.ClubId }); builder.HasAlternateKey(x => new { x.Id, x.ClubId });
builder.HasIndex(x => x.ClubId); builder.HasIndex(x => x.ClubId);
builder.HasIndex(x => new { x.ClubId, x.Name });
builder.HasQueryFilter(x => !x.IsDeleted); builder.HasQueryFilter(x => !x.IsDeleted);

View File

@ -38,12 +38,12 @@ public class ExpenseTriggerConfiguration : IEntityTypeConfiguration<ExpenseTrigg
.WithMany() .WithMany()
.HasForeignKey(x => new { x.ExpenseId, x.ClubId }) .HasForeignKey(x => new { x.ExpenseId, x.ClubId })
.HasPrincipalKey(e => new { e.Id, e.ClubId }) .HasPrincipalKey(e => new { e.Id, e.ClubId })
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.NoAction);
builder.HasOne(x => x.Trigger) builder.HasOne(x => x.Trigger)
.WithMany() .WithMany()
.HasForeignKey(x => x.TriggerId) .HasForeignKey(x => x.TriggerId)
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.NoAction);
builder.HasIndex(x => x.ExpenseId); builder.HasIndex(x => x.ExpenseId);
builder.HasIndex(x => x.TriggerId); builder.HasIndex(x => x.TriggerId);

View File

@ -39,7 +39,7 @@ public class GameConfiguration : IEntityTypeConfiguration<Game>
builder.HasOne(x => x.Day) builder.HasOne(x => x.Day)
.WithMany() .WithMany()
.HasForeignKey(x => x.DayId) .HasForeignKey(x => x.DayId)
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.NoAction);
// Mehrere Games pro Day -> Index nicht unique // Mehrere Games pro Day -> Index nicht unique
builder.HasIndex(x => x.DayId); builder.HasIndex(x => x.DayId);

View File

@ -20,14 +20,16 @@ public class GamePersonConfiguration : IEntityTypeConfiguration<GamePerson>
builder.HasOne(x => x.Game) builder.HasOne(x => x.Game)
.WithMany(x => x.GamePersons) .WithMany(x => x.GamePersons)
.HasForeignKey(x => x.GameId) .HasForeignKey(x => x.GameId)
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.NoAction);
builder.HasOne(x => x.Person) builder.HasOne(x => x.Person)
.WithMany() .WithMany()
.HasForeignKey(x => x.PersonId) .HasForeignKey(x => x.PersonId)
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.NoAction);
builder.HasIndex(x => x.PersonId); builder.HasIndex(x => x.PersonId);
builder.HasIndex(x => x.GameId);
// ✅ Matching Query Filter: // ✅ Matching Query Filter:

View File

@ -36,7 +36,7 @@ public class PersonConfiguration : IEntityTypeConfiguration<Person>
builder.HasMany(x => x.Expenses) builder.HasMany(x => x.Expenses)
.WithOne(x => x.Person) .WithOne(x => x.Person)
.HasForeignKey(x => x.PersonId) .HasForeignKey(x => x.PersonId)
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.NoAction);
builder.HasIndex(x => x.ClubId); builder.HasIndex(x => x.ClubId);

View File

@ -39,40 +39,49 @@ public class PersonExpenseConfiguration : IEntityTypeConfiguration<PersonExpense
builder.Property(x => x.AssignedById).IsRequired(); builder.Property(x => x.AssignedById).IsRequired();
// Soft-delete only => DB soll nicht cascade-löschen
builder.HasOne(x => x.Person) builder.HasOne(x => x.Person)
.WithMany(x => x.Expenses) .WithMany(x => x.Expenses)
.HasForeignKey(x => x.PersonId) .HasForeignKey(x => x.PersonId)
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.NoAction);
builder.HasOne(x => x.Day) builder.HasOne(x => x.Day)
.WithMany() .WithMany()
.HasForeignKey(x => x.DayId) .HasForeignKey(x => x.DayId)
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.NoAction);
builder.HasOne(x => x.Expense) builder.HasOne(x => x.Expense)
.WithMany() .WithMany()
.HasForeignKey(x => x.ExpenseId) .HasForeignKey(x => x.ExpenseId)
.OnDelete(DeleteBehavior.Restrict); .OnDelete(DeleteBehavior.NoAction);
builder.HasOne(x => x.Game) builder.HasOne(x => x.Game)
.WithMany() .WithMany()
.HasForeignKey(x => x.GameId) .HasForeignKey(x => x.GameId)
.OnDelete(DeleteBehavior.SetNull); // SQL Server: verhindert multiple cascade paths (SET NULL ist eine kaskadierende Aktion)
.OnDelete(DeleteBehavior.NoAction);
builder.HasIndex(x => x.ClubId);
builder.HasIndex(x => new { x.PersonId, x.DayId }); builder.HasIndex(x => new { x.PersonId, x.DayId });
builder.HasIndex(x => x.DayId); builder.HasIndex(x => x.DayId);
builder.HasIndex(x => x.GameId); builder.HasIndex(x => x.GameId);
builder.HasQueryFilter(pe => builder.HasQueryFilter(pe =>
// required Principals müssen sichtbar sein !pe.IsDeleted &&
!pe.Club.IsDeleted &&
!pe.Person.IsDeleted && !pe.Person.IsDeleted &&
!pe.Day.IsDeleted && !pe.Day.IsDeleted &&
!pe.Expense.IsDeleted && !pe.Expense.IsDeleted &&
// optionales Game: entweder kein Game, oder Game + dessen Day sichtbar
(pe.GameId == null || (!pe.Game!.IsDeleted && !pe.Game!.Day.IsDeleted)) (pe.GameId == null || (!pe.Game!.IsDeleted && !pe.Game!.Day.IsDeleted))
); );
} }
} }

View File

@ -26,6 +26,8 @@ public class TriggerConfiguration : IEntityTypeConfiguration<Trigger>
.HasConversion<int>() .HasConversion<int>()
.IsRequired(); .IsRequired();
builder.HasIndex(x => x.ExpenseTriggerType);
builder.HasQueryFilter(x => !x.IsDeleted); builder.HasQueryFilter(x => !x.IsDeleted);
} }
} }

View File

@ -20,12 +20,12 @@ public class UserProfileClubConfiguration : IEntityTypeConfiguration<UserProfile
builder.HasOne(x => x.UserProfile) builder.HasOne(x => x.UserProfile)
.WithMany(x => x.Clubs) .WithMany(x => x.Clubs)
.HasForeignKey(x => x.UserProfileId) .HasForeignKey(x => x.UserProfileId)
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.NoAction);
builder.HasOne(x => x.Club) builder.HasOne(x => x.Club)
.WithMany() .WithMany()
.HasForeignKey(x => x.ClubId) .HasForeignKey(x => x.ClubId)
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.NoAction);
builder.Property(x => x.IsDefault).IsRequired(); builder.Property(x => x.IsDefault).IsRequired();