sql server中tvp必须用用户定义表类型声明,即先执行create type创建表类型,再在存储过程中以readonly方式引用;c#调用时需用sqlparameter设置sqldbtype.structured并严格匹配typename与datatable列顺序。

SQL Server中TVP必须用用户定义表类型声明
直接在存储过程中定义临时表结构或用SELECT INTO建表,无法作为TVP传入——SQL Server要求TVP的结构必须提前通过CREATE TYPE创建为用户定义表类型。否则调用时会报错:The table-valued parameter "@data" must be declared with a user-defined table type.
- 必须先执行
CREATE TYPE dbo.ProductUpdateList AS TABLE (Id INT, Price DECIMAL(18,2), Stock INT); - 类型名(如
dbo.ProductUpdateList)需在存储过程参数中完整引用,不能只写ProductUpdateList - 该类型一旦被存储过程引用,就不能直接
DROP TYPE,需先删/改存储过程
存储过程内联TVP时别直接UPDATE…FROM,小心NULL覆盖
TVP本质是只读表,但很多人习惯写UPDATE t SET t.Price = s.Price FROM Products t INNER JOIN @data s ON t.Id = s.Id,这看似合理,实则危险:如果TVP某行Price为NULL,对应记录的Price会被清空。
- 正确做法是显式过滤:
UPDATE t SET t.Price = s.Price, t.Stock = s.Stock FROM Products t INNER JOIN @data s ON t.Id = s.Id WHERE s.Price IS NOT NULL OR s.Stock IS NOT NULL; - 更稳妥的是用
MERGE,它天然支持WHEN MATCHED THEN UPDATE且可加条件判断 - TVP列名和目标表列名不一致时,
UPDATE…FROM仍能运行,但逻辑易错,建议列名保持一致
C#调用时DataTable列顺序和类型必须与用户定义表类型严格一致
即使列名、类型都对,只要DataTable列顺序和CREATE TYPE中定义的顺序不一致,SQL Server可能静默忽略部分列,或报The given value of type String from the data source cannot be converted to type decimal of the specified target column.
- 构造
DataTable前,先查sys.table_types和sys.columns确认顺序:SELECT c.name, c.system_type_id, c.max_length FROM sys.table_types t JOIN sys.columns c ON t.type_table_object_id = c.object_id WHERE t.name = 'ProductUpdateList' ORDER BY c.column_id; - 添加列必须按此顺序:
dt.Columns.Add("Id", typeof(int)); dt.Columns.Add("Price", typeof(decimal)); ... -
SqlParameter.SqlDbType必须设为Structured,且TypeName必须匹配(如"dbo.ProductUpdateList"),大小写敏感
大批量更新时TVP性能不如BULK INSERT,但胜在事务一致性
TVP走的是常规查询计划路径,当行数超5000,单次调用延迟明显上升;而BULK INSERT或SqlBulkCopy底层绕过解析,快一个数量级。但TVP唯一优势是能和其他DML语句共处同一事务——比如“先更新价格,再插入日志,失败则全部回滚”。
- 500行以内:TVP简洁安全,推荐用
- 5000行以上:考虑拆成多个TVP批次(如每批2000行),或改用
SqlBulkCopy+ 临时表 +MERGE - TVP不支持
IDENTITY列插入,也不支持计算列,若源数据含这些字段,需提前剔除或映射到普通列
最常被忽略的是:TVP在存储过程中不可修改(INSERT INTO @tvp非法),也不能用SELECT * FROM @tvp以外的DDL操作——它就是一个带结构的只读结果集容器。











