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 | |