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

lucaslorentz / auto-compute / 18778187096

24 Oct 2025 11:14AM UTC coverage: 83.295% (-0.2%) from 83.516%
18778187096

push

github

lucaslorentz
Move IncrementalContext param into Input param

56 of 58 new or added lines in 22 files covered. (96.55%)

8 existing lines in 5 files now uncovered.

1790 of 2149 relevant lines covered (83.29%)

880.64 hits per line

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

95.8
/src/LLL.AutoCompute.EFCore/Internal/EFCoreObservedNavigation.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.ChangeTracking;
6
using Microsoft.EntityFrameworkCore.Metadata;
7

8
namespace LLL.AutoCompute.EFCore.Internal;
9

10
public class EFCoreObservedNavigation(
78✔
11
    INavigationBase navigation)
78✔
12
    : EFCoreObservedMember, IObservedNavigation<EFCoreComputedInput>
13
{
14
    public override INavigationBase Property => navigation;
86✔
15
    public INavigationBase Navigation => navigation;
22,635✔
16

17
    public override string Name => Navigation.Name;
×
18
    public virtual IObservedEntityType SourceEntityType => Navigation.DeclaringEntityType.GetOrCreateObservedEntityType();
1,188✔
19
    public virtual IObservedEntityType TargetEntityType => Navigation.TargetEntityType.GetOrCreateObservedEntityType();
1,158✔
20
    public virtual bool IsCollection => Navigation.IsCollection;
190✔
21

22
    public override string ToDebugString()
23
    {
24
        return $"{Navigation.DeclaringEntityType.ShortName()}.{Navigation.Name}";
×
25
    }
26

27
    public IObservedNavigation GetInverse()
28
    {
29
        var inverse = Navigation.Inverse
1,402✔
30
            ?? throw new InvalidOperationException($"No inverse for navigation '{Navigation.DeclaringType.ShortName()}.{Navigation.Name}'");
1,402✔
31

32
        if (inverse.IsShadowProperty())
1,402✔
33
            throw new InvalidOperationException($"Inverse for navigation '{Navigation.DeclaringType.ShortName()}.{Navigation.Name}' cannot be a shadow property");
×
34

35
        return inverse.GetOrCreateObservedNavigation();
1,402✔
36
    }
37

38
    public virtual async Task<IReadOnlyDictionary<object, IReadOnlyCollection<object>>> LoadOriginalAsync(
39
        EFCoreComputedInput input,
40
        IReadOnlyCollection<object> sourceEntities)
41
    {
42
        await input.DbContext.BulkLoadAsync(sourceEntities, Navigation);
2,804✔
43

44
        var targetEntities = new Dictionary<object, IReadOnlyCollection<object>>();
2,804✔
45
        foreach (var sourceEntity in sourceEntities)
10,004✔
46
        {
47
            var entityEntry = input.DbContext.Entry(sourceEntity!);
2,198✔
48
            if (entityEntry.State == EntityState.Added)
2,198✔
49
                continue;
50

51
            var navigationEntry = entityEntry.Navigation(Navigation);
355✔
52

53
            if (!navigationEntry.IsLoaded && entityEntry.State != EntityState.Detached)
355✔
54
                await navigationEntry.LoadAsync();
6✔
55

56
            targetEntities.Add(sourceEntity, navigationEntry.GetOriginalEntities());
355✔
57
        }
58
        return targetEntities;
2,804✔
59
    }
60

61
    public async Task<IReadOnlyDictionary<object, IReadOnlyCollection<object>>> LoadCurrentAsync(
62
        EFCoreComputedInput input,
63
        IReadOnlyCollection<object> sourceEntities)
64
    {
65
        await input.DbContext.BulkLoadAsync(sourceEntities, Navigation);
2,804✔
66

67
        var targetEntities = new Dictionary<object, IReadOnlyCollection<object>>();
2,804✔
68
        foreach (var sourceEntity in sourceEntities)
10,004✔
69
        {
70
            var entityEntry = input.DbContext.Entry(sourceEntity!);
2,198✔
71
            var navigationEntry = entityEntry.Navigation(Navigation);
2,198✔
72

73
            if (!navigationEntry.IsLoaded && entityEntry.State != EntityState.Detached)
2,198✔
74
                await navigationEntry.LoadAsync();
4✔
75

76
            targetEntities.Add(sourceEntity, navigationEntry.GetCurrentEntities());
2,198✔
77
        }
78
        return targetEntities;
2,804✔
79
    }
80

81
    public override Expression CreateOriginalValueExpression(
82
        IObservedMemberAccess memberAccess,
83
        Expression inputExpression)
84
    {
85
        return Expression.Convert(
190✔
86
            Expression.Call(
190✔
87
                Expression.Constant(this),
190✔
88
                GetType().GetMethod(nameof(GetOriginalValue), BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy)!,
190✔
89
                inputExpression,
190✔
90
                memberAccess.FromExpression
190✔
91
            ),
190✔
92
            Navigation.ClrType
190✔
93
        );
190✔
94
    }
95

96
    public override Expression CreateCurrentValueExpression(
97
        IObservedMemberAccess memberAccess,
98
        Expression inputExpression)
99
    {
100
        return Expression.Convert(
190✔
101
            Expression.Call(
190✔
102
                Expression.Constant(this),
190✔
103
                GetType().GetMethod(nameof(GetCurrentValue), BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy)!,
190✔
104
                inputExpression,
190✔
105
                memberAccess.FromExpression
190✔
106
            ),
190✔
107
            Navigation.ClrType
190✔
108
        );
190✔
109
    }
110

111
    protected virtual object? GetOriginalValue(EFCoreComputedInput input, object ent)
112
    {
113
        var dbContext = input.DbContext;
260✔
114

115
        var entityEntry = dbContext.Entry(ent!);
260✔
116

117
        if (entityEntry.State == EntityState.Added)
260✔
UNCOV
118
            throw new Exception($"Cannot access navigation '{Navigation.DeclaringType.ShortName()}.{Navigation.Name}' original value for an added entity");
×
119

120
        if (input.IncrementalContext is not null)
260✔
121
        {
122
            var incrementalEntities = input.IncrementalContext.GetOriginalEntities(ent, this);
192✔
123

124
            if (Navigation.IsCollection)
192✔
125
            {
126
                var collectionAccessor = Navigation.GetCollectionAccessor()!;
182✔
127
                var collection = collectionAccessor.Create();
182✔
128
                foreach (var entity in incrementalEntities)
592✔
129
                    collectionAccessor.AddStandalone(collection, entity);
114✔
130
                return collection;
182✔
131
            }
132
            else
133
            {
134
                return incrementalEntities.FirstOrDefault();
10✔
135
            }
136
        }
137
        else
138
        {
139
            var navigationEntry = entityEntry.Navigation(Navigation);
68✔
140

141
            if (!navigationEntry.IsLoaded && entityEntry.State != EntityState.Detached)
68✔
142
                navigationEntry.Load();
4✔
143

144
            return navigationEntry.GetOriginalValue();
68✔
145
        }
146
    }
147

148
    protected virtual object? GetCurrentValue(EFCoreComputedInput input, object ent)
149
    {
150
        var dbContext = input.DbContext;
1,380✔
151

152
        var entityEntry = dbContext.Entry(ent!);
1,380✔
153

154
        if (entityEntry.State == EntityState.Deleted)
1,380✔
UNCOV
155
            throw new Exception($"Cannot access navigation '{Navigation.DeclaringType.ShortName()}.{Navigation.Name}' current value for a deleted entity");
×
156

157
        if (input.IncrementalContext is not null)
1,380✔
158
        {
159
            var incrementalEntities = input.IncrementalContext.GetCurrentEntities(ent, this);
248✔
160

161
            if (Navigation.IsCollection)
248✔
162
            {
163
                var collectionAccessor = Navigation.GetCollectionAccessor()!;
240✔
164
                var collection = collectionAccessor.Create();
240✔
165
                foreach (var entity in incrementalEntities)
848✔
166
                    collectionAccessor.AddStandalone(collection, entity);
184✔
167
                return collection;
240✔
168
            }
169
            else
170
            {
171
                return incrementalEntities.FirstOrDefault();
8✔
172
            }
173
        }
174
        else
175
        {
176
            var navigationEntry = entityEntry.Navigation(Navigation);
1,132✔
177
            if (!navigationEntry.IsLoaded && entityEntry.State != EntityState.Detached)
1,132✔
178
                navigationEntry.Load();
2✔
179

180
            return navigationEntry.GetCurrentValue();
1,132✔
181
        }
182
    }
183

184
    public override async Task CollectChangesAsync(DbContext dbContext, EFCoreChangeset changes)
185
    {
186
        foreach (var entityEntry in dbContext.EntityEntriesOfType(Navigation.DeclaringEntityType))
2,408✔
187
        {
188
            await CollectChangesAsync(entityEntry, changes);
728✔
189
        }
190
        if (Navigation.Inverse is not null)
476✔
191
        {
192
            foreach (var entityEntry in dbContext.EntityEntriesOfType(Navigation.Inverse.DeclaringEntityType))
2,056✔
193
            {
194
                await CollectChangesAsync(entityEntry, changes);
558✔
195
            }
196
        }
197
        if (Navigation is ISkipNavigation skipNavigation)
476✔
198
        {
199
            foreach (var joinEntry in dbContext.EntityEntriesOfType(skipNavigation.JoinEntityType))
176✔
200
            {
201
                await CollectChangesAsync(joinEntry, changes);
40✔
202
            }
203
        }
204
    }
205

206
    public override async Task CollectChangesAsync(EntityEntry entityEntry, EFCoreChangeset changes)
207
    {
208
        if (entityEntry.State != EntityState.Added
1,334✔
209
            && entityEntry.State != EntityState.Deleted
1,334✔
210
            && entityEntry.State != EntityState.Modified)
1,334✔
211
        {
212
            return;
462✔
213
        }
214

215
        if (Navigation.DeclaringEntityType.IsAssignableFrom(entityEntry.Metadata))
872✔
216
        {
217
            var navigationEntry = entityEntry.Navigation(Navigation);
480✔
218
            if (entityEntry.State == EntityState.Added
480✔
219
                || entityEntry.State == EntityState.Deleted
480✔
220
                || navigationEntry.IsModified)
480✔
221
            {
222
                var modifiedEntities = navigationEntry.GetModifiedEntities();
466✔
223

224
                foreach (var ent in modifiedEntities.added)
1,408✔
225
                    changes.RegisterNavigationAdded(Navigation, entityEntry.Entity, ent);
238✔
226

227
                foreach (var ent in modifiedEntities.removed)
948✔
228
                    changes.RegisterNavigationRemoved(Navigation, entityEntry.Entity, ent);
8✔
229
            }
230
        }
231

232
        if (Navigation.Inverse is not null && Navigation.Inverse.DeclaringEntityType.IsAssignableFrom(entityEntry.Metadata))
872✔
233
        {
234
            if (entityEntry.State == EntityState.Added
396✔
235
                                || entityEntry.State == EntityState.Deleted
396✔
236
                                || entityEntry.State == EntityState.Modified)
396✔
237
            {
238
                var inverseNavigationEntry = entityEntry.Navigation(Navigation.Inverse);
396✔
239
                if (entityEntry.State == EntityState.Added
396✔
240
                    || entityEntry.State == EntityState.Deleted
396✔
241
                    || inverseNavigationEntry.IsModified)
396✔
242
                {
243
                    if (!inverseNavigationEntry.IsLoaded && entityEntry.State != EntityState.Detached)
360✔
244
                        await inverseNavigationEntry.LoadAsync();
64✔
245

246
                    var modifiedEntities = inverseNavigationEntry.GetModifiedEntities();
360✔
247

248
                    foreach (var entity in modifiedEntities.added)
1,292✔
249
                        changes.RegisterNavigationAdded(Navigation, entity, entityEntry.Entity);
286✔
250

251
                    foreach (var entity in modifiedEntities.removed)
816✔
252
                        changes.RegisterNavigationRemoved(Navigation, entity, entityEntry.Entity);
48✔
253
                }
254
            }
255
        }
256

257
        if (Navigation is ISkipNavigation skipNavigation && skipNavigation.JoinEntityType.IsAssignableFrom(entityEntry.Metadata))
872✔
258
        {
259
            var dependentToPrincipal = skipNavigation.ForeignKey.DependentToPrincipal!;
32✔
260
            var joinReferenceToOther = skipNavigation.Inverse.ForeignKey.DependentToPrincipal;
32✔
261
            var dependentToPrincipalEntry = entityEntry.Navigation(dependentToPrincipal);
32✔
262
            var otherReferenceEntry = entityEntry.Reference(joinReferenceToOther!);
32✔
263

264
            if (entityEntry.State == EntityState.Added
32✔
265
                || entityEntry.State == EntityState.Deleted
32✔
266
                || dependentToPrincipalEntry.IsModified)
32✔
267
            {
268
                if (!dependentToPrincipalEntry.IsLoaded && entityEntry.State != EntityState.Detached)
32✔
UNCOV
269
                    await dependentToPrincipalEntry.LoadAsync();
×
270

271
                if (entityEntry.State == EntityState.Added
32✔
272
                    || dependentToPrincipalEntry.IsModified)
32✔
273
                {
274
                    foreach (var entity in dependentToPrincipalEntry.GetCurrentEntities())
48✔
275
                    {
276
                        foreach (var otherEntity in otherReferenceEntry.GetCurrentEntities())
48✔
277
                            changes.RegisterNavigationAdded(Navigation, entity, otherEntity);
12✔
278
                    }
279
                }
280

281
                if (entityEntry.State == EntityState.Deleted
32✔
282
                    || dependentToPrincipalEntry.IsModified)
32✔
283
                {
284
                    foreach (var entity in dependentToPrincipalEntry.GetOriginalEntities())
80✔
285
                    {
286
                        foreach (var otherEntity in otherReferenceEntry.GetOriginalEntities())
80✔
287
                            changes.RegisterNavigationRemoved(Navigation, entity, otherEntity);
20✔
288
                    }
289
                }
290
            }
291
        }
292
    }
293

294
    public async Task<ObservedNavigationChanges> GetChangesAsync(EFCoreComputedInput input)
295
    {
296
        return input.ChangesToProcess.GetOrCreateNavigationChanges(Navigation);
1,232✔
297
    }
298
}
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