Rai*_*olt 3 sql-server default-value c# user-defined-table-type table-valued-parameters
我在 SQL 中创建了一个表类型:
CREATE TYPE [dbo].[MyObject_TableType] AS TABLE
(
[Name] VARCHAR(MAX) DEFAULT '',
[Index] VARCHAR(MAX) DEFAULT ''
)
Run Code Online (Sandbox Code Playgroud)
我构建了一个DataTable
并用一个填充它DataRow
。我将该表作为参数提供给存储过程:
// Set up connection and command variables (code omitted)
// ...
// Make the data table
var table = new DataTable();
table.Columns.Add("Name");
table.Columns.Add("Index");
// Add one row that is missing an Index
var row = table.NewRow();
row["Name"] = "ObjectOne";
table.Rows.Add(row);
// Make a parameter for the table
var tableParameter = new SqlParameter("MyObjects", SqlDbType.Structured)
{
TypeName = "MyObject_TableType",
Value = table
};
command.Parameters.Add(tableParameter);
command.ExecuteNonQuery();
Run Code Online (Sandbox Code Playgroud)
我预计在存储过程内部,索引值将默认为''
(空 VARCHAR)。相反,我观察到该值为空。我知道这一点是因为我试图将表类型合并到实际表中,并且表上的索引列不允许空值。
为什么 SQL Server 不尊重我放置在表类型上的默认值?有没有办法强制 SQL Server 兑现它?
我想我可以通过使用DataTable.DefaultValue属性来解决这个问题,但该解决方案的缺点是我需要在两个不同的地方声明相同的默认值。如果可能,我想避免冗余代码。
为什么 SQL Server 不尊重我放置在表类型上的默认值?
实际上,SQL Server 确实尊重[u]r 该默认值,如以下测试(使用问题中提供的 UDTT)所示:
DECLARE @Test dbo.MyObject_TableType;
INSERT INTO @Test (Name) VALUES ('ObjectOne');
SELECT * FROM @Test;
Run Code Online (Sandbox Code Playgroud)
返回:
Name Index
ObjectOne <--- empty string, not NULL
Run Code Online (Sandbox Code Playgroud)
您得到NULL
而不是空字符串的原因是因为这是您要求应用程序代码提供的内容。您甚至(当然是在不知不觉中;-)在陈述(强调)时确定问题的根本原因:
我构建了一个
DataTable
并用一个填充它DataRow
。我将该表作为参数提供给存储过程:
ADataTable
是内存中实际表的表示,而不是一个或多个INSERT
语句。因此,当您创建该单行时,该Index
列不能是未定义的:它要么是NULL
某个值,要么是某个值。当您将其DataTable
作为 TVP传递到 SQL Server 时,您传递的是等效于表的内容。
是的,您可能可以通过DataColumn
在DataTable
. 但我同意这样做不是最好的方法。
我的偏好是永远不要使用DataTable
s ,除非您已经碰巧有一个,无论是否使用 TVP。我想不出有什么理由仅仅为了将数据传递到 TVP 而创建一个。除了这个特殊问题,它还会复制您放入其中的任何集合,只是为了作为 TVP 的临时传输(这会浪费内存和复制该数据所需的时间,即使它们中的任何一个都不多) .
相反,创建一个方法,该方法将获取您已有的任何集合,并简单地将其流式传输到 TVP 中,一次一个项目/一行。您可以通过IEnumerable<SqlDataRecord>
从此方法返回来完成此操作,然后将此方法指定为表示 TVP的Value
属性SqlParameter
。
将集合流式传输到 TVP 的方法:
private static IEnumerable<SqlDataRecord> SendRows(List<DbObject> DbObjects)
{
SqlMetaData[] _TvpSchema = new SqlMetaData[]
{
new SqlMetaData("Name", SqlDbType.VarChar, SqlMetaData.Max,
true, false, SortOrder.Unspecified, 1),
new SqlMetaData("Index", SqlDbType.VarChar, SqlMetaData.Max,
true, false, SortOrder.Unspecified, 2)
};
SqlDataRecord _DataRecord = new SqlDataRecord(_TvpSchema);
int _NumRows = DbObjects.Count;
// read a row, send a row.
// "for" should be faster than "foreach", right?
for (int _Index = 0; _Index < _NumRows; _Index++)
{
_DataRecord.SetString(0, DbObjects[_Index].Name);
_DataRecord.SetString(1, DbObjects[_Index].Index);
yield return _DataRecord;
}
}
Run Code Online (Sandbox Code Playgroud)
使用方法:
private struct DbObject
{
public string Name;
public string Index;
}
List<DbObject> _DbObjects = new List<DbObject>();
// Add one row that is missing an Index
_DbObjects.Add(new DbObject() { Name = "ObjectOne" });
// Make a parameter for the table
SqlParameter _ParamMyObjects = new SqlParameter("MyObjects", SqlDbType.Structured)
{
TypeName = "MyObject_TableType", // not required for CommandType.StoredProcedure
Value = SendRows(_DbObjects)
};
command.Parameters.Add(_ParamMyObjects);
command.ExecuteNonQuery();
Run Code Online (Sandbox Code Playgroud)
获取 UDTT 上定义的默认值Index
以使用空字符串填充列的技巧是我上面使用的SqlMetaData 构造函数的特定重载。它有一个布尔参数useServerDefault
。在传递true
导致列无法传递到INSERT
在声明中如果没有“设置” SqlDataRecord
。或者,至少这就是我一直让IDENTITY
UDTT 中的列工作的方式(从未在实际的 中尝试过DEFAULT
,但应该是相同的行为)。
PS为什么你VARCHAR(MAX)
在UDTT中使用作为这两列的数据类型?如果任一个或两个,这些列中必须包含SQL Server的标识符(即表,视图,存储过程,索引的名字,等等),那么这些都是sysname
它的别名NVARCHAR(128)
。所以你真的需要至少使用NVARCHAR
来避免潜在的数据丢失/难以追踪的错误。