• 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

97.06
/src/LLL.AutoCompute/ComputedExpressionAnalyzer.cs
1
using System.Collections;
2
using System.Linq.Expressions;
3
using System.Reflection;
4
using LLL.AutoCompute.ChangesProviders;
5
using LLL.AutoCompute.EntityContextPropagators;
6
using LLL.AutoCompute.EntityContexts;
7
using LLL.AutoCompute.Internal;
8
using LLL.AutoCompute.Internal.ExpressionVisitors;
9

10
namespace LLL.AutoCompute;
11

12
public class ComputedExpressionAnalyzer<TInput> : IComputedExpressionAnalyzer<TInput>
13
    where TInput : ComputedInput
14
{
15
    private readonly IList<IEntityContextNodeRule> _entityContextRules = [];
20✔
16
    private readonly HashSet<IObservedMemberAccessLocator> _memberAccessLocators = [];
20✔
17
    private readonly IList<Func<Expression, Expression>> _expressionModifiers = [];
20✔
18
    private IObservedEntityTypeResolver? _entityTypeResolver;
19

20
    public ComputedExpressionAnalyzer<TInput> AddDefaults()
21
    {
22
        return AddEntityContextRule(new ChangeTrackingEntityContextRule())
20✔
23
            .AddEntityContextRule(new ConditionalEntityContextRule())
20✔
24
            .AddEntityContextRule(new ArrayEntityContextRule())
20✔
25
            .AddEntityContextRule(new ConvertEntityContextRule())
20✔
26
            .AddEntityContextRule(new LinqMethodsEntityContextRule(new Lazy<IObservedEntityTypeResolver?>(() => _entityTypeResolver)))
20✔
27
            .AddEntityContextRule(new KeyValuePairEntityContextRule())
20✔
28
            .AddEntityContextRule(new GroupingEntityContextRule())
20✔
29
            .AddEntityContextRule(new MemberEntityContextRule(_memberAccessLocators));
20✔
30
    }
31

32
    public ComputedExpressionAnalyzer<TInput> AddObservedMemberAccessLocator(
33
        IObservedMemberAccessLocator memberAccessLocator)
34
    {
35
        _memberAccessLocators.Add(memberAccessLocator);
20✔
36
        return this;
20✔
37
    }
38

39
    public ComputedExpressionAnalyzer<TInput> AddEntityContextRule(
40
        IEntityContextNodeRule rule)
41
    {
42
        _entityContextRules.Add(rule);
160✔
43
        return this;
160✔
44
    }
45

46
    public ComputedExpressionAnalyzer<TInput> AddExpressionModifier(Func<Expression, Expression> modifier)
47
    {
48
        _expressionModifiers.Add(modifier);
×
49
        return this;
×
50
    }
51

52
    public ComputedExpressionAnalyzer<TInput> SetObservedEntityTypeResolver(
53
        IObservedEntityTypeResolver entityTypeResolver)
54
    {
55
        _entityTypeResolver = entityTypeResolver;
20✔
56
        return this;
20✔
57
    }
58

59
    public IComputedChangesProvider<TInput, TEntity, TChange> CreateChangesProvider<TEntity, TValue, TChange>(
60
        IObservedEntityType<TInput> entityType,
61
        Expression<Func<TEntity, TValue>> computedExpression,
62
        Expression<Func<TEntity, bool>>? filterExpression,
63
        IChangeCalculator<TValue, TChange> changeCalculation)
64
        where TEntity : class
65
    {
66
        filterExpression ??= static x => true;
224✔
67

68
        computedExpression = (Expression<Func<TEntity, TValue>>)RunExpressionModifiers(computedExpression);
224✔
69

70
        var computedEntityContext = GetEntityContext(entityType, computedExpression, changeCalculation.IsIncremental);
224✔
71

72
        var originalValueGetter = GetOriginalValueExpression(entityType, computedExpression).Compile();
224✔
73
        var currentValueGetter = GetCurrentValueExpression(entityType, computedExpression).Compile();
224✔
74

75
        var filterEntityContext = GetEntityContext(entityType, filterExpression, false);
224✔
76

77
        return new ComputedChangesProvider<TInput, TEntity, TValue, TChange>(
224✔
78
            computedExpression,
224✔
79
            computedEntityContext,
224✔
80
            filterExpression.Compile(),
224✔
81
            filterEntityContext,
224✔
82
            changeCalculation,
224✔
83
            originalValueGetter,
224✔
84
            currentValueGetter
224✔
85
        );
224✔
86
    }
87

88
    private RootEntityContext GetEntityContext<TEntity, TValue>(
89
        IObservedEntityType entityType,
90
        Expression<Func<TEntity, TValue>> computedExpression,
91
        bool isIncremental)
92
        where TEntity : class
93
    {
94
        var entityContext = new RootEntityContext(computedExpression.Parameters[0], entityType);
448✔
95

96
        var entityContextRegistry = new EntityContextRegistry();
448✔
97
        entityContextRegistry.RegisterContext(computedExpression.Parameters[0], EntityContextKeys.None, entityContext);
448✔
98

99
        new ApplyEntityContextNodeRulesVisitor(
448✔
100
            _entityContextRules,
448✔
101
            entityContextRegistry
448✔
102
        ).Visit(computedExpression);
448✔
103

104
        entityContextRegistry.PrepareEntityContexts();
448✔
105

106
        entityContext.ValidateAll();
448✔
107

108
        return entityContext;
448✔
109
    }
110

111
    private Expression<Func<TInput, TEntity, TValue>> GetOriginalValueExpression<TEntity, TValue>(
112
        IObservedEntityType<TInput> entityType,
113
        Expression<Func<TEntity, TValue>> computedExpression)
114
    {
115
        return GetValueExpression(
224✔
116
            entityType,
224✔
117
            computedExpression,
224✔
118
            (memberAccess, inputParameter) => memberAccess.CreateOriginalValueExpression(inputParameter),
684✔
119
            ObservedEntityState.Added
224✔
120
        );
224✔
121
    }
122

123
    private Expression<Func<TInput, TEntity, TValue>> GetCurrentValueExpression<TEntity, TValue>(
124
        IObservedEntityType<TInput> entityType,
125
        Expression<Func<TEntity, TValue>> computedExpression)
126
    {
127
        return GetValueExpression(
224✔
128
            entityType,
224✔
129
            computedExpression,
224✔
130
            (memberAccess, inputParameter) => memberAccess.CreateCurrentValueExpression(inputParameter),
684✔
131
            ObservedEntityState.Removed
224✔
132
        );
224✔
133
    }
134

135
    private Expression<Func<TInput, TEntity, TValue>> GetValueExpression<TEntity, TValue>(
136
        IObservedEntityType<TInput> entityType,
137
        Expression<Func<TEntity, TValue>> computedExpression,
138
        Func<IObservedMemberAccess, ParameterExpression, Expression> expressionModifier,
139
        ObservedEntityState defaultValueEntityState)
140
    {
141
        var inputParameter = Expression.Parameter(typeof(TInput), "input");
448✔
142

143
        var newBody = new ReplaceObservedMemberAccessVisitor(
448✔
144
            _memberAccessLocators,
448✔
145
            memberAccess => expressionModifier(memberAccess, inputParameter)
1,368✔
146
        ).Visit(computedExpression.Body)!;
448✔
147

148
        newBody = PrepareComputedOutputExpression(computedExpression.ReturnType, newBody);
448✔
149

150
        newBody = ReturnDefaultIfEntityStateExpression(
448✔
151
            entityType,
448✔
152
            inputParameter,
448✔
153
            computedExpression.Parameters.First(),
448✔
154
            newBody,
448✔
155
            defaultValueEntityState);
448✔
156

157
        return (Expression<Func<TInput, TEntity, TValue>>)Expression.Lambda(newBody, [
448✔
158
            inputParameter,
448✔
159
            .. computedExpression.Parameters
448✔
160
        ]);
448✔
161
    }
162

163
    public Expression RunExpressionModifiers(Expression expression)
164
    {
165
        foreach (var modifier in _expressionModifiers)
448✔
UNCOV
166
            expression = modifier(expression);
×
167

168
        return expression;
224✔
169
    }
170

171
    private Expression PrepareComputedOutputExpression(Type returnType, Expression body)
172
    {
173
        var prepareOutputMethod = GetType().GetMethod(
448✔
174
            nameof(PrepareComputedOutput),
448✔
175
            BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.FlattenHierarchy)!
448✔
176
            .MakeGenericMethod(returnType);
448✔
177

178
        return Expression.Call(
448✔
179
            prepareOutputMethod,
448✔
180
            body);
448✔
181
    }
182

183
    private Expression ReturnDefaultIfEntityStateExpression(
184
        IObservedEntityType<TInput> entityType,
185
        ParameterExpression inputParameter,
186
        ParameterExpression entityParameter,
187
        Expression expression,
188
        ObservedEntityState entityState)
189
    {
190
        return Expression.Condition(
448✔
191
            Expression.Equal(
448✔
192
                Expression.Call(
448✔
193
                    Expression.Constant(entityType),
448✔
194
                    "GetEntityState",
448✔
195
                    [],
448✔
196
                    inputParameter,
448✔
197
                    entityParameter
448✔
198
                ),
448✔
199
                Expression.Constant(entityState)
448✔
200
            ),
448✔
201
            Expression.Default(expression.Type),
448✔
202
            expression
448✔
203
        );
448✔
204
    }
205

206
    private static T PrepareComputedOutput<T>(T value)
207
    {
208
        var type = typeof(T);
2,824✔
209
        if (type.IsConstructedGenericType
2,824✔
210
            && type.GetGenericTypeDefinition() == typeof(IEnumerable<>)
2,824✔
211
            && value is IEnumerable enumerable)
2,824✔
212
        {
213
            return (T)(object)enumerable.ToArray(type.GetGenericArguments()[0]);
90✔
214
        }
215

216
        return value;
2,734✔
217
    }
218
}
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

© 2025 Coveralls, Inc