profileShare

rasmusjy / splitapp-backend-clean-onion

Read-only snapshot

No repository description.

main default branch 429 files Expires Sep 13, 2026, 9:06 AM
TripRepository.cs 5,829 bytes
1 using App.Domain;
2 using App.Domain.Contracts;
3 using Microsoft.EntityFrameworkCore;
4
5 namespace App.DAL.EF.Repositories;
6
7 public class TripRepository : BaseRepository<Trip>, ITripRepository
8 {
9 public TripRepository(AppDbContext dbContext) : base(dbContext)
10 {
11 }
12
13 public override async Task<IEnumerable<Trip>> GetAllAsync()
14 {
15 return await DbContext.Trips
16 .Include(t => t.DefaultCurrency)
17 .Include(t => t.CreatedBy)
18 .ToListAsync();
19 }
20
21 public override async Task<Trip?> GetByIdAsync(Guid id)
22 {
23 return await DbContext.Trips
24 .Include(t => t.DefaultCurrency)
25 .Include(t => t.CreatedBy)
26 .FirstOrDefaultAsync(t => t.Id == id);
27 }
28
29 public async Task<IEnumerable<Trip>> GetUserTripsAsync(Guid userId)
30 {
31 return await DbContext.Trips
32 .Include(t => t.DefaultCurrency)
33 .Include(t => t.Participants!)
34 .ThenInclude(p => p.User)
35 .Where(t => t.Participants!.Any(p => p.UserId == userId && p.IsActive))
36 .ToListAsync();
37 }
38
39 public async Task<Trip?> GetByIdWithDetailsAsync(Guid id)
40 {
41 return await DbContext.Trips
42 .Include(t => t.DefaultCurrency)
43 .Include(t => t.Participants!)
44 .ThenInclude(p => p.User)
45 .Include(t => t.Expenses)
46 .FirstOrDefaultAsync(t => t.Id == id);
47 }
48
49 // Override because AppDbContext sets all FKs to DeleteBehavior.Restrict,
50 // which blocks Trip deletion as soon as any child row exists (and a Trip
51 // always has at least the organizer TripParticipant).
52 //
53 // Restrict also prevents EF's in-memory cascade: calling Remove(trip)
54 // alone makes the tracker try to null the children's FKs (which are
55 // non-nullable) and throws. We must therefore explicitly mark every
56 // descendant as Deleted before removing the Trip itself. Everything
57 // happens in a single SaveChanges, so it's still one DB transaction.
58 public override async Task<Trip?> RemoveAsync(Guid id)
59 {
60 var trip = await DbContext.Trips
61 .Include(t => t.Participants)
62 .Include(t => t.Invitations)
63 .Include(t => t.BudgetCategories)
64 .Include(t => t.WishlistItems!).ThenInclude(w => w.Votes)
65 .Include(t => t.Polls!).ThenInclude(p => p.Options!).ThenInclude(o => o.Votes)
66 .Include(t => t.Expenses!).ThenInclude(e => e.Splits)
67 .Include(t => t.SettlementPlans!).ThenInclude(sp => sp.Payments)
68 .FirstOrDefaultAsync(t => t.Id == id);
69
70 if (trip == null) return null;
71
72 // SplitPreset has TripId FK but no navigation collection on Trip,
73 // so load it separately.
74 var splitPresets = await DbContext.SplitPresets
75 .Include(sp => sp.Members)
76 .Where(sp => sp.TripId == id)
77 .ToListAsync();
78
79 // Delete grandchildren first, then children, then Trip.
80 // Order matters: a parent row cannot be deleted while its dependents
81 // still reference it (Restrict).
82
83 // Expenses -> ExpenseSplits
84 if (trip.Expenses != null)
85 {
86 foreach (var expense in trip.Expenses)
87 {
88 if (expense.Splits != null && expense.Splits.Count > 0)
89 DbContext.ExpenseSplits.RemoveRange(expense.Splits);
90 }
91 DbContext.Expenses.RemoveRange(trip.Expenses);
92 }
93
94 // SettlementPlans -> SettlementPayments
95 if (trip.SettlementPlans != null)
96 {
97 foreach (var plan in trip.SettlementPlans)
98 {
99 if (plan.Payments != null && plan.Payments.Count > 0)
100 DbContext.SettlementPayments.RemoveRange(plan.Payments);
101 }
102 DbContext.SettlementPlans.RemoveRange(trip.SettlementPlans);
103 }
104
105 // Polls -> PollOptions -> PollVotes
106 if (trip.Polls != null)
107 {
108 foreach (var poll in trip.Polls)
109 {
110 if (poll.Options != null)
111 {
112 foreach (var option in poll.Options)
113 {
114 if (option.Votes != null && option.Votes.Count > 0)
115 DbContext.TripPollVotes.RemoveRange(option.Votes);
116 }
117 DbContext.TripPollOptions.RemoveRange(poll.Options);
118 }
119 }
120 DbContext.TripPolls.RemoveRange(trip.Polls);
121 }
122
123 // WishlistItems -> WishlistVotes
124 if (trip.WishlistItems != null)
125 {
126 foreach (var item in trip.WishlistItems)
127 {
128 if (item.Votes != null && item.Votes.Count > 0)
129 DbContext.TripWishlistVotes.RemoveRange(item.Votes);
130 }
131 DbContext.TripWishlistItems.RemoveRange(trip.WishlistItems);
132 }
133
134 // SplitPresets -> SplitPresetMembers
135 foreach (var preset in splitPresets)
136 {
137 if (preset.Members != null && preset.Members.Count > 0)
138 DbContext.SplitPresetMembers.RemoveRange(preset.Members);
139 }
140 DbContext.SplitPresets.RemoveRange(splitPresets);
141
142 // Direct children of Trip with no further descendants referenced here
143 if (trip.BudgetCategories != null && trip.BudgetCategories.Count > 0)
144 DbContext.BudgetCategories.RemoveRange(trip.BudgetCategories);
145 if (trip.Invitations != null && trip.Invitations.Count > 0)
146 DbContext.TripInvitations.RemoveRange(trip.Invitations);
147 if (trip.Participants != null && trip.Participants.Count > 0)
148 DbContext.TripParticipants.RemoveRange(trip.Participants);
149
150 DbContext.Trips.Remove(trip);
151 return trip;
152 }
153 }
154