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

lucaslorentz / auto-compute / 12972395554

26 Jan 2025 07:02AM UTC coverage: 83.14%. Remained the same
12972395554

push

github

lucaslorentz
Fix reference consistency check

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

1790 of 2153 relevant lines covered (83.14%)

431.5 hits per line

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

15.33
/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>
1✔
14
    where TEntity : class
15
{
16
    private readonly Func<TEntity, TProperty> _compiledExpression = ((Expression<Func<TEntity, TProperty>>)changesProvider.Expression).Compile();
1✔
17

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

23
    public override string ToDebugString()
24
    {
25
        return navigationBase.ToString()!;
×
26
    }
27

28
    public override async Task<EFCoreChangeset> Update(IEFCoreComputedInput input)
29
    {
30
        var updateChanges = new EFCoreChangeset();
7✔
31
        var changes = await changesProvider.GetChangesAsync(input, null);
7✔
32
        foreach (var (entity, change) in changes)
24✔
33
        {
34
            var entityEntry = input.DbContext.Entry(entity);
5✔
35
            var navigationEntry = entityEntry.Navigation(navigationBase);
5✔
36

37
            var originalValue = GetOriginalValue(navigationEntry);
5✔
38

39
            var newValue = ChangesProvider.ChangeCalculation.IsIncremental
5✔
40
                ? ChangesProvider.ChangeCalculation.ApplyChange(
5✔
41
                    originalValue,
5✔
42
                    change)
5✔
43
                : change;
5✔
44

45
            if (!navigationEntry.IsLoaded && entityEntry.State != EntityState.Detached)
5✔
46
                await navigationEntry.LoadAsync();
×
47

48
            await MaybeUpdateNavigation(navigationEntry, newValue, updateChanges, ControlledMembers, ReuseKeySelector);
5✔
49
        }
50
        return updateChanges;
7✔
51
    }
52

53
    public override async Task FixAsync(object entity, DbContext dbContext)
54
    {
55
        var entityEntry = dbContext.Entry(entity);
×
56
        var navigationEntry = entityEntry.Navigation(navigationBase);
×
57

58
        if (!navigationEntry.IsLoaded && entityEntry.State != EntityState.Detached)
×
59
            await navigationEntry.LoadAsync();
×
60

61
        var newValue = _compiledExpression((TEntity)entity);
×
62

63
        await MaybeUpdateNavigation(navigationEntry, newValue, null, ControlledMembers, ReuseKeySelector);
×
64
    }
65

66
    private static TProperty GetOriginalValue(NavigationEntry navigationEntry)
67
    {
68
        if (navigationEntry.EntityEntry.State == EntityState.Added)
5✔
69
            return default!;
3✔
70

71
        return (TProperty)navigationEntry.GetOriginalValue()!;
2✔
72
    }
73

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

×
107
                    var elementType = navigationBase.TargetEntityType.ClrType;
×
108
                    var computedItemParamater = Expression.Parameter(elementType, "c");
×
109
                    var storedItemParameter = Expression.Parameter(elementType, "s");
×
110

×
111
                    Expression storedAndComputedItemMatches = Expression.Equal(Expression.Constant(1), Expression.Constant(1));
×
112
                    foreach (var controlledMember in controlledMembers)
×
113
                    {
×
114
                        storedAndComputedItemMatches = Expression.AndAlso(
×
115
                            storedAndComputedItemMatches,
×
116
                            Expression.Call(
×
117
                                typeof(object), nameof(object.Equals), [],
×
118
                                Expression.Convert(
×
119
                                    CreateEFPropertyExpression(computedItemParamater, controlledMember),
×
120
                                    typeof(object)),
×
121
                                Expression.Convert(
×
122
                                    CreateEFPropertyExpression(storedItemParameter, controlledMember),
×
123
                                    typeof(object))
×
124
                            )
×
125
                        );
×
126
                    }
×
127

×
128
                    var hasQuantityMismatch = Expression.NotEqual(
×
129
                        Expression.Call(
×
130
                            typeof(Enumerable), nameof(Enumerable.Count), [elementType],
×
131
                            computedValue
×
132
                        ),
×
133
                        Expression.Call(
×
134
                            typeof(Enumerable), nameof(Enumerable.Count), [elementType],
×
135
                            storedValue
×
136
                        )
×
137
                    );
×
138

×
139
                    var hasNonStoredComputed = Expression.Call(
×
140
                        typeof(Enumerable), nameof(Enumerable.Any), [storedItemParameter.Type],
×
141
                        storedValue,
×
142
                        Expression.Lambda(
×
143
                            Expression.Not(
×
144
                                Expression.Call(
×
145
                                    typeof(Enumerable), nameof(Enumerable.Any), [elementType],
×
146
                                    computedValue,
×
147
                                    Expression.Lambda(
×
148
                                        storedAndComputedItemMatches,
×
149
                                        computedItemParamater
×
150
                                    )
×
151
                                )
×
152
                            ),
×
153
                            storedItemParameter
×
154
                        )
×
155
                    );
×
156

×
157
                    return Expression.Or(
×
158
                        hasQuantityMismatch,
×
159
                        hasNonStoredComputed
×
160
                    );
×
161
                }
×
162
            );
×
163

164
            return body;
×
165
        }
166

NEW
167
        if (navigationBase is INavigation navigation && !navigation.IsCollection)
×
168
        {
169
            var principalKeyProperty = navigation.ForeignKey.PrincipalKey.Properties[0];
×
170
            // Compare keys of references
171
            computedValue = AddPropertyAccess(computedValue, principalKeyProperty);
×
172
            storedValue = AddPropertyAccess(storedValue, principalKeyProperty);
×
173
        }
174

175
        return Expression.Not(Expression.Call(
×
176
            typeof(object), nameof(object.Equals), [],
×
177
            Expression.Convert(computedValue, typeof(object)),
×
178
            Expression.Convert(storedValue, typeof(object))
×
179
        ));
×
180
    }
181

182
    private static Expression AddPropertyAccess(Expression expression, IProperty property)
183
    {
184
        return ChangeExpressionConditionals(expression, (expression) =>
×
185
        {
×
186
            if (expression is ConstantExpression c && c.Value is null)
×
187
            {
×
188
                return Expression.Constant(null, MakeNullable(property.ClrType));
×
189
            }
×
190
            else
×
191
            {
×
192
                return MakeNullable(CreateEFPropertyExpression(expression, property));
×
193
            }
×
194
        });
×
195
    }
196

197
    protected static Expression ChangeExpressionConditionals(
198
        Expression expression,
199
        Func<Expression, Expression> change)
200
    {
201
        if (expression is ConditionalExpression cond)
×
202
        {
203
            return Expression.Condition(
×
204
                cond.Test,
×
205
                ChangeExpressionConditionals(cond.IfTrue, change),
×
206
                ChangeExpressionConditionals(cond.IfFalse, change)
×
207
            );
×
208
        }
209
        else
210
        {
211
            return change(expression);
×
212
        }
213
    }
214

215
    private static Expression MakeNullable(Expression expression)
216
    {
217
        if (IsNullable(expression.Type))
×
218
        {
219
            return expression;
×
220
        }
221
        return Expression.Convert(expression, MakeNullable(expression.Type));
×
222
    }
223

224
    private static Type MakeNullable(Type type)
225
    {
226
        return IsNullable(type)
×
227
            ? type
×
228
            : typeof(Nullable<>).MakeGenericType(type);
×
229
    }
230

231
    private static bool IsNullable(Type type)
232
    {
233
        return type.IsClass || Nullable.GetUnderlyingType(type) is not null;
×
234
    }
235
}
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