diff --git a/src/Koogle.Domain/Entities/PersonExpense.cs b/src/Koogle.Domain/Entities/PersonExpense.cs
index 6e9e38b..d7e19a1 100644
--- a/src/Koogle.Domain/Entities/PersonExpense.cs
+++ b/src/Koogle.Domain/Entities/PersonExpense.cs
@@ -30,6 +30,11 @@ public class PersonExpense : BaseEntity
///
public Guid? GameId { get; set; } // Foreign Key
+ ///
+ /// Id of a club.
+ ///
+ public Guid ClubId { get; set; }
+
// Stammdaten
///
@@ -57,11 +62,6 @@ public class PersonExpense : BaseEntity
// Navigation Properties
- ///
- /// Id of a club.
- ///
- public Guid ClubId { get; set; }
-
///
/// ID des Benutzers, der die Zuordnung vorgenommen hat.
///
diff --git a/src/Koogle.Infrastructure/Data/AppDbContext.cs b/src/Koogle.Infrastructure/Data/AppDbContext.cs
index 936a76d..3dc06f1 100644
--- a/src/Koogle.Infrastructure/Data/AppDbContext.cs
+++ b/src/Koogle.Infrastructure/Data/AppDbContext.cs
@@ -42,4 +42,33 @@ public class AppDbContext : DbContext
// Deine IEntityTypeConfiguration<> Klassen automatisch anwenden
modelBuilder.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly);
}
+
+
+
+ public override int SaveChanges()
+ {
+ ConvertDeletesToSoftDeletes();
+ return base.SaveChanges();
+ }
+
+ public override Task SaveChangesAsync(CancellationToken cancellationToken = default)
+ {
+ ConvertDeletesToSoftDeletes();
+ return base.SaveChangesAsync(cancellationToken);
+ }
+
+ private void ConvertDeletesToSoftDeletes()
+ {
+ foreach (var entry in ChangeTracker.Entries())
+ {
+ if (entry.State == EntityState.Deleted)
+ {
+ entry.State = EntityState.Modified;
+ entry.Entity.IsDeleted = true;
+ entry.Entity.ModifiedAt = DateTime.UtcNow;
+ }
+ }
+ }
+
+
}
diff --git a/src/Koogle.Infrastructure/Data/Configurations/ClubConfiguration.cs b/src/Koogle.Infrastructure/Data/Configurations/ClubConfiguration.cs
index 4f6a7fc..d08c2ae 100644
--- a/src/Koogle.Infrastructure/Data/Configurations/ClubConfiguration.cs
+++ b/src/Koogle.Infrastructure/Data/Configurations/ClubConfiguration.cs
@@ -21,6 +21,8 @@ public class ClubConfiguration : IEntityTypeConfiguration
.HasConversion()
.IsRequired();
+ builder.HasIndex(x => x.Name);
+
builder.HasQueryFilter(x => !x.IsDeleted);
}
}
\ No newline at end of file
diff --git a/src/Koogle.Infrastructure/Data/Configurations/DayPersonConfiguration.cs b/src/Koogle.Infrastructure/Data/Configurations/DayPersonConfiguration.cs
index c3f3dfc..d92fa54 100644
--- a/src/Koogle.Infrastructure/Data/Configurations/DayPersonConfiguration.cs
+++ b/src/Koogle.Infrastructure/Data/Configurations/DayPersonConfiguration.cs
@@ -32,14 +32,14 @@ public class DayPersonConfiguration : IEntityTypeConfiguration
.WithMany(x => x.DayPersons)
.HasForeignKey(x => new { x.DayId, x.ClubId })
.HasPrincipalKey(d => new { d.Id, d.ClubId })
- .OnDelete(DeleteBehavior.Cascade);
+ .OnDelete(DeleteBehavior.NoAction);
// Club-Konsistenz: (PersonId, ClubId) muss in Person(Id, ClubId) existieren
builder.HasOne(x => x.Person)
.WithMany(x => x.DayPersons)
.HasForeignKey(x => new { x.PersonId, x.ClubId })
.HasPrincipalKey(p => new { p.Id, p.ClubId })
- .OnDelete(DeleteBehavior.Cascade);
+ .OnDelete(DeleteBehavior.NoAction);
@@ -58,10 +58,6 @@ public class DayPersonConfiguration : IEntityTypeConfiguration
- builder.HasIndex(x => x.ClubId);
- builder.HasIndex(x => x.DayId);
- builder.HasIndex(x => x.PersonId);
-
}
}
diff --git a/src/Koogle.Infrastructure/Data/Configurations/ExpenseConfiguration.cs b/src/Koogle.Infrastructure/Data/Configurations/ExpenseConfiguration.cs
index b60b2ec..d80c365 100644
--- a/src/Koogle.Infrastructure/Data/Configurations/ExpenseConfiguration.cs
+++ b/src/Koogle.Infrastructure/Data/Configurations/ExpenseConfiguration.cs
@@ -42,12 +42,13 @@ public class ExpenseConfiguration : IEntityTypeConfiguration
builder.HasOne(x => x.Club)
.WithMany()
.HasForeignKey(x => x.ClubId)
- .OnDelete(DeleteBehavior.Restrict);
+ .OnDelete(DeleteBehavior.NoAction);
// Alternate Key für Composite-FK in ExpenseTrigger: (Id, ClubId)
builder.HasAlternateKey(x => new { x.Id, x.ClubId });
builder.HasIndex(x => x.ClubId);
+ builder.HasIndex(x => new { x.ClubId, x.Name });
builder.HasQueryFilter(x => !x.IsDeleted);
diff --git a/src/Koogle.Infrastructure/Data/Configurations/ExpenseTriggerConfiguration.cs b/src/Koogle.Infrastructure/Data/Configurations/ExpenseTriggerConfiguration.cs
index 06475d9..e7bbf32 100644
--- a/src/Koogle.Infrastructure/Data/Configurations/ExpenseTriggerConfiguration.cs
+++ b/src/Koogle.Infrastructure/Data/Configurations/ExpenseTriggerConfiguration.cs
@@ -38,12 +38,12 @@ public class ExpenseTriggerConfiguration : IEntityTypeConfiguration new { x.ExpenseId, x.ClubId })
.HasPrincipalKey(e => new { e.Id, e.ClubId })
- .OnDelete(DeleteBehavior.Cascade);
+ .OnDelete(DeleteBehavior.NoAction);
builder.HasOne(x => x.Trigger)
.WithMany()
.HasForeignKey(x => x.TriggerId)
- .OnDelete(DeleteBehavior.Cascade);
+ .OnDelete(DeleteBehavior.NoAction);
builder.HasIndex(x => x.ExpenseId);
builder.HasIndex(x => x.TriggerId);
diff --git a/src/Koogle.Infrastructure/Data/Configurations/GameConfiguration.cs b/src/Koogle.Infrastructure/Data/Configurations/GameConfiguration.cs
index 57f2850..49656a5 100644
--- a/src/Koogle.Infrastructure/Data/Configurations/GameConfiguration.cs
+++ b/src/Koogle.Infrastructure/Data/Configurations/GameConfiguration.cs
@@ -39,7 +39,7 @@ public class GameConfiguration : IEntityTypeConfiguration
builder.HasOne(x => x.Day)
.WithMany()
.HasForeignKey(x => x.DayId)
- .OnDelete(DeleteBehavior.Cascade);
+ .OnDelete(DeleteBehavior.NoAction);
// Mehrere Games pro Day -> Index nicht unique
builder.HasIndex(x => x.DayId);
diff --git a/src/Koogle.Infrastructure/Data/Configurations/GamePersonConfiguration.cs b/src/Koogle.Infrastructure/Data/Configurations/GamePersonConfiguration.cs
index efc0161..834ca64 100644
--- a/src/Koogle.Infrastructure/Data/Configurations/GamePersonConfiguration.cs
+++ b/src/Koogle.Infrastructure/Data/Configurations/GamePersonConfiguration.cs
@@ -20,14 +20,16 @@ public class GamePersonConfiguration : IEntityTypeConfiguration
builder.HasOne(x => x.Game)
.WithMany(x => x.GamePersons)
.HasForeignKey(x => x.GameId)
- .OnDelete(DeleteBehavior.Cascade);
+ .OnDelete(DeleteBehavior.NoAction);
builder.HasOne(x => x.Person)
.WithMany()
.HasForeignKey(x => x.PersonId)
- .OnDelete(DeleteBehavior.Cascade);
+ .OnDelete(DeleteBehavior.NoAction);
+
builder.HasIndex(x => x.PersonId);
+ builder.HasIndex(x => x.GameId);
// ✅ Matching Query Filter:
diff --git a/src/Koogle.Infrastructure/Data/Configurations/PersonConfiguration.cs b/src/Koogle.Infrastructure/Data/Configurations/PersonConfiguration.cs
index 4bcaf3c..f583d9d 100644
--- a/src/Koogle.Infrastructure/Data/Configurations/PersonConfiguration.cs
+++ b/src/Koogle.Infrastructure/Data/Configurations/PersonConfiguration.cs
@@ -36,7 +36,7 @@ public class PersonConfiguration : IEntityTypeConfiguration
builder.HasMany(x => x.Expenses)
.WithOne(x => x.Person)
.HasForeignKey(x => x.PersonId)
- .OnDelete(DeleteBehavior.Cascade);
+ .OnDelete(DeleteBehavior.NoAction);
builder.HasIndex(x => x.ClubId);
diff --git a/src/Koogle.Infrastructure/Data/Configurations/PersonExpenseConfiguration.cs b/src/Koogle.Infrastructure/Data/Configurations/PersonExpenseConfiguration.cs
index 20a6763..bc8b449 100644
--- a/src/Koogle.Infrastructure/Data/Configurations/PersonExpenseConfiguration.cs
+++ b/src/Koogle.Infrastructure/Data/Configurations/PersonExpenseConfiguration.cs
@@ -39,40 +39,49 @@ public class PersonExpenseConfiguration : IEntityTypeConfiguration x.AssignedById).IsRequired();
+ // Soft-delete only => DB soll nicht cascade-löschen
builder.HasOne(x => x.Person)
.WithMany(x => x.Expenses)
.HasForeignKey(x => x.PersonId)
- .OnDelete(DeleteBehavior.Cascade);
+ .OnDelete(DeleteBehavior.NoAction);
builder.HasOne(x => x.Day)
.WithMany()
.HasForeignKey(x => x.DayId)
- .OnDelete(DeleteBehavior.Cascade);
+ .OnDelete(DeleteBehavior.NoAction);
builder.HasOne(x => x.Expense)
.WithMany()
.HasForeignKey(x => x.ExpenseId)
- .OnDelete(DeleteBehavior.Restrict);
+ .OnDelete(DeleteBehavior.NoAction);
+
builder.HasOne(x => x.Game)
.WithMany()
.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 => x.DayId);
builder.HasIndex(x => x.GameId);
+
+
builder.HasQueryFilter(pe =>
- // required Principals müssen sichtbar sein
+ !pe.IsDeleted &&
+ !pe.Club.IsDeleted &&
!pe.Person.IsDeleted &&
!pe.Day.IsDeleted &&
!pe.Expense.IsDeleted &&
- // optionales Game: entweder kein Game, oder Game + dessen Day sichtbar
(pe.GameId == null || (!pe.Game!.IsDeleted && !pe.Game!.Day.IsDeleted))
);
+
}
}
diff --git a/src/Koogle.Infrastructure/Data/Configurations/TriggerConfiguration.cs b/src/Koogle.Infrastructure/Data/Configurations/TriggerConfiguration.cs
index 5c314cf..c6cce4a 100644
--- a/src/Koogle.Infrastructure/Data/Configurations/TriggerConfiguration.cs
+++ b/src/Koogle.Infrastructure/Data/Configurations/TriggerConfiguration.cs
@@ -26,6 +26,8 @@ public class TriggerConfiguration : IEntityTypeConfiguration
.HasConversion()
.IsRequired();
+ builder.HasIndex(x => x.ExpenseTriggerType);
+
builder.HasQueryFilter(x => !x.IsDeleted);
}
}
diff --git a/src/Koogle.Infrastructure/Data/Configurations/UserProfileClubConfiguration.cs b/src/Koogle.Infrastructure/Data/Configurations/UserProfileClubConfiguration.cs
index 9dd60d7..ff838e4 100644
--- a/src/Koogle.Infrastructure/Data/Configurations/UserProfileClubConfiguration.cs
+++ b/src/Koogle.Infrastructure/Data/Configurations/UserProfileClubConfiguration.cs
@@ -20,12 +20,12 @@ public class UserProfileClubConfiguration : IEntityTypeConfiguration x.UserProfile)
.WithMany(x => x.Clubs)
.HasForeignKey(x => x.UserProfileId)
- .OnDelete(DeleteBehavior.Cascade);
+ .OnDelete(DeleteBehavior.NoAction);
builder.HasOne(x => x.Club)
.WithMany()
.HasForeignKey(x => x.ClubId)
- .OnDelete(DeleteBehavior.Cascade);
+ .OnDelete(DeleteBehavior.NoAction);
builder.Property(x => x.IsDefault).IsRequired();