abpframework/abp · error · Exception
Predicate cannot be started again.
Error message
Predicate cannot be started again.
What it means
ExpressionStarter<T>.Start(exp) seeds the predicate builder with a first expression and may be called only once; the builder tracks 'started' state via the private _predicate field (IsStarted == _predicate != null). The Or() and And() methods auto-call Start() internally when the builder is not yet started, then combine, so explicit Start() calls are rarely needed. Calling Start() after the predicate is already seeded (directly, or transitively via Or/And) is treated as a logic error and aborted rather than silently overwriting the predicate.
Source
Thrown at framework/src/Volo.Abp.Core/System/Linq/PredicateOperator.cs:148
private Expression<Func<T, bool>>? _predicate;
/// <summary>Determines if the predicate is started.</summary>
public bool IsStarted => _predicate != null;
/// <summary> A default expression to use only when the expression is null </summary>
public bool UseDefaultExpression => DefaultExpression != null;
/// <summary>The default expression</summary>
public Expression<Func<T, bool>>? DefaultExpression { get; set; }
/// <summary>Set the Expression predicate</summary>
/// <param name="exp">The first expression</param>
public Expression<Func<T, bool>> Start(Expression<Func<T, bool>> exp)
{
if (IsStarted)
{
throw new Exception("Predicate cannot be started again.");
}
return _predicate = exp;
}
/// <summary>Or</summary>
public Expression<Func<T, bool>> Or([NotNull] Expression<Func<T, bool>> expr2)
{
return (IsStarted) ? _predicate = Predicate.Or(expr2) : Start(expr2);
}
/// <summary>And</summary>
public Expression<Func<T, bool>> And([NotNull] Expression<Func<T, bool>> expr2)
{
return (IsStarted) ? _predicate = Predicate.And(expr2) : Start(expr2);
}
/// <summary> Show predicate string </summary>View on GitHub (pinned to 7ed43b1931)
Solutions
- Replace the second Start(expr) with Or(expr) or And(expr) - they auto-start when needed and combine thereafter.
- If a fresh seed is required, create a new ExpressionStarter<T> via PredicateBuilder.New<T>() instead of reusing one.
- Guard the call: 'if (!predicate.IsStarted) predicate.Start(seed);' before chaining Or/And.
Example fix
// before var pred = PredicateBuilder.New<MyEntity>(); pred.Start(x => x.Active); pred.Start(x => x.Name == "foo"); // throws // after var pred = PredicateBuilder.New<MyEntity>(); pred.Start(x => x.Active); pred.And(x => x.Name == "foo");
Defensive patterns
Strategy: validation
Validate before calling
var pred = PredicateBuilder.New<MyEntity>(); // never call Start twice; use Or/And which auto-start if (someCondition) pred = pred.Or(x => x.Active); // safe first call else pred = pred.Or(x => x.Inactive);
Type guard
bool CanStart<T>(ExpressionStarter<T> p) => !p.IsStarted;
Prevention
- Treat Start() as a one-time seed; prefer Or()/And() for all subsequent appends.
- Create a fresh ExpressionStarter via PredicateBuilder.New<T>() for each independent predicate rather than reusing and resetting.
- In loops, accumulate with Or/And against a single starter instead of calling Start per iteration.
When it happens
Trigger: Calling ExpressionStarter<T>.Start(expr) when IsStarted is already true: after a prior Start(), or after Or()/And() which set _predicate internally on the first combine.
Common situations: Building a dynamic LINQ filter in a loop where Start() is called per iteration instead of Or()/And(); sharing one ExpressionStarter instance across two builder methods that each call Start(); porting code that treats Start like an assignment.
AI-assisted analysis of abpframework/abp@7ed43b1931 (2026-08-13).
Data as JSON: /api/errors/4d5d5963e48fd294.
Report an issue: GitHub.