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

lucaslorentz / auto-compute / 11755569045

09 Nov 2024 10:37AM UTC coverage: 80.033% (-1.7%) from 81.696%
11755569045

push

github

lucaslorentz
Add initial support for computed navigations and observers

159 of 357 new or added lines in 22 files covered. (44.54%)

41 existing lines in 6 files now uncovered.

1459 of 1823 relevant lines covered (80.03%)

113.72 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 LLL.AutoCompute.EFCore.Metadata.Internal;
4
using Microsoft.EntityFrameworkCore;
5
using Microsoft.EntityFrameworkCore.Metadata;
6

7
namespace LLL.AutoCompute.EFCore.Internal;
8

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

188
        return navigationEntry.CurrentValue;
91✔
189
    }
190

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

382
                                foreach (var otherEntity in otherReferenceEntry.GetEntities())
20✔
383
                                {
384
                                    incrementalContext?.SetShouldLoadAll(otherEntity);
5✔
385
                                    incrementalContext?.AddIncrementalEntity(entity, this, otherEntity);
5✔
386
                                }
387
                            }
388
                        }
389
                    }
390
                }
391
            }
392
        }
393
        return affectedEntities;
155✔
394
    }
395
}
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