• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lucaslorentz / auto-compute / 11763467169

10 Nov 2024 08:07AM UTC coverage: 77.292% (-12.0%) from 89.255%
11763467169

push

github

lucaslorentz
Add initial support for computed navigations and observers

168 of 433 new or added lines in 25 files covered. (38.8%)

7 existing lines in 5 files now uncovered.

1467 of 1898 relevant lines covered (77.29%)

109.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.76
/src/LLL.AutoCompute.EFCore/Internal/EFCoreEntityNavigation.cs
1
using System.Linq.Expressions;
2
using System.Reflection;
3
using Microsoft.EntityFrameworkCore;
4
using Microsoft.EntityFrameworkCore.Metadata;
5

6
namespace LLL.AutoCompute.EFCore.Internal;
7

8
public abstract class EFCoreEntityNavigation(
21✔
9
    INavigationBase navigation)
21✔
10
    : EFCoreEntityMember
11
{
12
    public override IPropertyBase PropertyBase => navigation;
15✔
13
    public INavigationBase Navigation => navigation;
3,331✔
14
}
15

16
public class EFCoreEntityNavigation<TSourceEntity, TTargetEntity>(
17
    INavigationBase navigation
18
) : EFCoreEntityNavigation(navigation), IEntityNavigation<IEFCoreComputedInput, TSourceEntity, TTargetEntity>
21✔
19
    where TSourceEntity : class
20
    where TTargetEntity : class
21
{
NEW
22
    public virtual string Name => Navigation.Name;
×
23
    public virtual Type TargetEntityType => Navigation.TargetEntityType.ClrType;
27✔
24
    public virtual bool IsCollection => Navigation.IsCollection;
36✔
25

26
    public virtual string ToDebugString()
27
    {
NEW
28
        return $"{Navigation.DeclaringEntityType.ShortName()}.{Navigation.Name}";
×
29
    }
30

31
    public virtual IEntityNavigation<IEFCoreComputedInput, TTargetEntity, TSourceEntity> GetInverse()
32
    {
33
        var inverse = Navigation.Inverse
434✔
34
            ?? throw new InvalidOperationException($"No inverse for navigation '{Navigation.DeclaringType.ShortName()}.{Navigation.Name}'");
434✔
35

36
        return (EFCoreEntityNavigation<TTargetEntity, TSourceEntity>)inverse.GetEntityNavigation();
434✔
37
    }
38

39
    public virtual async Task<IReadOnlyCollection<TTargetEntity>> LoadOriginalAsync(
40
        IEFCoreComputedInput input,
41
        IReadOnlyCollection<TSourceEntity> sourceEntities,
42
        IncrementalContext incrementalContext)
43
    {
44
        await input.DbContext.BulkLoadAsync(sourceEntities, Navigation);
307✔
45

46
        var targetEntities = new HashSet<TTargetEntity>();
307✔
47
        foreach (var sourceEntity in sourceEntities)
958✔
48
        {
49
            var entityEntry = input.DbContext.Entry(sourceEntity!);
172✔
50
            if (entityEntry.State == EntityState.Added)
172✔
51
                continue;
52

53
            var navigationEntry = entityEntry.Navigation(Navigation);
88✔
54

55
            if (!navigationEntry.IsLoaded && entityEntry.State != EntityState.Detached)
88✔
56
                await navigationEntry.LoadAsync();
2✔
57

58
            foreach (var originalEntity in navigationEntry.GetOriginalEntities())
340✔
59
            {
60
                targetEntities.Add((TTargetEntity)originalEntity);
82✔
61
                incrementalContext?.AddIncrementalEntity(sourceEntity, this, originalEntity);
82✔
62
                if (Navigation.Inverse is not null)
82✔
63
                    incrementalContext?.AddIncrementalEntity(originalEntity, GetInverse(), sourceEntity);
82✔
64
            }
65
        }
66
        return targetEntities;
307✔
67
    }
68

69
    public virtual async Task<IReadOnlyCollection<TTargetEntity>> LoadCurrentAsync(
70
        IEFCoreComputedInput input,
71
        IReadOnlyCollection<TSourceEntity> sourceEntities,
72
        IncrementalContext incrementalContext)
73
    {
74
        await input.DbContext.BulkLoadAsync(sourceEntities, Navigation);
307✔
75

76
        var targetEntities = new HashSet<TTargetEntity>();
307✔
77
        foreach (var sourceEntity in sourceEntities)
958✔
78
        {
79
            var entityEntry = input.DbContext.Entry(sourceEntity!);
172✔
80
            var navigationEntry = entityEntry.Navigation(Navigation);
172✔
81

82
            foreach (var entity in navigationEntry.GetEntities())
642✔
83
            {
84
                targetEntities.Add((TTargetEntity)entity);
149✔
85
                incrementalContext?.AddIncrementalEntity(sourceEntity, this, entity);
149✔
86
                if (Navigation.Inverse is not null)
149✔
87
                    incrementalContext?.AddIncrementalEntity(entity, GetInverse(), sourceEntity);
149✔
88
            }
89
        }
90
        return targetEntities;
307✔
91
    }
92

93
    public virtual Expression CreateOriginalValueExpression(
94
        IEntityMemberAccess<IEntityNavigation> memberAccess,
95
        Expression inputExpression)
96
    {
97
        return Expression.Convert(
19✔
98
            Expression.Call(
19✔
99
                Expression.Constant(this),
19✔
100
                GetType().GetMethod(nameof(GetOriginalValue), BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy)!,
19✔
101
                inputExpression,
19✔
102
                memberAccess.FromExpression
19✔
103
            ),
19✔
104
            Navigation.ClrType
19✔
105
        );
19✔
106
    }
107

108
    public virtual Expression CreateCurrentValueExpression(
109
        IEntityMemberAccess<IEntityNavigation> memberAccess,
110
        Expression inputExpression)
111
    {
112
        return Expression.Convert(
19✔
113
            Expression.Call(
19✔
114
                Expression.Constant(this),
19✔
115
                GetType().GetMethod(nameof(GetCurrentValue), BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy)!,
19✔
116
                inputExpression,
19✔
117
                memberAccess.FromExpression
19✔
118
            ),
19✔
119
            Navigation.ClrType
19✔
120
        );
19✔
121
    }
122

123
    public virtual Expression CreateIncrementalOriginalValueExpression(
124
        IEntityMemberAccess<IEntityNavigation> memberAccess,
125
        Expression inputExpression,
126
        Expression incrementalContextExpression)
127
    {
128
        return Expression.Convert(
17✔
129
            Expression.Call(
17✔
130
                Expression.Constant(this),
17✔
131
                GetType().GetMethod(nameof(GetIncrementalOriginalValue), BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy)!,
17✔
132
                inputExpression,
17✔
133
                memberAccess.FromExpression,
17✔
134
                incrementalContextExpression
17✔
135
            ),
17✔
136
            Navigation.ClrType
17✔
137
        );
17✔
138
    }
139

140
    public virtual Expression CreateIncrementalCurrentValueExpression(
141
        IEntityMemberAccess<IEntityNavigation> memberAccess,
142
        Expression inputExpression,
143
        Expression incrementalContextExpression)
144
    {
145
        return Expression.Convert(
17✔
146
            Expression.Call(
17✔
147
                Expression.Constant(this),
17✔
148
                GetType().GetMethod(nameof(GetIncrementalCurrentValue), BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy)!,
17✔
149
                inputExpression,
17✔
150
                memberAccess.FromExpression,
17✔
151
                incrementalContextExpression
17✔
152
            ),
17✔
153
            Navigation.ClrType
17✔
154
        );
17✔
155
    }
156

157
    protected virtual object? GetOriginalValue(IEFCoreComputedInput input, TSourceEntity ent)
158
    {
159
        var dbContext = input.DbContext;
36✔
160

161
        var entityEntry = dbContext.Entry(ent!);
36✔
162

163
        if (entityEntry.State == EntityState.Added)
36✔
NEW
164
            throw new Exception($"Cannot access navigation '{Navigation.DeclaringType.ShortName()}.{Navigation.Name}' original value for an added entity");
×
165

166
        var navigationEntry = entityEntry.Navigation(Navigation);
36✔
167

168
        if (!navigationEntry.IsLoaded && entityEntry.State != EntityState.Detached)
36✔
169
            navigationEntry.Load();
9✔
170

171
        return navigationEntry.GetOriginalValue();
36✔
172
    }
173

174
    protected virtual object? GetCurrentValue(IEFCoreComputedInput input, TSourceEntity ent)
175
    {
176
        var dbContext = input.DbContext;
91✔
177

178
        var entityEntry = dbContext.Entry(ent!);
91✔
179

180
        if (entityEntry.State == EntityState.Deleted)
91✔
NEW
181
            throw new Exception($"Cannot access navigation '{Navigation.DeclaringType.ShortName()}.{Navigation.Name}' current value for a deleted entity");
×
182

183
        var navigationEntry = entityEntry.Navigation(Navigation);
91✔
184
        if (!navigationEntry.IsLoaded && entityEntry.State != EntityState.Detached)
91✔
185
            navigationEntry.Load();
18✔
186

187
        return navigationEntry.CurrentValue;
91✔
188
    }
189

190
    protected virtual object? GetIncrementalOriginalValue(IEFCoreComputedInput input, TSourceEntity ent, IncrementalContext incrementalContext)
191
    {
192
        var entityEntry = input.DbContext.Entry(ent);
83✔
193

194
        if (entityEntry.State == EntityState.Added)
83✔
NEW
195
            throw new Exception($"Cannot access navigation '{Navigation.DeclaringType.ShortName()}.{Navigation.Name}' original value for an added entity");
×
196

197
        var incrementalEntities = incrementalContext!.GetIncrementalEntities(ent, this);
83✔
198

199
        var principalEntry = input.DbContext.Entry(ent);
83✔
200

201
        var entities = incrementalEntities
83✔
202
            .Where(e =>
83✔
203
            {
83✔
204
                if (Navigation is ISkipNavigation skipNavigation)
164✔
205
                {
83✔
206
                    var relatedEntry = input.DbContext.Entry(e);
95✔
207
                    skipNavigation.LoadJoinEntity(input, principalEntry, relatedEntry);
95✔
208
                    return skipNavigation.WasRelated(
95✔
209
                        input,
95✔
210
                        principalEntry,
95✔
211
                        relatedEntry);
95✔
212
                }
83✔
213
                else if (Navigation is INavigation directNav)
152✔
214
                {
83✔
215
                    return directNav.WasRelated(
152✔
216
                        principalEntry,
152✔
217
                        input.DbContext.Entry(e));
152✔
218
                }
83✔
219

83✔
220
                throw new Exception("Navigation not supported");
83✔
221
            })
83✔
222
            .ToArray();
83✔
223

224
        if (Navigation.IsCollection)
83✔
225
        {
226
            var collectionAccessor = Navigation.GetCollectionAccessor()!;
78✔
227
            var collection = collectionAccessor.Create();
78✔
228
            foreach (var entity in entities)
256✔
229
                collectionAccessor.AddStandalone(collection, entity);
50✔
230
            return collection;
78✔
231
        }
232
        else
233
        {
234
            return entities.FirstOrDefault();
5✔
235
        }
236
    }
237

238
    protected virtual object? GetIncrementalCurrentValue(IEFCoreComputedInput input, TSourceEntity ent, IncrementalContext incrementalContext)
239
    {
240
        var entityEntry = input.DbContext.Entry(ent);
112✔
241

242
        if (entityEntry.State == EntityState.Deleted)
112✔
NEW
243
            throw new Exception($"Cannot access navigation '{Navigation.DeclaringType.ShortName()}.{Navigation.Name}' current value for a deleted entity");
×
244

245
        var incrementalEntities = incrementalContext!.GetIncrementalEntities(ent, this);
112✔
246

247
        var principalEntry = input.DbContext.Entry(ent);
112✔
248

249
        var entities = incrementalEntities
112✔
250
            .Where(e =>
112✔
251
            {
112✔
252
                if (Navigation is ISkipNavigation skipNavigation)
205✔
253
                {
112✔
254
                    var relatedEntry = input.DbContext.Entry(e);
124✔
255
                    skipNavigation.LoadJoinEntity(input, principalEntry, relatedEntry);
124✔
256
                    return skipNavigation.IsRelated(
124✔
257
                        input,
124✔
258
                        principalEntry,
124✔
259
                        relatedEntry);
124✔
260
                }
112✔
261
                else if (Navigation is INavigation directNav)
193✔
262
                {
112✔
263
                    return directNav.IsRelated(
193✔
264
                        principalEntry,
193✔
265
                        input.DbContext.Entry(e));
193✔
266
                }
112✔
267

112✔
268
                throw new Exception("Navigation not supported");
112✔
269
            })
112✔
270
            .ToArray();
112✔
271

272
        if (Navigation.IsCollection)
112✔
273
        {
274
            var collectionAccessor = Navigation.GetCollectionAccessor()!;
108✔
275
            var collection = collectionAccessor.Create();
108✔
276
            foreach (var entity in entities)
342✔
277
                collectionAccessor.AddStandalone(collection, entity);
63✔
278
            return collection;
108✔
279
        }
280
        else
281
        {
282
            return entities.FirstOrDefault();
4✔
283
        }
284
    }
285

286
    public virtual async Task<IReadOnlyCollection<TSourceEntity>> GetAffectedEntitiesAsync(IEFCoreComputedInput input, IncrementalContext incrementalContext)
287
    {
288
        var affectedEntities = new HashSet<TSourceEntity>();
155✔
289
        foreach (var entityEntry in input.EntityEntriesOfType(Navigation.DeclaringEntityType))
706✔
290
        {
291
            if (entityEntry.State == EntityState.Added
198✔
292
                || entityEntry.State == EntityState.Deleted
198✔
293
                || entityEntry.State == EntityState.Modified)
198✔
294
            {
295
                var navigationEntry = entityEntry.Navigation(Navigation);
84✔
296
                if (entityEntry.State == EntityState.Added
84✔
297
                    || entityEntry.State == EntityState.Deleted
84✔
298
                    || navigationEntry.IsModified)
84✔
299
                {
300
                    affectedEntities.Add((TSourceEntity)entityEntry.Entity);
75✔
301

302
                    var modifiedEntities = navigationEntry.GetModifiedEntities();
75✔
303

304
                    foreach (var ent in modifiedEntities)
234✔
305
                    {
306
                        incrementalContext?.SetShouldLoadAll(ent);
42✔
307
                        incrementalContext?.AddIncrementalEntity(entityEntry.Entity, this, ent);
42✔
308
                    }
309
                }
310
            }
311
        }
312
        if (Navigation.Inverse is not null)
155✔
313
        {
314
            foreach (var entityEntry in input.EntityEntriesOfType(Navigation.Inverse.DeclaringEntityType))
666✔
315
            {
316
                if (entityEntry.State == EntityState.Added
178✔
317
                    || entityEntry.State == EntityState.Deleted
178✔
318
                    || entityEntry.State == EntityState.Modified)
178✔
319
                {
320
                    var inverseNavigationEntry = entityEntry.Navigation(Navigation.Inverse);
121✔
321
                    if (entityEntry.State == EntityState.Added
121✔
322
                        || entityEntry.State == EntityState.Deleted
121✔
323
                        || inverseNavigationEntry.IsModified)
121✔
324
                    {
325
                        if (!inverseNavigationEntry.IsLoaded && entityEntry.State != EntityState.Detached)
99✔
326
                            await inverseNavigationEntry.LoadAsync();
31✔
327

328
                        var modifiedEntities = inverseNavigationEntry.GetModifiedEntities();
99✔
329

330
                        foreach (var entity in modifiedEntities)
384✔
331
                        {
332
                            affectedEntities.Add((TSourceEntity)entity);
93✔
333
                            incrementalContext?.SetShouldLoadAll(entityEntry.Entity);
93✔
334
                            incrementalContext?.AddIncrementalEntity(entity, this, entityEntry.Entity);
93✔
335
                        }
336
                    }
337
                }
338
            }
339
        }
340
        if (Navigation is ISkipNavigation skipNavigation)
155✔
341
        {
342
            var dependentToPrincipal = skipNavigation.ForeignKey.DependentToPrincipal!;
22✔
343
            var joinReferenceToOther = skipNavigation.Inverse.ForeignKey.DependentToPrincipal;
22✔
344

345
            foreach (var joinEntry in input.EntityEntriesOfType(skipNavigation.JoinEntityType))
82✔
346
            {
347
                if (joinEntry.State == EntityState.Added
19✔
348
                    || joinEntry.State == EntityState.Deleted
19✔
349
                    || joinEntry.State == EntityState.Modified)
19✔
350
                {
351
                    var dependentToPrincipalEntry = joinEntry.Navigation(dependentToPrincipal);
15✔
352
                    var otherReferenceEntry = joinEntry.Reference(joinReferenceToOther!);
15✔
353

354
                    if (joinEntry.State == EntityState.Added
15✔
355
                        || joinEntry.State == EntityState.Deleted
15✔
356
                        || dependentToPrincipalEntry.IsModified)
15✔
357
                    {
358
                        if (!dependentToPrincipalEntry.IsLoaded && joinEntry.State != EntityState.Detached)
15✔
359
                            await dependentToPrincipalEntry.LoadAsync();
×
360

361
                        if (joinEntry.State != EntityState.Added)
15✔
362
                        {
363
                            foreach (var entity in dependentToPrincipalEntry.GetOriginalEntities())
40✔
364
                            {
365
                                affectedEntities.Add((TSourceEntity)entity);
10✔
366

367
                                foreach (var otherEntity in otherReferenceEntry.GetOriginalEntities())
40✔
368
                                {
369
                                    incrementalContext?.SetShouldLoadAll(otherEntity);
10✔
370
                                    incrementalContext?.AddIncrementalEntity(entity, this, otherEntity);
10✔
371
                                }
372
                            }
373
                        }
374

375
                        if (joinEntry.State != EntityState.Deleted)
15✔
376
                        {
377
                            foreach (var entity in dependentToPrincipalEntry.GetEntities())
20✔
378
                            {
379
                                affectedEntities.Add((TSourceEntity)entity);
5✔
380

381
                                foreach (var otherEntity in otherReferenceEntry.GetEntities())
20✔
382
                                {
383
                                    incrementalContext?.SetShouldLoadAll(otherEntity);
5✔
384
                                    incrementalContext?.AddIncrementalEntity(entity, this, otherEntity);
5✔
385
                                }
386
                            }
387
                        }
388
                    }
389
                }
390
            }
391
        }
392
        return affectedEntities;
155✔
393
    }
394
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc