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

lucaslorentz / auto-compute / 19404221921

16 Nov 2025 10:27AM UTC coverage: 82.741% (-0.2%) from 82.971%
19404221921

push

github

lucaslorentz
Avoid direct ToDictionaryAsync in Queryables

0 of 8 new or added lines in 1 file covered. (0.0%)

1793 of 2167 relevant lines covered (82.74%)

876.94 hits per line

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

51.61
/src/LLL.AutoCompute.EFCore/Metadata/Internal/ComputedMember.cs
1
using System.Collections;
2
using System.Collections.Immutable;
3
using System.Linq.Expressions;
4
using System.Reflection;
5
using LLL.AutoCompute.EFCore.Internal;
6
using LLL.AutoCompute.Internal.ExpressionVisitors;
7
using Microsoft.EntityFrameworkCore;
8
using Microsoft.EntityFrameworkCore.ChangeTracking;
9
using Microsoft.EntityFrameworkCore.Metadata;
10

11
namespace LLL.AutoCompute.EFCore.Metadata.Internal;
12

13
public abstract class ComputedMember(
14
    IComputedChangesProvider changesProvider)
15
    : ComputedBase(changesProvider)
156✔
16
{
17
    public IEntityType EntityType => Property.DeclaringType.ContainingEntityType;
×
18
    public abstract IPropertyBase Property { get; }
19
    public abstract Task<ComputedMemberConsistency> CheckConsistencyAsync(DbContext dbContext, DateTime since);
20
    public abstract IQueryable QueryInconsistentEntities(DbContext dbContext, DateTime since);
21
    public abstract LambdaExpression GetIsMemberInconsistentLambda(DbContext dbContext);
22
    public abstract LambdaExpression GetIsMemberConsistentLambda(DbContext dbContext);
23
    public abstract Task FixAsync(object entity, DbContext dbContext);
24

25
    public override string ToDebugString()
26
    {
27
        return $"{Property.DeclaringType.Name}.{Property.Name}";
×
28
    }
29

30
    public abstract Task<EFCoreChangeset> Update(IEFCoreComputedInput input);
31

32
    protected static void MaybeUpdateProperty(PropertyEntry propertyEntry, object? newValue, EFCoreChangeset? updateChanges)
33
    {
34
        var valueComparer = propertyEntry.Metadata.GetValueComparer();
2,426✔
35
        if (valueComparer.Equals(propertyEntry.CurrentValue, newValue))
2,426✔
36
            return;
776✔
37

38
        updateChanges?.AddPropertyChange(propertyEntry.Metadata, propertyEntry.EntityEntry.Entity);
1,650✔
39

40
        propertyEntry.CurrentValue = newValue;
1,650✔
41
    }
42

43
    protected static async Task MaybeUpdateNavigation(
44
        NavigationEntry navigationEntry,
45
        object? newValue,
46
        EFCoreChangeset? updateChanges,
47
        IReadOnlySet<IPropertyBase> updateMembers,
48
        Delegate? reuseKeySelector)
49
    {
50
        var navigation = navigationEntry.Metadata;
14✔
51
        var dbContext = navigationEntry.EntityEntry.Context;
14✔
52
        var entity = navigationEntry.EntityEntry.Entity;
14✔
53
        var itemsToRemove = navigationEntry.GetOriginalEntities().ToHashSet();
14✔
54
        var itemsToAdd = new HashSet<object>();
14✔
55
        var newItems = navigation.IsCollection
14✔
56
            ? (newValue is IEnumerable enumerable ? enumerable : Array.Empty<object>())
14✔
57
            : (newValue is not null ? [newValue] : Array.Empty<object>());
14✔
58

59
        foreach (var newItem in newItems)
60✔
60
        {
61
            var existingItem = reuseKeySelector is not null
16✔
62
                ? FindEntityToReuse(itemsToRemove, newItem, reuseKeySelector)
16✔
63
                : null;
16✔
64

65
            if (existingItem is null)
16✔
66
            {
67
                if (dbContext.Entry(newItem).State == EntityState.Detached)
12✔
68
                    dbContext.Add(newItem);
8✔
69

70
                itemsToAdd.Add(newItem);
12✔
71

72
                if (updateChanges is not null)
12✔
73
                {
74
                    var entry = dbContext.Entry(newItem);
12✔
75
                    foreach (var member in updateMembers)
56✔
76
                    {
77
                        var observedMember = member.GetObservedMember();
16✔
78
                        if (observedMember is null)
16✔
79
                            continue;
80

81
                        await observedMember.CollectChangesAsync(entry, updateChanges);
16✔
82
                    }
83
                }
84
            }
85
            else
86
            {
87
                itemsToRemove.Remove(existingItem);
4✔
88

89
                foreach (var memberToUpdate in updateMembers)
24✔
90
                {
91
                    var existingEntityEntry = dbContext.Entry(existingItem);
8✔
92
                    var existingMemberEntry = existingEntityEntry.Member(memberToUpdate);
8✔
93
                    var newMemberValue = memberToUpdate.GetGetter().GetClrValueUsingContainingEntity(newItem);
8✔
94
                    switch (existingMemberEntry)
95
                    {
96
                        case PropertyEntry existingPropertyEntry:
97
                            MaybeUpdateProperty(
4✔
98
                                existingPropertyEntry,
4✔
99
                                newMemberValue,
4✔
100
                                updateChanges);
4✔
101
                            break;
4✔
102
                        case NavigationEntry existingNavigationEntry:
103
                            await MaybeUpdateNavigation(
4✔
104
                                existingNavigationEntry,
4✔
105
                                newMemberValue,
4✔
106
                                updateChanges,
4✔
107
                                ImmutableHashSet<IPropertyBase>.Empty,
4✔
108
                                null);
4✔
109
                            break;
4✔
110
                        default:
111
                            throw new NotSupportedException($"Controlled member {memberToUpdate} is not supported");
×
112
                    }
113
                }
114
            }
115
        }
116

117
        var collectionAccessor = navigation.GetCollectionAccessor();
14✔
118
        foreach (var entityToRemove in itemsToRemove)
36✔
119
        {
120
            if (collectionAccessor is not null)
4✔
121
                collectionAccessor.Remove(entity, entityToRemove);
×
122
            else
123
                navigationEntry.CurrentValue = null;
4✔
124

125
            if (updateChanges is not null)
4✔
126
            {
127
                updateChanges.RegisterNavigationRemoved(navigation, entity, entityToRemove);
4✔
128

129
                if (navigation.Inverse is not null)
4✔
130
                    updateChanges.RegisterNavigationRemoved(navigation.Inverse, entityToRemove, entity);
×
131
            }
132
        }
133

134
        foreach (var entityToAdd in itemsToAdd)
52✔
135
        {
136
            if (collectionAccessor is not null)
12✔
137
                collectionAccessor.Add(entity, entityToAdd, false);
8✔
138
            else
139
                navigationEntry.CurrentValue = entityToAdd;
4✔
140

141
            if (updateChanges is not null)
12✔
142
            {
143
                updateChanges.RegisterNavigationAdded(navigation, entity, entityToAdd);
12✔
144

145
                if (navigation.Inverse is not null)
12✔
146
                    updateChanges.RegisterNavigationAdded(navigation.Inverse, entityToAdd, entity);
8✔
147
            }
148
        }
149
    }
150

151
    private static object? FindEntityToReuse(
152
        IEnumerable<object> availableEntities,
153
        object newEntity,
154
        Delegate reuseKeySelector)
155
    {
156
        if (reuseKeySelector is null)
12✔
157
            return null;
×
158

159
        var reuseKey = reuseKeySelector.DynamicInvoke(newEntity);
12✔
160
        return availableEntities.FirstOrDefault(x => Equals(reuseKeySelector.DynamicInvoke(x), reuseKey));
16✔
161
    }
162
}
163

164
public abstract class ComputedMember<TEntity, TMember>(
165
    IComputedChangesProvider changesProvider)
166
    : ComputedMember(changesProvider)
156✔
167
    where TEntity : class
168
{
169
    public override async Task<ComputedMemberConsistency> CheckConsistencyAsync(DbContext dbContext, DateTime since)
170
    {
NEW
171
        var result = (await dbContext.CreateConsistencyQuery<TEntity>(EntityType, since)
×
172
            .GroupBy(GetIsMemberConsistentLambda(dbContext))
×
NEW
173
            .Select(x => new
×
NEW
174
            {
×
NEW
175
                x.Key,
×
NEW
176
                Count = x.Count()
×
NEW
177
            })
×
NEW
178
            .ToArrayAsync())
×
NEW
179
            .ToDictionary(x => x.Key, x => x.Count);
×
180

181
        if (!result.TryGetValue(false, out var inconsistent))
×
182
            inconsistent = 0;
×
183

184
        if (!result.TryGetValue(true, out var consistent))
×
185
            consistent = 0;
×
186

187
        return new ComputedMemberConsistency(consistent, inconsistent);
×
188
    }
189

190
    public override IQueryable<TEntity> QueryInconsistentEntities(DbContext dbContext, DateTime since)
191
    {
192
        return dbContext.CreateConsistencyQuery<TEntity>(EntityType, since)
×
193
            .Where(GetIsMemberInconsistentLambda(dbContext));
×
194
    }
195

196
    public override Expression<Func<TEntity, bool>> GetIsMemberInconsistentLambda(DbContext dbContext)
197
    {
198
        var isMemberConsistentLambda = GetIsMemberConsistentLambda(dbContext);
×
199
        return (Expression<Func<TEntity, bool>>)Expression.Lambda(
×
200
            Expression.Not(isMemberConsistentLambda.Body),
×
201
            isMemberConsistentLambda.Parameters
×
202
        );
×
203
    }
204

205
    public override Expression<Func<TEntity, bool>> GetIsMemberConsistentLambda(DbContext dbContext)
206
    {
207
        var consistencyCheck = Property.GetConsistencyCheck();
×
208
        if (consistencyCheck is not null)
×
209
        {
210
            var analyzer = dbContext.Model.GetComputedExpressionAnalyzerOrThrow();
×
211
            return (Expression<Func<TEntity, bool>>)analyzer.RunExpressionModifiers(consistencyCheck);
×
212
        }
213

214
        var entParameter = ChangesProvider.Expression.Parameters.First();
×
215

216
        var computedValue = new RemoveChangeComputedTrackingVisitor().Visit(
×
217
            ChangesProvider.Expression.Body
×
218
        );
×
219

220
        var storedValue = CreateEFPropertyExpression(entParameter, Property);
×
221

222
        return Expression.Lambda<Func<TEntity, bool>>(
×
223
            CreateIsValueConsistentExpression(computedValue, storedValue),
×
224
            entParameter
×
225
        );
×
226
    }
227

228
    protected abstract Expression CreateIsValueConsistentExpression(Expression computedValue, Expression storedValue);
229

230
    private static readonly MethodInfo _efPropertyMethod = ((Func<object, string, object>)EF.Property<object>)
×
231
        .Method.GetGenericMethodDefinition();
×
232

233
    protected static Expression CreateEFPropertyExpression(
234
        Expression expression,
235
        IPropertyBase property)
236
    {
237
        if (property.PropertyInfo is not null)
×
238
        {
239
            return Expression.Property(
×
240
                expression,
×
241
                property.PropertyInfo
×
242
            );
×
243
        }
244
        else if (property.FieldInfo is not null)
×
245
        {
246
            return Expression.Field(
×
247
                expression,
×
248
                property.FieldInfo
×
249
            );
×
250
        }
251
        else if (property.IsShadowProperty())
×
252
        {
253
            return Expression.Call(
×
254
                null,
×
255
                _efPropertyMethod.MakeGenericMethod(property.ClrType),
×
256
                expression,
×
257
                Expression.Constant(property.Name)
×
258
            );
×
259
        }
260
        else
261
        {
262
            throw new Exception("Unsupported property access");
×
263
        }
264
    }
265
}
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