CrossModuleNavigationLoader.cs
7,061 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.Shared.Contracts.Users; |
| 7 | using SplitApp.Shared.Messaging.Integration.Users; |
| 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 (and now process boundaries) — the WebApp does |
| 15 | /// it manually via <see cref="IUserLookup"/> (RabbitMQ RPC) for users and the in-process |
| 16 | /// DbContexts for trips/currencies. |
| 17 | /// </summary> |
| 18 | public class CrossModuleNavigationLoader |
| 19 | { |
| 20 | private readonly IUserLookup _users; |
| 21 | private readonly TripsDbContext _trips; |
| 22 | private readonly ExpensesDbContext _expenses; |
| 23 | |
| 24 | public CrossModuleNavigationLoader(IUserLookup users, TripsDbContext trips, ExpensesDbContext expenses) |
| 25 | { |
| 26 | _users = users; |
| 27 | _trips = trips; |
| 28 | _expenses = expenses; |
| 29 | } |
| 30 | |
| 31 | private async Task<IReadOnlyDictionary<Guid, UserDto>> UsersByIdAsync(IEnumerable<Guid> ids) |
| 32 | => await _users.GetByIdsAsync(ids); |
| 33 | |
| 34 | private async Task<Dictionary<Guid, Currency>> CurrenciesByIdAsync(IEnumerable<Guid> ids) |
| 35 | { |
| 36 | var idList = ids.Where(id => id != Guid.Empty).Distinct().ToList(); |
| 37 | if (idList.Count == 0) return new Dictionary<Guid, Currency>(); |
| 38 | return await _expenses.Currencies.Where(c => idList.Contains(c.Id)).ToDictionaryAsync(c => c.Id); |
| 39 | } |
| 40 | |
| 41 | private async Task<Dictionary<Guid, Trip>> TripsByIdAsync(IEnumerable<Guid> ids) |
| 42 | { |
| 43 | var idList = ids.Where(id => id != Guid.Empty).Distinct().ToList(); |
| 44 | if (idList.Count == 0) return new Dictionary<Guid, Trip>(); |
| 45 | return await _trips.Trips.Where(t => idList.Contains(t.Id)).ToDictionaryAsync(t => t.Id); |
| 46 | } |
| 47 | |
| 48 | public async Task PopulateAsync(Trip? trip) |
| 49 | { |
| 50 | if (trip == null) return; |
| 51 | var users = await UsersByIdAsync(new[] { trip.CreatedById }); |
| 52 | var currencies = await CurrenciesByIdAsync(new[] { trip.DefaultCurrencyId }); |
| 53 | if (users.TryGetValue(trip.CreatedById, out var creator)) trip.CreatedBy = creator; |
| 54 | if (currencies.TryGetValue(trip.DefaultCurrencyId, out var currency)) trip.DefaultCurrency = currency; |
| 55 | if (trip.Participants != null) |
| 56 | { |
| 57 | var pUsers = await UsersByIdAsync(trip.Participants.Select(p => p.UserId)); |
| 58 | foreach (var p in trip.Participants) |
| 59 | if (pUsers.TryGetValue(p.UserId, out var u)) p.User = u; |
| 60 | } |
| 61 | if (trip.Invitations != null) |
| 62 | { |
| 63 | var iUsers = await UsersByIdAsync(trip.Invitations.Select(i => i.InvitedByUserId)); |
| 64 | foreach (var i in trip.Invitations) |
| 65 | if (iUsers.TryGetValue(i.InvitedByUserId, out var u)) i.InvitedByUser = u; |
| 66 | } |
| 67 | } |
| 68 | |
| 69 | public async Task PopulateAsync(IEnumerable<Trip> trips) |
| 70 | { |
| 71 | foreach (var t in trips) await PopulateAsync(t); |
| 72 | } |
| 73 | |
| 74 | public async Task PopulateAsync(TripParticipant? p) |
| 75 | { |
| 76 | if (p == null) return; |
| 77 | var users = await UsersByIdAsync(new[] { p.UserId }); |
| 78 | if (users.TryGetValue(p.UserId, out var u)) p.User = u; |
| 79 | } |
| 80 | |
| 81 | public async Task PopulateAsync(IEnumerable<TripParticipant> ps) |
| 82 | { |
| 83 | var ids = ps.Select(p => p.UserId).ToList(); |
| 84 | var users = await UsersByIdAsync(ids); |
| 85 | foreach (var p in ps) if (users.TryGetValue(p.UserId, out var u)) p.User = u; |
| 86 | } |
| 87 | |
| 88 | public async Task PopulateAsync(Expense? e) |
| 89 | { |
| 90 | if (e == null) return; |
| 91 | var users = await UsersByIdAsync(new[] { e.PaidByUserId }); |
| 92 | if (users.TryGetValue(e.PaidByUserId, out var u)) e.PaidByUser = u; |
| 93 | if (e.Splits != null) |
| 94 | { |
| 95 | var splitUsers = await UsersByIdAsync(e.Splits.Select(s => s.UserId)); |
| 96 | foreach (var s in e.Splits) |
| 97 | if (splitUsers.TryGetValue(s.UserId, out var su)) s.User = su; |
| 98 | } |
| 99 | } |
| 100 | |
| 101 | public async Task PopulateAsync(IEnumerable<Expense> es) |
| 102 | { |
| 103 | foreach (var e in es) await PopulateAsync(e); |
| 104 | } |
| 105 | |
| 106 | public async Task PopulateAsync(SettlementPlan? plan) |
| 107 | { |
| 108 | if (plan == null) return; |
| 109 | var users = await UsersByIdAsync( |
| 110 | new[] { plan.CreatedByUserId } |
| 111 | .Concat(plan.Payments?.Select(p => p.FromUserId) ?? Enumerable.Empty<Guid>()) |
| 112 | .Concat(plan.Payments?.Select(p => p.ToUserId) ?? Enumerable.Empty<Guid>())); |
| 113 | if (users.TryGetValue(plan.CreatedByUserId, out var creator)) plan.CreatedByUser = creator; |
| 114 | if (plan.Payments != null) |
| 115 | foreach (var p in plan.Payments) |
| 116 | { |
| 117 | if (users.TryGetValue(p.FromUserId, out var fu)) p.FromUser = fu; |
| 118 | if (users.TryGetValue(p.ToUserId, out var tu)) p.ToUser = tu; |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | public async Task PopulateAsync(SettlementPayment? p) |
| 123 | { |
| 124 | if (p == null) return; |
| 125 | var users = await UsersByIdAsync(new[] { p.FromUserId, p.ToUserId }); |
| 126 | if (users.TryGetValue(p.FromUserId, out var fu)) p.FromUser = fu; |
| 127 | if (users.TryGetValue(p.ToUserId, out var tu)) p.ToUser = tu; |
| 128 | } |
| 129 | |
| 130 | public async Task PopulateAsync(SplitPreset? sp) |
| 131 | { |
| 132 | if (sp == null) return; |
| 133 | var users = await UsersByIdAsync( |
| 134 | new[] { sp.CreatedById } |
| 135 | .Concat(sp.Members?.Select(m => m.UserId) ?? Enumerable.Empty<Guid>())); |
| 136 | if (users.TryGetValue(sp.CreatedById, out var creator)) sp.CreatedBy = creator; |
| 137 | if (sp.Members != null) |
| 138 | foreach (var m in sp.Members) |
| 139 | if (users.TryGetValue(m.UserId, out var u)) m.User = u; |
| 140 | } |
| 141 | |
| 142 | public async Task PopulateAsync(IEnumerable<SplitPreset> sps) |
| 143 | { |
| 144 | foreach (var sp in sps) await PopulateAsync(sp); |
| 145 | } |
| 146 | |
| 147 | public async Task PopulateAsync(TripPoll? poll) |
| 148 | { |
| 149 | if (poll == null) return; |
| 150 | var users = await UsersByIdAsync(new[] { poll.CreatedByUserId }); |
| 151 | if (users.TryGetValue(poll.CreatedByUserId, out var u)) poll.CreatedByUser = u; |
| 152 | } |
| 153 | |
| 154 | public async Task PopulateAsync(IEnumerable<TripPoll> polls) |
| 155 | { |
| 156 | foreach (var p in polls) await PopulateAsync(p); |
| 157 | } |
| 158 | |
| 159 | public async Task PopulateAsync(TripWishlistItem? item) |
| 160 | { |
| 161 | if (item == null) return; |
| 162 | var users = await UsersByIdAsync(new[] { item.AddedByUserId }); |
| 163 | if (users.TryGetValue(item.AddedByUserId, out var u)) item.AddedByUser = u; |
| 164 | } |
| 165 | |
| 166 | public async Task PopulateAsync(IEnumerable<TripWishlistItem> items) |
| 167 | { |
| 168 | foreach (var i in items) await PopulateAsync(i); |
| 169 | } |
| 170 | |
| 171 | public async Task PopulateAsync(TripInvitation? inv) |
| 172 | { |
| 173 | if (inv == null) return; |
| 174 | var users = await UsersByIdAsync(new[] { inv.InvitedByUserId }); |
| 175 | if (users.TryGetValue(inv.InvitedByUserId, out var u)) inv.InvitedByUser = u; |
| 176 | } |
| 177 | } |
| 178 | |