Troubleshooting Guide: SQL Server Error 352 — Table-Valued Parameter Must Be Declared with READONLY Option
Why This Error Happens
This error occurs because SQL Server 2025 requires all table-valued parameters (TVPs) passed into stored procedures or functions to be explicitly marked with the READONLY keyword in the parameter declaration. TVPs are internally represented as read-only, memory-optimized structures, so the engine cannot modify them within the procedure body, and it enforces this at compile time. This typically surfaces after a code migration, an ORM-generated query (like Entity Framework or Dapper), or a manually written stored procedure where the READONLY clause was omitted from the parameter list.
Step-by-Step Resolution Guide
Step 1: Identify the Offending Procedure or Query
- Open SQL Server Management Studio (SSMS) 21 or Azure Data Studio connected to your Windows Server 2025 instance.
- Check the error message text carefully — it names the specific parameter (e.g.,
@MyTableParam) and the procedure/batch where it failed. - Run the following to locate the procedure definition:
SELECT OBJECT_DEFINITION(OBJECT_ID('dbo.YourProcedureName'));
Step 2: Correct the Parameter Declaration
- Locate the parameter definition in the
CREATE PROCEDUREorCREATE FUNCTIONstatement. - Add the
READONLYkeyword immediately after the table type declaration:
CREATE PROCEDURE dbo.usp_InsertOrders
@OrderList dbo.OrderTableType READONLY
AS
BEGIN
-- procedure body
END
- Do not attempt to
UPDATE,INSERT, orDELETEagainst the TVP inside the procedure body, as this will cause a separate error even after addingREADONLY.
Step 3: Update the Application/Client-Side Code
- If the call originates from .NET, Java, or a driver such as ODBC 18/19, ensure the parameter is passed using
SqlDbType.Structuredand that the corresponding stored procedure signature matches the correctedREADONLYdeclaration. - Redeploy or recompile the client application after regenerating any auto-generated data access layer code (common with Entity Framework Core scaffolding).
Step 4: Recompile and Redeploy the Stored Procedure
- Drop and recreate the procedure, or use
ALTER PROCEDUREwith the corrected syntax:
ALTER PROCEDURE dbo.usp_InsertOrders
@OrderList dbo.OrderTableType READONLY
AS
BEGIN
SELECT * FROM @OrderList;
END
- Clear the plan cache for the specific object if testing repeatedly:
DBCC FREEPROCCACHE;
Alternative or Verification Steps
- Verify the fix by executing the procedure manually with a sample table variable populated using the same user-defined table type — it should run without Error 352.
- If the error persists across multiple procedures, run a metadata audit query against
sys.parametersandsys.table_typesto confirm no other TVP parameters in the database are missing theis_readonlyflag:
SELECT p.name, p.is_readonly, t.name AS TypeName
FROM sys.parameters p
JOIN sys.table_types t ON p.user_type_id = t.user_type_id;