首页 文章

如何在从字符串转换为另一种数据类型时解决SQL Server和.NET之间的隐式转换差异

提问于
浏览
1

背景

我们有一个VB.NET应用程序,可以将文本文件加载到数据库表中 . 文本文件可以是各种格式(平面文件(固定列宽),字符分隔)并且可以具有复杂结构(例如,可变数量的列) . 正在导入的文件的格式和结构是预先知道的 .

在开始文件导入之前,应用程序会加载"file format/structure"配置设置 . 然后,应用程序读取文件并构建一个SQL字符串( INSERT( [col list] ) VALUES( 'value1', 'value2', .. ), .. ),然后将其发送到目标表 .
Update: 因为所有数据都以文本形式出现(来自文本文件),所以应用程序还将其作为文本插入到数据库中,让数据库执行所有隐式转换工作 . 如上所述,基于给定类型的文件的配置设置,存在一些格式化(例如,美国风格06-05-2016将被转换为2016-06-05) .

目标表列可以具有非VARCHAR数据类型,例如 INTDATETIMEDECIMAL( x, y ) 等因此,插入字符串数据会导致隐式VARCHAR->其他数据类型转换 .
值得注意的是,SQL Server能够将空字符串转换为某些类型的默认值(最值得注意的例子是'' -> ' 1900-01-01 ' are ' ' -> ' 0')

这个应用程序是一个意大利面条的怪物,在我做出改变之前我不得不解开 . 作为这个“解开过程”的一部分,我希望用SqlBulkCopy替换SQL字符串构建(使用DataTable作为缓存对象) .

问题

在将数据发送到SQL Server之前,SqlBulkCopy似乎首先将字符串转换为CLR类型 . CLR类型对隐式字符串到非字符串类型转换具有不同的规则,因此我的数据无法按原样导入(即使使用上面的SQL String构建方法导入也很好) .

有办法解决这个问题吗?

到目前为止我发现:这个问题提到可能需要实现自定义的“清理”功能 . SqlBulkCopy - The given value of type String from the data source cannot be converted to type money of the specified target column
IMO自定义"clean up" / coversion函数解决方案复制SQL Server的行为是"akward" .

我也尝试过使用DataTable列类型,但无济于事:

SQL表声明如下:

CREATE TABLE LoadTable( IntVal INT NULL, BitVal BIT NULL, DateVal DATETIME NULL, DecVal DECIMAL( 12, 3 ) NULL )

.NET代码:

'DTColumns(1) = New System.Data.DataColumn("BitVal", GetType(SqlTypes.SqlString))
'DTColumns(1) = New System.Data.DataColumn("BitVal", GetType(System.String))
DTColumns(1) = New System.Data.DataColumn("BitVal")
CacheRow.Item("BitVal") = "1"
BulkCopier.WriteToServer(_DataCache)

但总是得到Exceptions告诉我无法将值转换为布尔值:

数据源中String类型的给定值无法转换为指定目标列的类型位 . 无法将参数值从String转换为布尔值 .

另一个:

无法将参数值从SqlString转换为布尔值 .

隐式默认值也不起作用( NOTDEFAULT CONSTRAINT 混淆):

DTColumns(2) = New System.Data.DataColumn("DateVal", GetType(SqlTypes.SqlString))
CacheRow.Item("DateVal") = ""

另一方面,这适用于SQL:

INSERT LoadTable( IntVal, BitVal, DateVal )
SELECT '', '', ''
/* Result
IntVal  BitVal  DateVal
0       0       1900-01-01 00:00:00.000
*/

它清楚地证明了SqlBulkCopy在内部将传入数据类型转换为CLR等效目标类型 .

奖金问题:谁知道为什么SqlBulkCopy必须将所有内容转换为CLR类型?鉴于它正在将数据写入非CLR目标,似乎很奇怪 .

Clarification: 我完全理解必须转换(隐式或显式)数据类型 . 我正在寻找一种解决方案,以与SQL Server相同的方式将字符串转换为其他类型 . 我的问题是.NET做的不同,所以简单地使用Integer.Parse(或等价物)会在某些情况下失败,在SQL中相同的转换成功 . 我提供了一些例子,其中这样的转换失败但是我知道的更多(我还有更多) .

1 回答

  • 1

    不,没有(直接)方法解决这个问题并保留批量副本 . SqlBulkCopy 包含分析目标表并执行自己的转换的代码,如果根据其规则这些不合适,它会抱怨 . 没有标志或设置会说服它不要这样做 . 资料来源:the source .

    至于你的奖金问题,为什么需要这样做呢?嗯,事实并非如此 . 它可以只发送数据,确切地说是您定义的类型,并让SQL Server弄明白 . 它可以简单地发送一个 SqlInt32 作为 INT ,然后让SQL Server弄清楚你实际上想要 2 转换为 BIT1 . 该协议允许这样做,但 SqlBulkCopy 坚持在将数据传递到服务器之前将其连接起来 . 这不是完全不合理的,因为替代方案是忽略目标类型并使用保守和过于宽泛的类型在发送数据之前转换为所有字符串(所有字符串都需要 NVARCHAR(MAX) ,所有 DateTime 作为 DATETIME2 ,只是在目标的关闭时间列需要满足整个精度),这会降低性能,或者要求程序员指定SQL类型应该是什么样的,确切的长度和规模,这是单调乏味,容易出错,并且会产生显而易见的问题"why doesn't it just ask the server"?而且,那是's what it does now. To do what you want, it would need a separate mode where it checks if you'"really sure"关于使用与目标不同的确切数据类型,以防您希望将其留给SQL Server为您进行转换 . 我猜这个功能只是没有削减 .

    大多数情况下,事先将您自己的转换转换为确切的数据类型是一件好事 . 假设"SQL Server knows best"是一个糟糕的做法,因为SQL Server应用的转换只是部分记录,依赖于语言,并且通常是彻头彻尾的怪异 . 你引用的例子是 '' 被转换为 1900-01-01 ,这是一个反对依赖它们的明确论据 . 而不是假设您的应用程序肯定需要bug-for-bug兼容性,最好回顾一下是否可以切换到正确类型化的数据模型,只有少数例外可能隐式依赖于明确实现 . 也许您的应用程序依靠SQL Server将 True 转换为 BIT1 . 也许它甚至依靠它将 2 转换为 1 . 也许它甚至需要从 -,. 转换为 MONEY0.00 . 也许,但它可能不会't, and if it does, it probably shouldn' .

    在这里讲道 . 如果出于某种不正当理由你还需要这个怎么办?

    首先,你可以简单地不使用 SqlBulkCopy . 生成一个巨大的 INSERT 语句是非常低效的(T-SQL解析器不喜欢大语句),但你可以发出很多单独的(参数化的!) INSERT 语句并将它们包装在一个事务中 . 这比批量复制效率低,但仍然比事务外的单个插入或大量语句更有效,并且可能足以满足您的需要 .

    您可以使用所需的列创建一个临时表(但不是 #temporary table ),并将所有列设为 (N)VARCHAR(MAX) . 然后你可以 SqlBulkCopy 进入,然后让SQL Server从这个表中进行转换 . 即使您没有批量记录,也可以使用它 . 简而言之,这是非常低效的 . 尽管采用批量复制步骤,它可能比直接进行插入效率低 .

    您可以让应用程序使用所需的列和字符串格式的所有值吐出一个平面文件,然后以编程方式调用bcp . 当然这需要安装 bcp (它可以与SQL Server分开安装,作为Command Line Utilities包的一部分),你可能会失去编写中间文件的一大块性能,但它可能仍然会产生巨大的 INSERT 语句或中间文件表 . 只要您在输入中指定所有内容都是 (N)VARCHARbcp 将尽职地将转换保留到SQL Server .

    最后但并非最不重要的是,为了完整性,你可以想象编写自己的代码来包装native bulk copy functions,它没有这个限制 . 但这将是相当多的工作,所有互操作也不利于性能 . SqlBulkCopy 不是't wrap around these functions -- it'是TDS的批量加载操作的从头开始实现,也称为神秘的 INSERT BULK 语句 . 我建议你自己编写更少的实现,但是现在.NET是开源的,你可以(比方说)fork .NET Core并拥有自己的 SqlRawBulkCopy 版本 . 随着二十一点和妓女 .

相关问题