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

lucaslorentz / auto-compute / 12972299212

26 Jan 2025 06:44AM UTC coverage: 83.14% (-0.08%) from 83.217%
12972299212

push

github

lucaslorentz
Transform consistency filter expression before executing

3 of 7 new or added lines in 3 files covered. (42.86%)

1 existing line in 1 file now uncovered.

1790 of 2153 relevant lines covered (83.14%)

431.54 hits per line

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

98.11
/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
{
14
    private readonly IList<IEntityContextPropagator> _entityContextPropagators = [];
10✔
15
    private readonly HashSet<IObservedNavigationAccessLocator> _navigationAccessLocators = [];
10✔
16
    private readonly HashSet<IObservedMemberAccessLocator> _memberAccessLocators = [];
10✔
17
    private readonly IList<Func<Expression, Expression>> _expressionModifiers = [];
10✔
18
    private IObservedEntityTypeResolver? _entityTypeResolver;
19

20
    public ComputedExpressionAnalyzer<TInput> AddDefaults()
21
    {
22
        return AddEntityContextPropagator(new ChangeTrackingEntityContextPropagator())
10✔
23
            .AddEntityContextPropagator(new ConditionalEntityContextPropagator())
10✔
24
            .AddEntityContextPropagator(new ArrayEntityContextPropagator())
10✔
25
            .AddEntityContextPropagator(new ConvertEntityContextPropagator())
10✔
26
            .AddEntityContextPropagator(new LinqMethodsEntityContextPropagator(new Lazy<IObservedEntityTypeResolver?>(() => _entityTypeResolver)))
10✔
27
            .AddEntityContextPropagator(new KeyValuePairEntityContextPropagator())
10✔
28
            .AddEntityContextPropagator(new GroupingEntityContextPropagator())
10✔
29
            .AddEntityContextPropagator(new NavigationEntityContextPropagator(_navigationAccessLocators));
10✔
30
    }
31

32
    public ComputedExpressionAnalyzer<TInput> AddObservedMemberAccessLocator(
33
        IObservedMemberAccessLocator memberAccessLocator)
34
    {
35
        if (memberAccessLocator is IObservedNavigationAccessLocator nav)
10✔
36
            _navigationAccessLocators.Add(nav);
10✔
37

38
        _memberAccessLocators.Add(memberAccessLocator);
10✔
39
        return this;
10✔
40
    }
41

42
    public ComputedExpressionAnalyzer<TInput> AddEntityContextPropagator(
43
        IEntityContextPropagator propagator)
44
    {
45
        _entityContextPropagators.Add(propagator);
80✔
46
        return this;
80✔
47
    }
48

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

55
    public ComputedExpressionAnalyzer<TInput> SetObservedEntityTypeResolver(
56
        IObservedEntityTypeResolver entityTypeResolver)
57
    {
58
        _entityTypeResolver = entityTypeResolver;
10✔
59
        return this;
10✔
60
    }
61

62
    public IComputedChangesProvider<TInput, TEntity, TChange> CreateChangesProvider<TEntity, TValue, TChange>(
63
        IObservedEntityType<TInput> entityType,
64
        Expression<Func<TEntity, TValue>> computedExpression,
65
        Expression<Func<TEntity, bool>>? filterExpression,
66
        IChangeCalculation<TValue, TChange> changeCalculation)
67
        where TEntity : class
68
    {
69
        filterExpression ??= static x => true;
112✔
70

71
        computedExpression = (Expression<Func<TEntity, TValue>>)RunExpressionModifiers(computedExpression);
112✔
72

73
        var computedEntityContext = GetEntityContext(entityType, computedExpression, changeCalculation.IsIncremental);
112✔
74

75
        var computedValueAccessors = new ComputedValueAccessors<TInput, TEntity, TValue>(
112✔
76
            changeCalculation.IsIncremental
112✔
77
                ? GetIncrementalOriginalValueExpression(entityType, computedExpression).Compile()
112✔
78
                : GetOriginalValueExpression(entityType, computedExpression).Compile(),
112✔
79
            changeCalculation.IsIncremental
112✔
80
                ? GetIncrementalCurrentValueExpression(entityType, computedExpression).Compile()
112✔
81
                : GetCurrentValueExpression(entityType, computedExpression).Compile()
112✔
82
        );
112✔
83

84
        var filterEntityContext = GetEntityContext(entityType, filterExpression, false);
112✔
85

86
        return new ComputedChangesProvider<TInput, TEntity, TValue, TChange>(
112✔
87
            computedExpression,
112✔
88
            computedEntityContext,
112✔
89
            filterExpression.Compile(),
112✔
90
            filterEntityContext,
112✔
91
            changeCalculation,
112✔
92
            computedValueAccessors
112✔
93
        );
112✔
94
    }
95

96
    private RootEntityContext GetEntityContext<TEntity, TValue>(
97
        IObservedEntityType entityType,
98
        Expression<Func<TEntity, TValue>> computedExpression,
99
        bool isIncremental)
100
        where TEntity : class
101
    {
102
        var analysis = new ComputedExpressionAnalysis();
224✔
103

104
        var entityContext = new RootEntityContext(entityType);
224✔
105
        analysis.AddContext(computedExpression.Parameters[0], EntityContextKeys.None, entityContext);
224✔
106

107
        new PropagateEntityContextsVisitor(
224✔
108
            _entityContextPropagators,
224✔
109
            analysis
224✔
110
        ).Visit(computedExpression);
224✔
111

112
        analysis.RunPropagations();
224✔
113

114
        new CollectObservedMembersVisitor(
224✔
115
            analysis,
224✔
116
            _memberAccessLocators
224✔
117
        ).Visit(computedExpression);
224✔
118

119
        analysis.RunActions();
224✔
120

121
        entityContext.ValidateAll();
224✔
122

123
        return entityContext;
224✔
124
    }
125

126
    private Expression<Func<TInput, IncrementalContext, TEntity, TValue>> GetOriginalValueExpression<TEntity, TValue>(
127
        IObservedEntityType<TInput> entityType,
128
        Expression<Func<TEntity, TValue>> computedExpression)
129
    {
130
        var inputParameter = Expression.Parameter(typeof(TInput), "input");
92✔
131
        var incrementalContextParameter = Expression.Parameter(typeof(IncrementalContext), "incrementalContext");
92✔
132

133
        var newBody = new ReplaceObservedMemberAccessVisitor(
92✔
134
            _memberAccessLocators,
92✔
135
            memberAccess => memberAccess.CreateOriginalValueExpression(inputParameter)
274✔
136
        ).Visit(computedExpression.Body)!;
92✔
137

138
        newBody = PrepareComputedOutputExpression(computedExpression.ReturnType, newBody);
92✔
139

140
        newBody = ReturnDefaultIfEntityStateExpression(
92✔
141
            entityType,
92✔
142
            inputParameter,
92✔
143
            computedExpression.Parameters.First(),
92✔
144
            newBody,
92✔
145
            ObservedEntityState.Added);
92✔
146

147
        return (Expression<Func<TInput, IncrementalContext, TEntity, TValue>>)Expression.Lambda(newBody, [
92✔
148
            inputParameter,
92✔
149
            incrementalContextParameter,
92✔
150
            .. computedExpression.Parameters
92✔
151
        ]);
92✔
152
    }
153

154
    private Expression<Func<TInput, IncrementalContext, TEntity, TValue>> GetCurrentValueExpression<TEntity, TValue>(
155
        IObservedEntityType<TInput> entityType,
156
        Expression<Func<TEntity, TValue>> computedExpression)
157
    {
158
        var inputParameter = Expression.Parameter(typeof(TInput), "input");
92✔
159
        var incrementalContextParameter = Expression.Parameter(typeof(IncrementalContext), "incrementalContext");
92✔
160

161
        var newBody = new ReplaceObservedMemberAccessVisitor(
92✔
162
            _memberAccessLocators,
92✔
163
            memberAccess => memberAccess.CreateCurrentValueExpression(inputParameter)
274✔
164
        ).Visit(computedExpression.Body)!;
92✔
165

166
        newBody = PrepareComputedOutputExpression(computedExpression.ReturnType, newBody);
92✔
167

168
        newBody = ReturnDefaultIfEntityStateExpression(
92✔
169
            entityType,
92✔
170
            inputParameter,
92✔
171
            computedExpression.Parameters.First(),
92✔
172
            newBody,
92✔
173
            ObservedEntityState.Removed);
92✔
174

175
        return (Expression<Func<TInput, IncrementalContext, TEntity, TValue>>)Expression.Lambda(newBody, [
92✔
176
            inputParameter,
92✔
177
            incrementalContextParameter,
92✔
178
            .. computedExpression.Parameters
92✔
179
        ]);
92✔
180
    }
181

182
    private Expression<Func<TInput, IncrementalContext, TEntity, TValue>> GetIncrementalOriginalValueExpression<TEntity, TValue>(
183
        IObservedEntityType<TInput> entityType,
184
        Expression<Func<TEntity, TValue>> computedExpression)
185
    {
186
        var inputParameter = Expression.Parameter(typeof(TInput), "input");
20✔
187
        var incrementalContextParameter = Expression.Parameter(typeof(IncrementalContext), "incrementalContext");
20✔
188

189
        var newBody = new ReplaceObservedMemberAccessVisitor(
20✔
190
            _memberAccessLocators,
20✔
191
            memberAccess => memberAccess.CreateIncrementalOriginalValueExpression(inputParameter, incrementalContextParameter)
70✔
192
        ).Visit(computedExpression.Body)!;
20✔
193

194
        newBody = PrepareComputedOutputExpression(computedExpression.ReturnType, newBody);
20✔
195

196
        newBody = ReturnDefaultIfEntityStateExpression(
20✔
197
            entityType,
20✔
198
            inputParameter,
20✔
199
            computedExpression.Parameters.First(),
20✔
200
            newBody,
20✔
201
            ObservedEntityState.Added);
20✔
202

203
        return (Expression<Func<TInput, IncrementalContext, TEntity, TValue>>)Expression.Lambda(newBody, [
20✔
204
            inputParameter,
20✔
205
            incrementalContextParameter,
20✔
206
            .. computedExpression.Parameters
20✔
207
        ]);
20✔
208
    }
209

210
    private Expression<Func<TInput, IncrementalContext, TEntity, TValue>> GetIncrementalCurrentValueExpression<TEntity, TValue>(
211
        IObservedEntityType<TInput> entityType,
212
        Expression<Func<TEntity, TValue>> computedExpression)
213
    {
214
        var inputParameter = Expression.Parameter(typeof(TInput), "input");
20✔
215
        var incrementalContextParameter = Expression.Parameter(typeof(IncrementalContext), "incrementalContext");
20✔
216

217
        var newBody = new ReplaceObservedMemberAccessVisitor(
20✔
218
            _memberAccessLocators,
20✔
219
            memberAccess => memberAccess.CreateIncrementalCurrentValueExpression(inputParameter, incrementalContextParameter)
70✔
220
        ).Visit(computedExpression.Body)!;
20✔
221

222
        newBody = PrepareComputedOutputExpression(computedExpression.ReturnType, newBody);
20✔
223

224
        newBody = ReturnDefaultIfEntityStateExpression(
20✔
225
            entityType,
20✔
226
            inputParameter,
20✔
227
            computedExpression.Parameters.First(),
20✔
228
            newBody,
20✔
229
            ObservedEntityState.Removed);
20✔
230

231
        return (Expression<Func<TInput, IncrementalContext, TEntity, TValue>>)Expression.Lambda(newBody, [
20✔
232
            inputParameter,
20✔
233
            incrementalContextParameter,
20✔
234
            .. computedExpression.Parameters
20✔
235
        ]);
20✔
236
    }
237

238
    public Expression RunExpressionModifiers(Expression expression)
239
    {
240
        foreach (var modifier in _expressionModifiers)
224✔
NEW
241
            expression = modifier(expression);
×
242

243
        return expression;
112✔
244
    }
245

246
    private Expression PrepareComputedOutputExpression(Type returnType, Expression body)
247
    {
248
        var prepareOutputMethod = GetType().GetMethod(
224✔
249
            nameof(PrepareComputedOutput),
224✔
250
            BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.FlattenHierarchy)!
224✔
251
            .MakeGenericMethod(returnType);
224✔
252

253
        return Expression.Call(
224✔
254
            prepareOutputMethod,
224✔
255
            body);
224✔
256
    }
257

258
    private Expression ReturnDefaultIfEntityStateExpression(
259
        IObservedEntityType<TInput> entityType,
260
        ParameterExpression inputParameter,
261
        ParameterExpression entityParameter,
262
        Expression expression,
263
        ObservedEntityState entityState)
264
    {
265
        return Expression.Condition(
224✔
266
            Expression.Equal(
224✔
267
                Expression.Call(
224✔
268
                    Expression.Constant(entityType),
224✔
269
                    "GetEntityState",
224✔
270
                    [],
224✔
271
                    inputParameter,
224✔
272
                    entityParameter
224✔
273
                ),
224✔
274
                Expression.Constant(entityState)
224✔
275
            ),
224✔
276
            Expression.Default(expression.Type),
224✔
277
            expression
224✔
278
        );
224✔
279
    }
280

281
    private static T PrepareComputedOutput<T>(T value)
282
    {
283
        var type = typeof(T);
1,412✔
284
        if (type.IsConstructedGenericType
1,412✔
285
            && type.GetGenericTypeDefinition() == typeof(IEnumerable<>)
1,412✔
286
            && value is IEnumerable enumerable)
1,412✔
287
        {
288
            return (T)(object)enumerable.ToArray(type.GetGenericArguments()[0]);
45✔
289
        }
290

291
        return value;
1,367✔
292
    }
293
}
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