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

  1. Replace the second Start(expr) with Or(expr) or And(expr) - they auto-start when needed and combine thereafter.
  2. If a fresh seed is required, create a new ExpressionStarter<T> via PredicateBuilder.New<T>() instead of reusing one.
  3. 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

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.