profileShare

rasmusjy / splitapp-backend-modular-monolith

Read-only snapshot

No repository description.

main default branch 418 files Expires Sep 13, 2026, 9:06 AM
CrossModuleNavigationLoader.cs 7,141 bytes
1 using Microsoft.EntityFrameworkCore;
2 using SplitApp.Modules.Expenses.Domain.Entities;
3 using SplitApp.Modules.Expenses.Infrastructure.Persistence;
4 using SplitApp.Modules.Trips.Domain.Entities;
5 using SplitApp.Modules.Trips.Infrastructure.Persistence;
6 using SplitApp.Modules.Users.Domain.Entities;
7 using SplitApp.Modules.Users.Infrastructure.Persistence;
8
9 namespace SplitApp.WebApp.Application.Persistence;
10
11 /// <summary>
12 /// Composition-root helper that hydrates the [NotMapped] cross-module nav properties
13 /// after entities have been loaded by their owning module's DbContext. EF can't follow
14 /// these because the FK crosses schemas — the WebApp does it manually.
15 /// </summary>
16 public class CrossModuleNavigationLoader
17 {
18 private readonly UsersDbContext _users;
19 private readonly TripsDbContext _trips;
20 private readonly ExpensesDbContext _expenses;
21
22 public CrossModuleNavigationLoader(UsersDbContext users, TripsDbContext trips, ExpensesDbContext expenses)
23 {
24 _users = users;
25 _trips = trips;
26 _expenses = expenses;
27 }
28
29 private async Task<Dictionary<Guid, AppUser>> UsersByIdAsync(IEnumerable<Guid> ids)
30 {
31 var idList = ids.Where(id => id != Guid.Empty).Distinct().ToList();
32 if (idList.Count == 0) return new Dictionary<Guid, AppUser>();
33 return await _users.Users.Where(u => idList.Contains(u.Id)).ToDictionaryAsync(u => u.Id);
34 }
35
36 private async Task<Dictionary<Guid, Currency>> CurrenciesByIdAsync(IEnumerable<Guid> ids)
37 {
38 var idList = ids.Where(id => id != Guid.Empty).Distinct().ToList();
39 if (idList.Count == 0) return new Dictionary<Guid, Currency>();
40 return await _expenses.Currencies.Where(c => idList.Contains(c.Id)).ToDictionaryAsync(c => c.Id);
41 }
42
43 private async Task<Dictionary<Guid, Trip>> TripsByIdAsync(IEnumerable<Guid> ids)
44 {
45 var idList = ids.Where(id => id != Guid.Empty).Distinct().ToList();
46 if (idList.Count == 0) return new Dictionary<Guid, Trip>();
47 return await _trips.Trips.Where(t => idList.Contains(t.Id)).ToDictionaryAsync(t => t.Id);
48 }
49
50 public async Task PopulateAsync(Trip? trip)
51 {
52 if (trip == null) return;
53 var users = await UsersByIdAsync(new[] { trip.CreatedById });
54 var currencies = await CurrenciesByIdAsync(new[] { trip.DefaultCurrencyId });
55 if (users.TryGetValue(trip.CreatedById, out var creator)) trip.CreatedBy = creator;
56 if (currencies.TryGetValue(trip.DefaultCurrencyId, out var currency)) trip.DefaultCurrency = currency;
57 if (trip.Participants != null)
58 {
59 var pUsers = await UsersByIdAsync(trip.Participants.Select(p => p.UserId));
60 foreach (var p in trip.Participants)
61 if (pUsers.TryGetValue(p.UserId, out var u)) p.User = u;
62 }
63 if (trip.Invitations != null)
64 {
65 var iUsers = await UsersByIdAsync(trip.Invitations.Select(i => i.InvitedByUserId));
66 foreach (var i in trip.Invitations)
67 if (iUsers.TryGetValue(i.InvitedByUserId, out var u)) i.InvitedByUser = u;
68 }
69 }
70
71 public async Task PopulateAsync(IEnumerable<Trip> trips)
72 {
73 foreach (var t in trips) await PopulateAsync(t);
74 }
75
76 public async Task PopulateAsync(TripParticipant? p)
77 {
78 if (p == null) return;
79 var users = await UsersByIdAsync(new[] { p.UserId });
80 if (users.TryGetValue(p.UserId, out var u)) p.User = u;
81 }
82
83 public async Task PopulateAsync(IEnumerable<TripParticipant> ps)
84 {
85 var ids = ps.Select(p => p.UserId).ToList();
86 var users = await UsersByIdAsync(ids);
87 foreach (var p in ps) if (users.TryGetValue(p.UserId, out var u)) p.User = u;
88 }
89
90 public async Task PopulateAsync(Expense? e)
91 {
92 if (e == null) return;
93 var users = await UsersByIdAsync(new[] { e.PaidByUserId });
94 if (users.TryGetValue(e.PaidByUserId, out var u)) e.PaidByUser = u;
95 if (e.Splits != null)
96 {
97 var splitUsers = await UsersByIdAsync(e.Splits.Select(s => s.UserId));
98 foreach (var s in e.Splits)
99 if (splitUsers.TryGetValue(s.UserId, out var su)) s.User = su;
100 }
101 }
102
103 public async Task PopulateAsync(IEnumerable<Expense> es)
104 {
105 foreach (var e in es) await PopulateAsync(e);
106 }
107
108 public async Task PopulateAsync(SettlementPlan? plan)
109 {
110 if (plan == null) return;
111 var users = await UsersByIdAsync(
112 new[] { plan.CreatedByUserId }
113 .Concat(plan.Payments?.Select(p => p.FromUserId) ?? Enumerable.Empty<Guid>())
114 .Concat(plan.Payments?.Select(p => p.ToUserId) ?? Enumerable.Empty<Guid>()));
115 if (users.TryGetValue(plan.CreatedByUserId, out var creator)) plan.CreatedByUser = creator;
116 if (plan.Payments != null)
117 foreach (var p in plan.Payments)
118 {
119 if (users.TryGetValue(p.FromUserId, out var fu)) p.FromUser = fu;
120 if (users.TryGetValue(p.ToUserId, out var tu)) p.ToUser = tu;
121 }
122 }
123
124 public async Task PopulateAsync(SettlementPayment? p)
125 {
126 if (p == null) return;
127 var users = await UsersByIdAsync(new[] { p.FromUserId, p.ToUserId });
128 if (users.TryGetValue(p.FromUserId, out var fu)) p.FromUser = fu;
129 if (users.TryGetValue(p.ToUserId, out var tu)) p.ToUser = tu;
130 }
131
132 public async Task PopulateAsync(SplitPreset? sp)
133 {
134 if (sp == null) return;
135 var users = await UsersByIdAsync(
136 new[] { sp.CreatedById }
137 .Concat(sp.Members?.Select(m => m.UserId) ?? Enumerable.Empty<Guid>()));
138 if (users.TryGetValue(sp.CreatedById, out var creator)) sp.CreatedBy = creator;
139 if (sp.Members != null)
140 foreach (var m in sp.Members)
141 if (users.TryGetValue(m.UserId, out var u)) m.User = u;
142 }
143
144 public async Task PopulateAsync(IEnumerable<SplitPreset> sps)
145 {
146 foreach (var sp in sps) await PopulateAsync(sp);
147 }
148
149 public async Task PopulateAsync(TripPoll? poll)
150 {
151 if (poll == null) return;
152 var users = await UsersByIdAsync(new[] { poll.CreatedByUserId });
153 if (users.TryGetValue(poll.CreatedByUserId, out var u)) poll.CreatedByUser = u;
154 }
155
156 public async Task PopulateAsync(IEnumerable<TripPoll> polls)
157 {
158 foreach (var p in polls) await PopulateAsync(p);
159 }
160
161 public async Task PopulateAsync(TripWishlistItem? item)
162 {
163 if (item == null) return;
164 var users = await UsersByIdAsync(new[] { item.AddedByUserId });
165 if (users.TryGetValue(item.AddedByUserId, out var u)) item.AddedByUser = u;
166 }
167
168 public async Task PopulateAsync(IEnumerable<TripWishlistItem> items)
169 {
170 foreach (var i in items) await PopulateAsync(i);
171 }
172
173 public async Task PopulateAsync(TripInvitation? inv)
174 {
175 if (inv == null) return;
176 var users = await UsersByIdAsync(new[] { inv.InvitedByUserId });
177 if (users.TryGetValue(inv.InvitedByUserId, out var u)) inv.InvitedByUser = u;
178 }
179 }
180