dotnet/efcore · error · InvalidOperationException

Custom translation cannot be set on the DbFunction

Error message

Custom translation cannot be set on the DbFunction '{function}' since it is not a scalar function.

What it means

Custom translation (a Func<IReadOnlyList<SqlExpression>,SqlExpression>) only makes sense for scalar functions where EF translates the call into a single SQL expression. SetTranslation throws when translation is non-null and the function is not scalar or is an aggregate, because there is no single SQL expression to produce for a row set.

Solutions

  1. Remove the HasTranslation / SetTranslation call from the TVF DbFunction — TVFs are mapped by name/schema, not by expression translation.
  2. If you genuinely need custom SQL expression translation, convert the function back to a scalar return type so it qualifies for a translator.

Example fix

// before
public IQueryable<Order> Search(string term) => throw new NotSupportedException();
modelBuilder.HasDbFunction(typeof(AppDb).GetMethod(nameof(Search)))
    .HasTranslation(args => new SqlFragmentExpression("..."));

// after
modelBuilder.HasDbFunction(typeof(AppDb).GetMethod(nameof(Search)));
// TVF mapping is by schema/name; no HasTranslation
Defensive patterns

Strategy: validation

Validate before calling

var fn = modelBuilder.HasDbFunction(typeof(AppDb).GetMethod(nameof(AppDb.Search))!).Metadata;
if (fn.IsScalar && !fn.IsAggregate)
{
    // safe to call HasTranslation
}
else
{
    // skip HasTranslation for TVF / aggregate
}

Type guard

static bool SupportsCustomTranslation(IReadOnlyDbFunction fn) => fn.IsScalar && !fn.IsAggregate;

Prevention

When it happens

Trigger: Calling HasTranslation / SetTranslation on a DbFunction that returns IQueryable<T> (a TVF) or is marked as an aggregate function.

Common situations: Copy-pasting scalar DbFunction configuration (which includes HasTranslation) onto a TVF; refactor that converted a scalar function into a TVF without dropping the custom translator.

Related errors


AI-assisted analysis of dotnet/efcore@3a2006ef56 (2026-08-11). Data as JSON: /api/errors/512631e12a7cb1e4. Report an issue: GitHub.

Appendix: source

Thrown at src/EFCore.Relational/Metadata/Internal/DbFunction.cs:611

        set => SetTranslation(value, ConfigurationSource.Explicit);
    }

    /// <summary>
    ///     This is an internal API that supports the Entity Framework Core infrastructure and not subject to
    ///     the same compatibility standards as public APIs. It may be changed or removed without notice in
    ///     any release. You should only use it directly in your code with extreme caution and knowing that
    ///     doing so can result in application failures when updating to a new Entity Framework Core release.
    /// </summary>
    public virtual Func<IReadOnlyList<SqlExpression>, SqlExpression>? SetTranslation(
        Func<IReadOnlyList<SqlExpression>, SqlExpression>? translation,
        ConfigurationSource configurationSource)
    {
        EnsureMutable();

        if (translation != null
            && (!IsScalar || IsAggregate))
        {
            throw new InvalidOperationException(RelationalStrings.DbFunctionNonScalarCustomTranslation(MethodInfo?.DisplayName()));
        }

        _translation = translation;

        _translationConfigurationSource = translation == null
            ? null
            : configurationSource.Max(_translationConfigurationSource);

        return translation;
    }

    /// <summary>
    ///     This is an internal API that supports the Entity Framework Core infrastructure and not subject to
    ///     the same compatibility standards as public APIs. It may be changed or removed without notice in
    ///     any release. You should only use it directly in your code with extreme caution and knowing that
    ///     doing so can result in application failures when updating to a new Entity Framework Core release.
    /// </summary>
    public virtual ConfigurationSource? GetTranslationConfigurationSource()

View on GitHub (pinned to 3a2006ef56)