dotnet/efcore · error · InvalidOperationException

The DbFunction '{function}' has an invalid return type '{typ

Error message

The DbFunction '{function}' has an invalid return type '{type}'. Non-scalar functions must return 'IQueryable' of a valid entity type.

What it means

Thrown by TableValuedDbFunctionConvention.ProcessModelFinalizing when a non-scalar (IQueryable-returning) DbFunction's element type is not a valid entity type. EF requires TVFs to return IQueryable<T> where T is (or can become) an entity type so it can shape results into tracked entities. The convention runs at model finalization, so the error surfaces late — when the model is being sealed.

Source

Thrown at src/EFCore.Relational/Metadata/Conventions/TableValuedDbFunctionConvention.cs:65

    }

    /// <summary>
    ///     Called when an <see cref="IConventionDbFunction" /> is added to the model.
    /// </summary>
    /// <param name="dbFunctionBuilder">The builder for the <see cref="IConventionDbFunction" />.</param>
    private static void ProcessDbFunctionAdded(
        IConventionDbFunctionBuilder dbFunctionBuilder)
    {
        var function = dbFunctionBuilder.Metadata;
        if (function.IsScalar)
        {
            return;
        }

        var elementType = function.ReturnType.TryGetElementType(typeof(IQueryable<>))!;
        if (!elementType.IsValidEntityType())
        {
            throw new InvalidOperationException(
                RelationalStrings.DbFunctionInvalidIQueryableReturnType(
                    function.ModelName, function.ReturnType.ShortDisplayName()));
        }

        var model = function.Model;
        var entityType = model.FindEntityType(elementType);
        if (entityType?.IsOwned() == true
            || model.IsOwned(elementType)
            || (entityType == null && model.FindEntityTypes(elementType).Any()))
        {
            return;
        }

        dbFunctionBuilder.ModelBuilder.Entity(elementType);
    }
}

View on GitHub (pinned to dbf9771522)

Solutions

  1. Make the IQueryable<T>'s T a real entity type by configuring it in the model with a primary key (modelBuilder.Entity<T>().HasKey(...)) or HasNoKey() if it is a keyless entity.
  2. If the function returns scalar/primitive rows, change the return type to a scalar type that the provider can map instead of IQueryable<T>.
  3. If T is only used as a projection, introduce a dedicated entity/owned type or keyless entity for the row shape and map that.
  4. Verify the return type is exactly IQueryable<T> (not IEnumerable<T>, Task<IQueryable<T>>, or a custom wrapper).

Example fix

// before
public IQueryable<RawOrderRow> OrderRows(int id)
    => FromExpression(() => OrderRows(id));
// 'RawOrderRow' is not configured as an entity

// after
modelBuilder.Entity<RawOrderRow>(b =>
{
    b.HasNoKey();
    b.Property(r => r.OrderId);
    b.Property(r => r.Total);
});
public IQueryable<RawOrderRow> OrderRows(int id)
    => FromExpression(() => OrderRows(id));
Defensive patterns

Strategy: validation

Validate before calling

// Before finalizing the model, verify each non-scalar DbFunction returns IQueryable<ValidEntityType>
foreach (var fn in modelBuilder.Model.GetDbFunctions())
{
    if (!fn.IsScalar)
    {
        var elem = fn.ReturnType.TryGetElementType(typeof(IQueryable<>));
        if (elem is null || !elem.IsValidEntityType())
        {
            throw new InvalidOperationException($"{fn.ModelName} must return IQueryable<T> of a valid entity type.");
        }
    }
}

Type guard

// Reflect over a candidate method before registering as a TVF DbFunction
static bool IsValidTvfReturn(Type returnType, IMutableModel model)
{
    var elem = returnType.TryGetElementType(typeof(IQueryable<>));
    return elem is not null && elem.IsValidEntityType();
}

Prevention

When it happens

Trigger: Registering a DbFunction whose method returns IQueryable<T> where T is a struct, an interface, a class with no key, or a type that cannot be mapped as an entity (e.g. IQueryable<string>, IQueryable<Dictionary>, or IQueryable<DtoWithoutKey>). Triggered via HasDbFunction on a DbContext or via modelBuilder.HasDbFunction(...).

Common situations: Developers try to map a TVF that returns a row type that is a plain DTO rather than an entity. Also happens after refactoring a return type from a scalar to a queryable of a non-entity, or when a keyless entity is expected but the type was never configured with HasNoKey or a key.

Related errors


AI-assisted analysis of dotnet/efcore@dbf9771522 (2026-08-06). Data as JSON: /api/errors/f197af4a035f6e5f. Report an issue: GitHub.