【问题标题】:Check if SqlParameter value is in range of it's target SQL column检查 SqlParameter 值是否在其目标 SQL 列的范围内
【发布时间】:2014-12-10 20:33:13
【问题描述】:

tldnr;

在尝试将SqlParameter 值放入数据库之前,我应该如何检查它们的界限?

加长版:

所以,我有这些动态生成的 SQL 语句,我在其中传递了一堆 SqlParameters。

我们声明SqlParameters的方式就是

new SqlParameter("fieldName", value)

我们让运行时找出 dbtype 是什么。

也就是说,有时更新/插入语句会失败,我们想通过边界检查来确定哪个字段太大(因为我们的服务器只告诉我们字段更新失败,而不是哪个)。也就是说,我们不能在只允许 1 位的列中放入两位数 (say a decimal(1,0) for example.)

我们在内存中有列的模式(information_schema.columns ftw),所以我们可以尝试对 SqlParameter 值进行边界检查,但是由于该值是一个对象,甚至不一定是数字类型,所以应该怎么做我检查一个值是否在范围内?

还是我把问题弄得太难了,而是应该在构造 SqlParameters 时提供精度和比例?或者更好的是,我们是否应该使用反映数据库中列的类型?

更新:

设置精度/比例似乎没有任何后果,如in this code

decimal d= 10.0M*(decimal)Math.Pow(10, 6);
SqlParameter p = new SqlParameter("someparam", SqlDbType.Decimal);
                    p.Precision = (byte)1;
                    p.Scale = (byte)0;
                    p.Value = d;

Console.WriteLine(p.SqlValue); // doesn't throw an error, i would think the sql value would be at most 10, not 10 million.

【问题讨论】:

  • 我看到您进行了适当的测试,因为在没有 delcared DbType 的情况下创建 SqlParameter 确实不是一个好主意。但是,您没有通过 SqlCommand 执行该操作。我敢打赌,如果您执行了 Execute,将会出现截断,无论是静默还是导致异常。
  • @srutzky:是的,很抱歉,如果我的问题不是很清楚。我的更新语句会转到数据库然后它会失败,但它不会告诉我是哪个参数导致了失败。我的目标只是确定导致失败的参数,我想知道最好的方法是什么。
  • 对。我想我得到了那个部分。我的意思是,在您的测试中,您说向 SqlParameter 添加无效值并没有出错,因为您通过WriteLine 看到它,我特别问您是否更改了执行 ExecuteNonQuery/Reader 的代码以包含精度和规模。在至少调用 SqlCommand.Execute* 方法之前,我不希望 SqlParam 进行验证。但我也在尝试另一种方法来看看它是否有效。
  • 我刚刚意识到您是正确的,使用参数的查询的实际执行受参数指定方式的影响。如果指定精度和小数位数,它将抛出参数异常而不是 sqlexception,但是识别有问题的参数仍然是相同的问题。有趣的是我还发现5.0M和5M是不一样的,即使Sql不报错,tdsparser也会报错。

标签: sql-server c#-3.0 sqlparameter


【解决方案1】:

似乎SqlParameter 在设置Value 属性时没有验证。而DataColumn 不允许指定PrecisionScale,所以不是非常有用。但是,一种方法:

  1. 使用您已有的架构信息集合,根据架构集合的大小动态创建 SqlMetaData 数组,并使用列名和大小数据填充它:

    SqlMetaData[] _TempColumns = new SqlMetaData[_SchemaCollection.Count];
    
    loop-of-some-sort
    {
       switch (_SchemaCollection.DataType)
       {
          case "decimal":
             _TempColumns[_Index] = new SqlMetaData(
                         _SchemaCollection.Name,
                         SqlDbType.Decimal,
                         (byte)_SchemaCollection.Precision, 
                         (byte)_SchemaCollection.Scale
                      );
             break;
           case "others...."
       }
    }
    
  2. 使用步骤 1 中的 SqlMetaData[] 创建一个新的 SqlDataRecord

    SqlDataRecord _TempRow = new SqlDataRecord(_TempColumns);
    
  3. 循环通过_TempRow 为每个位置调用适当的Set 方法,在try/catch 中:

    string _DataAintRight;
    
    try
    {
       _TempRow.SetDecimal(_Index, _SchemaCollection.Value);
    }
    catch
    {
      _DataAintRight = _SchemaCollection.Name;
      break;
    }
    

注意事项:

  • 这只会进行与将参数传递给 proc 相同的验证。意思是,它会默默地截断太长的值,例如小数点右边的数字太多,以及超过最大大小的字符串。

  • 固定长度的数字类型应该已经在其等效的 .Net 类型中(即 Int16 变量或属性中的 SMALLINT 值),因此已经预先验证。如果确实如此,那么测试它们并没有额外的好处。但是如果它们当前驻留在更通用的容器中(更大的 Int 类型甚至是字符串),那么在这里进行测试是合适的。

  • 如果您需要知道字符串将被截断,则必须单独进行测试。至少不像SqlMetaData那样,而是在循环和切换中,在那种情况下只测试字符串的长度。

  • 不管这些测试内容如何,​​最好不要通过让 .Net 通过new SqlParameter("fieldName", value) 甚至_Command.Parameters.AddWithValue() 猜测类型来创建参数。因此,关于“在构造 SqlParameters 时是否应该提供精度和比例”的问题,绝对可以。


另一个选项(我可以在明天有时间更新它时详细说明)是验证所有内容,就好像没有应该反映真实数据库/提供程序数据类型的内置容器一样。因此,有两个主要考虑因素将推动实施:

  • 源数据当前是强类型还是全部序列化为字符串?

  • 是否需要知道值是否会被截断(特别是在值会被静默截断而不导致错误的情况下,这可能会导致意外行为)。这里的问题是,将数据插入超过指定最大长度的表中的字段将导致string or binary data will be truncated 错误。但是当将数据传递给参数(即不直接传递给表)时,该数据将被截断而不会导致错误。有时这没问题,但有时这可能会导致输入参数指定不正确(或输入参数正确但随后字段被扩展并且参数从未更新以匹配新长度)并且可能会切断末端直到客户报告“报告中的某些内容看起来不太正确,并且哦,顺便说一句,这种情况现在可能已经断断续续地发生了四五个月,但我真的忙,老是忘了提,可能已经九个月了,我不记得了,但是,是的,有问题”。我的意思是,我们多久通过传入每个参数的最大值来测试我们的代码,以确保系统可以处理它?

如果源数据属于适当的 .Net 类型:

有几个不需要检查,因为固定长度的数字类型在 .Net 和 SQL Server 之间是相同的。仅通过存在于各自的 .Net 类型中即可进行预验证的是:

  • bool -> 位
  • 字节 -> TINYINT
  • Int16 -> SMALLINT
  • Int32 -> INT
  • Int64 -> BIGINT
  • 双 -> 浮点数
  • 单身 -> 真实
  • Guid -> 唯一标识符

有一些只需要检查截断(如果这是一个问题),因为它们的值应该始终与它们的 SQL Server 对应值处于同一范围内。请记住,这里我们谈论的是当值传递给较小比例的参数时的严格截断,但是在直接插入具有较小比例的列时实际上会四舍五入(嗯,在 5)规模。例如,在传递给定义为 DATETIME2(2) 的参数时,发送精确到小数点后 5 位的 DateTime 值将截断最右边的 3 个数字。

有一些需要检查以确保它们没有超出 SQL Server 数据类型的有效范围,因为超出范围会导致异常。他们也可能需要检查截断(如果这是一个问题)。请记住,这里我们谈论的是当值传递给较小比例的参数时的严格截断,但是在直接插入具有较小比例的列时实际上会四舍五入(嗯,在 5)规模。例如,在传递给定义为 SMALLDATETIME 的参数时,发送 DateTime 值将丢失所有秒和小数秒。

  • 日期时间 -> DATETIME : 1753-01-01 到 9999-12-31, 00:00:00.000 到 23:59:59.997
  • DateTime -> SMALLDATETIME:1900-01-01 到 2079-06-06、00:00 到 23:59(无秒)
  • 十进制 -> MONEY : -922,337,203,685,477.5808 到 922,337,203,685,477.5807
  • 十进制 -> SMALLMONEY : -214,748.3648 到 214,748.3647
  • 十进制 -> 十进制:范围 = -9[digits = (Precision - Scale)] 到 9[digits = (Precision - Scale)],截断取决于定义的 Scale

以下字符串类型在传递给最大长度小于其值长度的参数时会静默截断,但如果直接插入最大长度小于它们的值的长度:

  • 字节[] -> 二进制
  • byte[] -> VARBINARY
  • 字符串 -> 字符
  • 字符串 -> VARCHAR
  • 字符串 -> NCHAR
  • 字符串 -> NVARCHAR

以下内容很棘手,因为真正的验证需要了解更多在数据库中创建它时使用的选项。

  • string -> XML -- 默认情况下,XML 字段是无类型的,因此对于“正确”的 XML 语法非常宽松。但是,可以通过将XML Schema Collection(1 个或多个 XSD)与验证字段相关联来更改该行为(另请参见:Compare Typed XML to Untyped XML)。因此,对 XML 字段的真正验证将包括获取该信息(如果存在),如果存在,则检查这些 XSD。至少它应该是格式良好的 XML(即 '<b>' 会失败,但 '<b />' 会成功)。

对于上述类型,可以忽略预先验证的类型。其余的可以在switch(DestinationDataType) 结构中进行测试:

  • 需要对范围进行验证的类型可以按如下方式完成

    case "smalldatetime":
      if ((_Value < range_min) || (_Value > range_max))
      {
        _ThisValueSucks = true;
      }
      break;
    
  • Numeric/DateTime 截断,如果被测试,可能最好执行 ToString() 并使用 IndexOf(".") 对于大多数,或 IndexOf(":00.0") 对于 DATE 和 SMALLDATETIME,找到数字的位数小数点右边(或 SMALLDATETIME 从“秒”开始)

  • 如果要测试字符串截断,只需测试长度即可。

  • 十进制范围可以用数字来测试:

    if ((Math.Floor(_Value) < -999) || (Math.Floor(_Value) > 999))
    

    或:

    if (Math.Abs(Math.Floor(_Value)).ToString().Length <= DataTypeMaxSize)
    
  • Xml

    • 因为 XmlDocument 在与 XML 字段相关的潜在 XSD 验证之外进行了预验证
    • 因为 String 可以首先用于创建 XmlDocument,它只留下与 XML 字段关联的任何潜在 XSD 验证

如果源数据都是字符串:

然后他们都需要被验证。对于这些,您将首先使用与每种类型关联的TryParse 方法。然后,您可以为每种类型应用上述规则。

【讨论】:

  • 不幸的是,尽管看起来工作量很大,但对于小数点来说,这对我不起作用。 dotnetfiddle.net/WdcEq1 。我看到您的第二个项目符号提到类型已经与列类型匹配,但是对于小数来说似乎没有这种类型。
  • @user420667 好吧,在我的盒子上工作;-)。这很奇怪。我修补了 dotnetfiddle 但不能让它失败,而我不能让它在我的机器上不失败。我在 VS 2012 中使用 .Net 4.5。它是一个 SQLCLR 过程,但这无关紧要,因为错误是:SqlContext.Pipe.Send 发生在 ...SqlDataRecord.SetDecimal(Int32 ordinal, Decimal value) 中。我将发布另一个想法。哦,我的第二个项目符号涉及固定长度的数字类型:Int、Int64、Int16、字节。
  • 呵呵,真奇怪。当我使用 .NET 3.5 和 VS 2008 将其作为单元测试运行时,它也对我不起作用,作为从 NUnit 可执行文件调用的库类型项目。
  • @user420667 这很奇怪。我不认为在 SQL Server 内部运行的环境会改变那个级别的东西。除非有不同的编译设置/标志/警告级别/等?无论哪种方式,我都用其他想法更新了我的答案。
猜你喜欢
  • 1970-01-01
  • 2021-08-26
  • 1970-01-01
  • 2023-03-16
  • 1970-01-01
  • 2019-03-23
  • 1970-01-01
  • 2023-04-03
  • 2017-07-13
相关资源
最近更新 更多