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

lucaslorentz / auto-compute / 20057011414

09 Dec 2025 08:21AM UTC coverage: 81.626% (-1.1%) from 82.723%
20057011414

push

github

lucaslorentz
Use EFCoreChangeset to calculate original member values

212 of 275 new or added lines in 14 files covered. (77.09%)

1 existing line in 1 file now uncovered.

1888 of 2313 relevant lines covered (81.63%)

963.86 hits per line

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

56.93
/src/LLL.AutoCompute.EFCore/Metadata/Internal/EFCoreChangeset.cs
1
using System.Collections.Concurrent;
2
using System.Collections.Frozen;
3
using LLL.AutoCompute.EFCore.Metadata.Internal;
4
using Microsoft.EntityFrameworkCore.Metadata;
5

6
namespace LLL.AutoCompute.EFCore;
7

8
public class EFCoreChangeset
9
{
10
    public int Count { get; private set; } = 0;
18,302✔
11
    private readonly ConcurrentDictionary<IProperty, ConcurrentDictionary<object, ObservedPropertyChange>> _propertyChanges = [];
2,854✔
12
    private readonly ConcurrentDictionary<INavigationBase, ConcurrentDictionary<object, ObservedNavigationChange>> _navigationChanges = [];
2,854✔
13
    private readonly ConcurrentDictionary<IEntityType, ObservedEntityTypeChange> _entityTypeChanges = [];
2,854✔
14

15
    public void RegisterEntityAdded(IEntityType entityType, object entity)
16
    {
17
        var entityChange = _entityTypeChanges.GetOrAdd(entityType, static k => new ObservedEntityTypeChange());
1,238✔
18
        // TODO: Change count
19
        entityChange.RegisterAdded(entity);
732✔
20
    }
21

22
    public void RegisterEntityRemoved(IEntityType entityType, object entity)
23
    {
24
        var entityChange = _entityTypeChanges.GetOrAdd(entityType, static k => new ObservedEntityTypeChange());
12✔
25
        // TODO: Change count
26
        entityChange.RegisterRemoved(entity);
6✔
27
    }
28

29
    public void RegisterPropertyChange(IProperty property, object entity, object? originalValue, object? currentValue)
30
    {
31
        var propertyChanges = _propertyChanges.GetOrAdd(property, static k => []);
12,264✔
32

33
        if (!propertyChanges.TryGetValue(entity, out var change))
7,560✔
34
        {
35
            Count++;
7,190✔
36
            change = new ObservedPropertyChange(entity, originalValue, currentValue);
7,190✔
37
            propertyChanges[entity] = change;
7,190✔
38
        }
39
        else
40
        {
41
            change.CurrentValue = currentValue;
370✔
42
        }
43

44
        MaybeCleanupPropertyChange(property, propertyChanges, change);
7,560✔
45
    }
46

47
    private void MaybeCleanupPropertyChange(
48
        IProperty property,
49
        ConcurrentDictionary<object, ObservedPropertyChange> propertyChanges,
50
        ObservedPropertyChange change)
51
    {
52
        var valueComparer = property.GetValueComparer();
7,560✔
53
        if (valueComparer.Equals(change.OriginalValue, change.CurrentValue))
7,560✔
54
        {
55
            Count--;
386✔
56
            propertyChanges.TryRemove(change.Entity, out _);
386✔
57
        }
58
    }
59

60
    public void RegisterNavigationAdded(INavigationBase navigation, object entity, object relatedEntity)
61
    {
62
        var navigationChanges = _navigationChanges.GetOrAdd(navigation, static k => []);
984✔
63
        if (!navigationChanges.TryGetValue(entity, out var navigationChange))
634✔
64
        {
65
            Count++;
396✔
66
            navigationChange = new ObservedNavigationChange(entity);
396✔
67
            navigationChanges[entity] = navigationChange;
396✔
68
        }
69
        navigationChange.RegisterAdded(relatedEntity);
634✔
70
        MaybeCleanupNavigationChange(navigation, navigationChanges, navigationChange);
634✔
71
    }
72

73
    public void RegisterNavigationRemoved(INavigationBase navigation, object entity, object relatedEntity)
74
    {
75
        var navigationChanges = _navigationChanges.GetOrAdd(navigation, static k => []);
154✔
76
        if (!navigationChanges.TryGetValue(entity, out var navigationChange))
80✔
77
        {
78
            Count++;
78✔
79
            navigationChange = new ObservedNavigationChange(entity);
78✔
80
            navigationChanges[entity] = navigationChange;
78✔
81
        }
82
        navigationChange.RegisterRemoved(relatedEntity);
80✔
83
        MaybeCleanupNavigationChange(navigation, navigationChanges, navigationChange);
80✔
84
    }
85

86
    private void MaybeCleanupNavigationChange(
87
        INavigationBase navigation,
88
        ConcurrentDictionary<object, ObservedNavigationChange> navigationChanges,
89
        ObservedNavigationChange navigationChange)
90
    {
91
        if (navigationChange.IsEmpty)
714✔
92
        {
93
            Count--;
4✔
94
            navigationChanges.TryRemove(navigationChange.Entity, out _);
4✔
95
        }
96
    }
97

98
    public IReadOnlyList<ObservedPropertyChange> GetChanges(IProperty property)
99
    {
100
        if (!_propertyChanges.TryGetValue(property, out var changes))
2,630✔
101
            return [];
462✔
102

103
        return changes.Values.ToArray();
2,168✔
104
    }
105

106
    public IReadOnlyList<ObservedNavigationChange> GetChanges(INavigationBase navigation)
107
    {
108
        if (!_navigationChanges.TryGetValue(navigation, out var changes))
1,238✔
109
            return [];
212✔
110

111
        return changes.Values.ToArray();
1,026✔
112
    }
113

114
    public ObservedEntityTypeChange? GetChanges(IEntityType entityType)
115
    {
116
        if (!_entityTypeChanges.TryGetValue(entityType, out var changes))
15,728✔
117
            return null;
2,496✔
118

119
        return changes;
13,232✔
120
    }
121

122
    public ObservedPropertyChange? GetChange(IProperty property, object entity)
123
    {
124
        if (!_propertyChanges.TryGetValue(property, out var changes))
1,782✔
125
            return null;
1,340✔
126

127
        if (!changes.TryGetValue(entity, out var change))
442✔
128
            return null;
404✔
129

130
        return change;
38✔
131
    }
132

133
    public ObservedNavigationChange? GetChange(INavigationBase navigation, object entity)
134
    {
135
        if (!_navigationChanges.TryGetValue(navigation, out var changes))
106✔
136
            return null;
28✔
137

138
        if (!changes.TryGetValue(entity, out var change))
78✔
139
            return null;
12✔
140

141
        return change;
66✔
142
    }
143

144
    public void MergeInto(EFCoreChangeset target, bool detectCycles)
145
    {
146
        foreach (var (member, entities) in _propertyChanges)
10,192✔
147
        {
148
            foreach (var (entity, change) in entities)
11,744✔
149
            {
150
                if (detectCycles && target.GetChange(member, entity) is not null)
3,324✔
NEW
151
                    throw new Exception($"Cyclic update detected for member: {member.DeclaringType.Name}.{member.Name}");
×
152

153
                target.RegisterPropertyChange(member, entity, change.OriginalValue, change.CurrentValue);
3,324✔
154
            }
155
        }
156

157
        foreach (var (navigation, observedNavigationChanges) in _navigationChanges)
5,216✔
158
        {
159
            foreach (var (entity, entityChanges) in observedNavigationChanges)
264✔
160
            {
161
                if (detectCycles && target.GetChange(navigation, entity) is not null)
72✔
NEW
162
                    throw new Exception($"Cyclic update detected for member: {navigation.DeclaringType.Name}.{navigation.Name}");
×
163

164
                foreach (var added in entityChanges.Added)
288✔
165
                    target.RegisterNavigationAdded(navigation, entity, added);
72✔
166

167
                foreach (var removed in entityChanges.Removed)
144✔
NEW
168
                    target.RegisterNavigationAdded(navigation, entity, removed);
×
169
            }
170
        }
171

172
        foreach (var (entityType, entityChanges) in _entityTypeChanges)
5,136✔
173
        {
174
            foreach (var added in entityChanges.Added)
88✔
175
                target.RegisterEntityAdded(entityType, added);
24✔
176

177
            foreach (var removed in entityChanges.Removed)
40✔
NEW
178
                target.RegisterEntityRemoved(entityType, removed);
×
179
        }
180
    }
181

182
    public EFCoreChangeset DeltaFrom(EFCoreChangeset previous)
183
    {
NEW
184
        var delta = new EFCoreChangeset();
×
185

NEW
186
        var properties = _propertyChanges.Keys.Concat(previous._propertyChanges.Keys).Distinct();
×
NEW
187
        foreach (var property in properties)
×
188
        {
NEW
189
            var comparer = property.GetValueComparer();
×
190

NEW
191
            var currentEntities = _propertyChanges.ContainsKey(property)
×
NEW
192
                ? _propertyChanges[property].Keys
×
NEW
193
                : [];
×
194

NEW
195
            var previousEntities = previous._propertyChanges.ContainsKey(property)
×
NEW
196
                ? previous._propertyChanges[property].Keys
×
NEW
197
                : [];
×
198

NEW
199
            var entities = currentEntities.Concat(previousEntities).Distinct();
×
200

NEW
201
            foreach (var entity in entities)
×
202
            {
NEW
203
                var previousChange = previous.GetChange(property, entity);
×
NEW
204
                var change = GetChange(property, entity);
×
NEW
205
                var previousValue = previousChange?.CurrentValue;
×
NEW
206
                var currentValue = change?.CurrentValue;
×
NEW
207
                if (!comparer.Equals(previousValue, currentValue))
×
NEW
208
                    delta.RegisterPropertyChange(property, entity, previousValue, currentValue);
×
209
            }
210
        }
211

NEW
212
        var navigations = _navigationChanges.Keys.Concat(previous._navigationChanges.Keys).Distinct();
×
NEW
213
        foreach (var navigation in navigations)
×
214
        {
NEW
215
            var currentEntities = _navigationChanges.ContainsKey(navigation)
×
NEW
216
                ? _navigationChanges[navigation].Keys
×
NEW
217
                : [];
×
218

NEW
219
            var previousEntities = previous._navigationChanges.ContainsKey(navigation)
×
NEW
220
                ? previous._navigationChanges[navigation].Keys
×
NEW
221
                : [];
×
222

NEW
223
            var entities = currentEntities.Concat(previousEntities).Distinct();
×
224

NEW
225
            foreach (var entity in entities)
×
226
            {
NEW
227
                var previousChange = previous.GetChange(navigation, entity);
×
NEW
228
                var change = GetChange(navigation, entity);
×
229

NEW
230
                var deltaAdded = (change?.Added ?? FrozenSet<object>.Empty).Except(previousChange?.Added ?? FrozenSet<object>.Empty)
×
NEW
231
                        .Concat((previousChange?.Removed ?? FrozenSet<object>.Empty).Except(change?.Removed ?? FrozenSet<object>.Empty))
×
NEW
232
                        .ToHashSet();
×
233

NEW
234
                foreach (var added in deltaAdded)
×
NEW
235
                    delta.RegisterNavigationAdded(navigation, entity, added);
×
236

NEW
237
                var deltaRemoved = (change?.Removed ?? FrozenSet<object>.Empty).Except(previousChange?.Removed ?? FrozenSet<object>.Empty)
×
NEW
238
                        .Concat((previousChange?.Added ?? FrozenSet<object>.Empty).Except(change?.Added ?? FrozenSet<object>.Empty))
×
NEW
239
                        .ToHashSet();
×
240

NEW
241
                foreach (var removed in deltaRemoved)
×
NEW
242
                    delta.RegisterNavigationRemoved(navigation, entity, removed);
×
243
            }
244
        }
245

NEW
246
        var entityTypes = _entityTypeChanges.Keys.Concat(previous._entityTypeChanges.Keys).Distinct();
×
NEW
247
        foreach (var entityType in entityTypes)
×
248
        {
NEW
249
            var previousChange = previous.GetChanges(entityType);
×
NEW
250
            var change = GetChanges(entityType);
×
251

NEW
252
            var deltaAdded = (change?.Added ?? FrozenSet<object>.Empty).Except(previousChange?.Added ?? FrozenSet<object>.Empty)
×
NEW
253
                .Concat((previousChange?.Removed ?? FrozenSet<object>.Empty).Except(change?.Removed ?? FrozenSet<object>.Empty))
×
NEW
254
                .ToHashSet();
×
255

NEW
256
            foreach (var added in deltaAdded)
×
NEW
257
                delta.RegisterEntityAdded(entityType, added);
×
258

NEW
259
            var deltaRemoved = (change?.Removed ?? FrozenSet<object>.Empty).Except(previousChange?.Removed ?? FrozenSet<object>.Empty)
×
NEW
260
                .Concat((previousChange?.Added ?? FrozenSet<object>.Empty).Except(change?.Added ?? FrozenSet<object>.Empty))
×
NEW
261
                .ToHashSet();
×
262

NEW
263
            foreach (var removed in deltaRemoved)
×
NEW
264
                delta.RegisterEntityRemoved(entityType, removed);
×
265
        }
266

NEW
267
        return delta;
×
268
    }
269

270
    public IReadOnlySet<ComputedMember> GetAffectedComputedMembers(IReadOnlySet<ComputedMember> targetComputeds)
271
    {
272
        var affectedComputedMembers = new HashSet<ComputedMember>();
1,274✔
273

274
        var affectedMembers = _propertyChanges.Keys.OfType<IPropertyBase>()
1,274✔
275
            .Concat(_navigationChanges.Keys);
1,274✔
276

277
        foreach (var member in affectedMembers)
5,156✔
278
        {
279
            var observedMember = member.GetObservedMember();
1,304✔
280
            if (observedMember is null)
1,304✔
281
                continue;
282

283
            affectedComputedMembers.UnionWith(observedMember.DependentMembers);
672✔
284
        }
285

286
        affectedComputedMembers.IntersectWith(targetComputeds);
1,274✔
287

288
        return affectedComputedMembers;
1,274✔
289
    }
290
}
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