{"record":{"id":"f197af4a035f6e5f","repo":"dotnet/efcore","slug":"the-dbfunction-function-has-an-invalid-return-f197af","errorCode":null,"errorMessage":"The DbFunction '{function}' has an invalid return type '{type}'. Non-scalar functions must return 'IQueryable' of a valid entity type.","messagePattern":"The DbFunction '(.+?)' has an invalid return type '(.+?)'\\. Non-scalar functions must return 'IQueryable' of a valid entity type\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/EFCore.Relational/Metadata/Conventions/TableValuedDbFunctionConvention.cs","lineNumber":65,"sourceCode":"    }\n\n    /// <summary>\n    ///     Called when an <see cref=\"IConventionDbFunction\" /> is added to the model.\n    /// </summary>\n    /// <param name=\"dbFunctionBuilder\">The builder for the <see cref=\"IConventionDbFunction\" />.</param>\n    private static void ProcessDbFunctionAdded(\n        IConventionDbFunctionBuilder dbFunctionBuilder)\n    {\n        var function = dbFunctionBuilder.Metadata;\n        if (function.IsScalar)\n        {\n            return;\n        }\n\n        var elementType = function.ReturnType.TryGetElementType(typeof(IQueryable<>))!;\n        if (!elementType.IsValidEntityType())\n        {\n            throw new InvalidOperationException(\n                RelationalStrings.DbFunctionInvalidIQueryableReturnType(\n                    function.ModelName, function.ReturnType.ShortDisplayName()));\n        }\n\n        var model = function.Model;\n        var entityType = model.FindEntityType(elementType);\n        if (entityType?.IsOwned() == true\n            || model.IsOwned(elementType)\n            || (entityType == null && model.FindEntityTypes(elementType).Any()))\n        {\n            return;\n        }\n\n        dbFunctionBuilder.ModelBuilder.Entity(elementType);\n    }\n}\n","sourceCodeStart":47,"sourceCodeEnd":82,"githubUrl":"https://github.com/dotnet/efcore/blob/dbf9771522148d61a2467854921bd5dc6f6e6916/src/EFCore.Relational/Metadata/Conventions/TableValuedDbFunctionConvention.cs#L47-L82","documentation":"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.","triggerScenarios":"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(...).","commonSituations":"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.","solutions":["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.","If the function returns scalar/primitive rows, change the return type to a scalar type that the provider can map instead of IQueryable<T>.","If T is only used as a projection, introduce a dedicated entity/owned type or keyless entity for the row shape and map that.","Verify the return type is exactly IQueryable<T> (not IEnumerable<T>, Task<IQueryable<T>>, or a custom wrapper)."],"exampleFix":"// before\npublic IQueryable<RawOrderRow> OrderRows(int id)\n    => FromExpression(() => OrderRows(id));\n// 'RawOrderRow' is not configured as an entity\n\n// after\nmodelBuilder.Entity<RawOrderRow>(b =>\n{\n    b.HasNoKey();\n    b.Property(r => r.OrderId);\n    b.Property(r => r.Total);\n});\npublic IQueryable<RawOrderRow> OrderRows(int id)\n    => FromExpression(() => OrderRows(id));","handlingStrategy":"validation","validationCode":"// Before finalizing the model, verify each non-scalar DbFunction returns IQueryable<ValidEntityType>\nforeach (var fn in modelBuilder.Model.GetDbFunctions())\n{\n    if (!fn.IsScalar)\n    {\n        var elem = fn.ReturnType.TryGetElementType(typeof(IQueryable<>));\n        if (elem is null || !elem.IsValidEntityType())\n        {\n            throw new InvalidOperationException($\"{fn.ModelName} must return IQueryable<T> of a valid entity type.\");\n        }\n    }\n}","typeGuard":"// Reflect over a candidate method before registering as a TVF DbFunction\nstatic bool IsValidTvfReturn(Type returnType, IMutableModel model)\n{\n    var elem = returnType.TryGetElementType(typeof(IQueryable<>));\n    return elem is not null && elem.IsValidEntityType();\n}","tryCatchPattern":null,"preventionTips":["Always configure the row type returned by a TVF as an entity (HasKey) or keyless entity (HasNoKey) in the same OnModelCreating.","Write a unit test that builds the model and runs the first query in-memory to surface convention errors early.","Keep TVF row types in their own entity classes; do not reuse arbitrary DTOs."],"tags":["efcore","dbfunction","tvf","model-building","entity-mapping"],"analyzedSha":"dbf9771522148d61a2467854921bd5dc6f6e6916","analyzedAt":"2026-08-06T20:46:03.226Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}