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

lucaslorentz / auto-compute / 19292295081

12 Nov 2025 09:15AM UTC coverage: 83.163% (-0.3%) from 83.434%
19292295081

push

github

lucaslorentz
Allow custom consistency check for properties

0 of 23 new or added lines in 5 files covered. (0.0%)

2 existing lines in 1 file now uncovered.

1793 of 2156 relevant lines covered (83.16%)

881.85 hits per line

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

14.29
/src/LLL.AutoCompute.EFCore/Metadata/Internal/ComputedNavigation.cs
1
using System.Linq.Expressions;
2
using LLL.AutoCompute.EFCore.Internal;
3
using Microsoft.EntityFrameworkCore;
4
using Microsoft.EntityFrameworkCore.ChangeTracking;
5
using Microsoft.EntityFrameworkCore.Metadata;
6

7
namespace LLL.AutoCompute.EFCore.Metadata.Internal;
8

9
public class ComputedNavigation<TEntity, TProperty>(
10
    INavigationBase navigationBase,
11
    IComputedChangesProvider<IEFCoreComputedInput, TEntity, TProperty> changesProvider,
12
    IReadOnlySet<IPropertyBase> controlledMembers
13
) : ComputedMember<TEntity, TProperty>(changesProvider), IComputedNavigationBuilder<TEntity, TProperty>
2✔
14
    where TEntity : class
15
{
16
    private readonly Func<TEntity, TProperty> _compiledExpression = ((Expression<Func<TEntity, TProperty>>)changesProvider.Expression).Compile();
2✔
17

18
    public new IComputedChangesProvider<IEFCoreComputedInput, TEntity, TProperty> ChangesProvider => changesProvider;
10✔
19
    public override INavigationBase Property => navigationBase;
2✔
20
    public IReadOnlySet<IPropertyBase> ControlledMembers => controlledMembers;
10✔
21
    public Delegate? ReuseKeySelector { get; set; }
12✔
22

23
    public override async Task<EFCoreChangeset> Update(IEFCoreComputedInput input)
24
    {
25
        var updateChanges = new EFCoreChangeset();
14✔
26
        var changes = await changesProvider.GetChangesAsync(input, null);
14✔
27
        foreach (var (entity, change) in changes)
48✔
28
        {
29
            var entityEntry = input.DbContext.Entry(entity);
10✔
30
            var navigationEntry = entityEntry.Navigation(navigationBase);
10✔
31

32
            var originalValue = GetOriginalValue(navigationEntry);
10✔
33

34
            var newValue = ChangesProvider.ChangeCalculation.ApplyChange(
10✔
35
                originalValue,
10✔
36
                change);
10✔
37

38
            if (!navigationEntry.IsLoaded && entityEntry.State != EntityState.Detached)
10✔
39
                await navigationEntry.LoadAsync();
×
40

41
            await MaybeUpdateNavigation(navigationEntry, newValue, updateChanges, ControlledMembers, ReuseKeySelector);
10✔
42
        }
43
        return updateChanges;
14✔
44
    }
45

46
    public override async Task FixAsync(object entity, DbContext dbContext)
47
    {
48
        var entityEntry = dbContext.Entry(entity);
×
49
        var navigationEntry = entityEntry.Navigation(navigationBase);
×
50

51
        if (!navigationEntry.IsLoaded && entityEntry.State != EntityState.Detached)
×
52
            await navigationEntry.LoadAsync();
×
53

54
        var newValue = _compiledExpression((TEntity)entity);
×
55

56
        await MaybeUpdateNavigation(navigationEntry, newValue, null, ControlledMembers, ReuseKeySelector);
×
57
    }
58

59
    private static TProperty GetOriginalValue(NavigationEntry navigationEntry)
60
    {
61
        if (navigationEntry.EntityEntry.State == EntityState.Added)
10✔
62
            return default!;
6✔
63

64
        return (TProperty)navigationEntry.GetOriginalValue()!;
4✔
65
    }
66

67
    protected override Expression CreateIsValueConsistentExpression(
68
        Expression computedValue,
69
        Expression storedValue)
70
    {
71
        if (navigationBase.IsCollection)
×
72
        {
73
            var body = ChangeExpressionConditionals(
×
74
                computedValue,
×
75
                computedValue =>
×
76
                {
×
77
                    // Unwrap unnecessary stuff
×
78
                    while (true)
×
79
                    {
×
80
                        if (computedValue is UnaryExpression unaryExpression
×
81
                            && unaryExpression.NodeType == ExpressionType.Convert)
×
82
                        {
×
83
                            computedValue = unaryExpression.Operand;
×
84
                        }
×
85
                        else if (computedValue is MethodCallExpression methodCallExpression
×
86
                            && methodCallExpression.Method.DeclaringType == typeof(Enumerable)
×
87
                            && (
×
88
                                methodCallExpression.Method.Name == nameof(Enumerable.ToList)
×
89
                                || methodCallExpression.Method.Name == nameof(Enumerable.ToArray)
×
90
                                || methodCallExpression.Method.Name == nameof(Enumerable.AsEnumerable)))
×
91
                        {
×
92
                            computedValue = methodCallExpression.Arguments[0];
×
93
                        }
×
94
                        else
×
95
                        {
×
96
                            break;
×
97
                        }
×
98
                    }
×
99

×
100
                    var elementType = navigationBase.TargetEntityType.ClrType;
×
101
                    var computedItemParamater = Expression.Parameter(elementType, "c");
×
102
                    var storedItemParameter = Expression.Parameter(elementType, "s");
×
103

×
104
                    Expression storedAndComputedItemMatches = Expression.Equal(Expression.Constant(1), Expression.Constant(1));
×
105
                    foreach (var controlledMember in controlledMembers)
×
106
                    {
×
107
                        storedAndComputedItemMatches = Expression.AndAlso(
×
108
                            storedAndComputedItemMatches,
×
109
                            Expression.Call(
×
110
                                typeof(object), nameof(object.Equals), [],
×
111
                                Expression.Convert(
×
112
                                    CreateEFPropertyExpression(computedItemParamater, controlledMember),
×
113
                                    typeof(object)),
×
114
                                Expression.Convert(
×
115
                                    CreateEFPropertyExpression(storedItemParameter, controlledMember),
×
116
                                    typeof(object))
×
117
                            )
×
118
                        );
×
119
                    }
×
120

×
NEW
121
                    var quantityMatches = Expression.Equal(
×
122
                        Expression.Call(
×
123
                            typeof(Enumerable), nameof(Enumerable.Count), [elementType],
×
124
                            computedValue
×
125
                        ),
×
126
                        Expression.Call(
×
127
                            typeof(Enumerable), nameof(Enumerable.Count), [elementType],
×
128
                            storedValue
×
129
                        )
×
130
                    );
×
131

×
NEW
132
                    var allValuesAreStored = Expression.Not(Expression.Call(
×
133
                        typeof(Enumerable), nameof(Enumerable.Any), [storedItemParameter.Type],
×
134
                        storedValue,
×
135
                        Expression.Lambda(
×
136
                            Expression.Not(
×
137
                                Expression.Call(
×
138
                                    typeof(Enumerable), nameof(Enumerable.Any), [elementType],
×
139
                                    computedValue,
×
140
                                    Expression.Lambda(
×
141
                                        storedAndComputedItemMatches,
×
142
                                        computedItemParamater
×
143
                                    )
×
144
                                )
×
145
                            ),
×
146
                            storedItemParameter
×
147
                        )
×
NEW
148
                    ));
×
149

×
NEW
150
                    return Expression.And(
×
NEW
151
                        quantityMatches,
×
NEW
152
                        allValuesAreStored
×
153
                    );
×
154
                }
×
155
            );
×
156

157
            return body;
×
158
        }
159

160
        if (navigationBase is INavigation navigation && !navigation.IsCollection)
×
161
        {
162
            var principalKeyProperty = navigation.ForeignKey.PrincipalKey.Properties[0];
×
163
            // Compare keys of references
164
            computedValue = AddPropertyAccess(computedValue, principalKeyProperty);
×
165
            storedValue = AddPropertyAccess(storedValue, principalKeyProperty);
×
166
        }
167

NEW
168
        return Expression.Call(
×
169
            typeof(object), nameof(object.Equals), [],
×
170
            Expression.Convert(computedValue, typeof(object)),
×
171
            Expression.Convert(storedValue, typeof(object))
×
NEW
172
        );
×
173
    }
174

175
    private static Expression AddPropertyAccess(Expression expression, IProperty property)
176
    {
177
        return ChangeExpressionConditionals(expression, (expression) =>
×
178
        {
×
179
            if (expression is ConstantExpression c && c.Value is null)
×
180
            {
×
181
                return Expression.Constant(null, MakeNullable(property.ClrType));
×
182
            }
×
183
            else
×
184
            {
×
185
                return MakeNullable(CreateEFPropertyExpression(expression, property));
×
186
            }
×
187
        });
×
188
    }
189

190
    protected static Expression ChangeExpressionConditionals(
191
        Expression expression,
192
        Func<Expression, Expression> change)
193
    {
194
        if (expression is ConditionalExpression cond)
×
195
        {
196
            return Expression.Condition(
×
197
                cond.Test,
×
198
                ChangeExpressionConditionals(cond.IfTrue, change),
×
199
                ChangeExpressionConditionals(cond.IfFalse, change)
×
200
            );
×
201
        }
202
        else
203
        {
204
            return change(expression);
×
205
        }
206
    }
207

208
    private static Expression MakeNullable(Expression expression)
209
    {
210
        if (IsNullable(expression.Type))
×
211
        {
212
            return expression;
×
213
        }
214
        return Expression.Convert(expression, MakeNullable(expression.Type));
×
215
    }
216

217
    private static Type MakeNullable(Type type)
218
    {
219
        return IsNullable(type)
×
220
            ? type
×
221
            : typeof(Nullable<>).MakeGenericType(type);
×
222
    }
223

224
    private static bool IsNullable(Type type)
225
    {
226
        return type.IsClass || Nullable.GetUnderlyingType(type) is not null;
×
227
    }
228
}
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