【问题标题】:SQLException : String or binary data would be truncatedSQLException : 字符串或二进制数据将被截断
【发布时间】:2010-10-21 05:08:46
【问题描述】:

我有一个 C# 代码,它可以批量执行大量插入语句。在执行这些语句时,我得到“字符串或二进制数据将被截断”错误和事务roledback。

要找出是哪条insert语句导致的,我需要在SQLServer中一一插入,直到遇到错误。

有没有聪明的方法来找出哪个语句和哪个字段使用异常处理导致了这个问题? (SqlException)

【问题讨论】:

  • 你会认为 .Net 会为这样的错误添加一个内部异常,其中包含有问题的表和列信息。唉,没有。 SMH。
  • @pwrgreg007 我同意,因为我没有找到任何答案,可以将数据库表中的列宽直接与我们尝试插入的值的宽度进行比较,所以我继续创建方法来做只是。看看下面:stackoverflow.com/a/71329954/8644294

标签: c# sql sql-server


【解决方案1】:

一般来说,没有办法确定是哪个特定语句导致了错误。如果您正在运行多个,您可以观察分析器并查看最后完成的语句,然后查看之后的语句可能是什么,尽管我不知道这种方法是否适合您。

无论如何,您的参数变量之一(以及其中的数据)对于它试图存储数据的字段来说太大了。根据列大小检查您的参数大小,并且有问题的字段应该是显而易见的很快。

【讨论】:

  • 这个是可能的,看我的answer below
  • 在我的例子中,要存储的参数值对于数据库表字段来说太大了
  • @profMamba 答案将有助于解决问题的根本原因。我能够检测到一个我没有意识到正在更新的列,并注意到它的更新不正确。
【解决方案2】:

当 SQL Server 列的数据类型的长度小于输入到输入表单中的数据长度时,就会发生这种类型的错误。

【讨论】:

    【解决方案3】:

    这种类型的错误通常发生在您必须放置的字符或值超过您在数据库表中指定的数量时,例如在这种情况下:您指定 transaction_status varchar(10) 但你实际上试图存储 _transaction_status 其中包含 19 个字符。这就是您在此代码中遇到此类错误的原因

    【讨论】:

      【解决方案4】:

      通常是您插入的值大于允许的最大值。例如,数据列最多只能容纳 200 个字符,但您插入的是 201 个字符的字符串

      【讨论】:

        【解决方案5】:
        BEGIN TRY
            INSERT INTO YourTable (col1, col2) VALUES (@val1, @val2)
        END TRY
        BEGIN CATCH
            --print or insert into error log or return param or etc...
            PRINT '@val1='+ISNULL(CONVERT(varchar,@val1),'')
            PRINT '@val2='+ISNULL(CONVERT(varchar,@val2),'')
        END CATCH
        

        【讨论】:

        • 这假设他正在使用比 MS SQL 2000 更强大的东西 ;)
        • @Nick DeVore,这个问题很模糊,没有解释上下文,还有很多猜测。它确实要求“异常处理”。
        • 是的,但他说的是 SqlException,我认为是 System.Data.SqlClient.SqlException ;)
        • @Adam Robinson,但如果 SqlException 是他想要的,他本可以自己查找语法,我认为提出这个问题是因为他们正在寻找解决问题的想法。
        【解决方案6】:

        For SQL 2016 SP2 or higher follow this link

        对于旧版本的 SQL,请执行以下操作:

        1. 获取导致问题的查询(如果没有源,也可以使用 SQL Profiler)
        2. 删除所有 WHERE 子句和其他不重要的部分,直到您基本上只剩下 SELECT 和 FROM 部分
        3. 添加 WHERE 0 = 1(这将只选择表结构)
        4. 在 FROM 子句之前添加 INTO [MyTempTable]

        你应该得到类似的东西

        SELECT
         Col1, Col2, ..., [ColN]
        INTO [MyTempTable]
        FROM
          [Tables etc.]
        WHERE 0 = 1
        

        这将在您的数据库中创建一个名为 MyTempTable 的表,您可以将其与目标表结构进行比较,即您可以比较两个表上的列以查看它们的不同之处。这是一种解决方法,但它是我找到的最快的方法。

        【讨论】:

        • 如何使用 C# 或 Sql Server 以编程方式与目标表结构进行比较?
        【解决方案7】:

        这取决于您如何进行插入调用。全部作为一个调用,还是作为交易中的单独调用?如果单独调用,则可以(当您遍历调用时,捕获失败的调用)。如果一个大电话,那么没有。 SQL 正在处理整个语句,因此它不受代码控制。

        【讨论】:

          【解决方案8】:

          这也可能是因为您试图将null 值放回数据库中。因此,您的一项交易可能包含空值。

          【讨论】:

            【解决方案9】:

            使用 Linq To SQL 我通过记录上下文进行调试,例如。 Context.Log = Console.Out 然后扫描SQL检查是否有明显错误,有两个:

            -- @p46: Input Char (Size = -1; Prec = 0; Scale = 0) [some long text value1]
            -- @p8: Input Char (Size = -1; Prec = 0; Scale = 0) [some long text value2]
            

            我通过根据值扫描表架构找到的最后一个字段是 nvarchar(20) 但值是 22 个字符

            -- @p41: Input NVarChar (Size = 4000; Prec = 0; Scale = 0) [1234567890123456789012]

            【讨论】:

              【解决方案10】:

              在我们自己的情况下,我增加了 sql 表允许的字符或字段大小,该大小小于从前端发布的总字符数。这样就解决了问题。

              【讨论】:

                【解决方案11】:

                这里的大多数答案都是为了进行明显的检查,即数据库中定义的列的长度不小于您尝试传递给它的数据。

                我有好几次因为去 SQL Management Studio 做一个快速的操作而被咬了:

                sp_help 'mytable'
                

                困惑了几分钟,直到我意识到有问题的列是 nvarchar,这意味着 sp_help 报告的长度实际上是支持的实际长度的两倍,因为它是双字节(unicode)数据类型。

                即如果 sp_help 报告 nvarchar 长度为 40,则最多可以存储 20 个字符。

                【讨论】:

                  【解决方案12】:

                  简单地使用这个: MessageBox.Show(cmd4.CommandText.ToString()); 在 c#.net 中,这将显示主要查询,复制它并在数据库中运行。

                  【讨论】:

                  • 这无助于找出脚本中的哪个特定语句失败。
                  • 当您复制查询并在数据库中运行时很容易识别
                  【解决方案13】:

                  查看此要点。 https://gist.github.com/mrameezraja/9f15ad624e2cba8ac24066cdf271453b.

                  public Dictionary<string, string> GetEvilFields(string tableName, object instance)
                      {
                          Dictionary<string, string> result = new Dictionary<string, string>();
                  
                          var tableType = this.Model.GetEntityTypes().First(c => c.GetTableName().Contains(tableName));
                  
                          if (tableType != null)
                          {
                             int i = 0;
                  
                             foreach (var property in tableType.GetProperties())
                             {
                                 var maxlength = property.GetMaxLength();
                                 var prop = instance.GetType().GetProperties().FirstOrDefault(_ => _.Name == property.Name);
                  
                                 if (prop != null)
                                 {
                                     var length = prop.GetValue(instance)?.ToString()?.Length;
                  
                                     if (length > maxlength)
                                     {
                                          result.Add($"{i}.Evil.Property", prop.Name);
                                          result.Add($"{i}.Evil.Value", prop.GetValue(instance)?.ToString());
                                          result.Add($"{i}.Evil.Value.Length", length?.ToString());
                                          result.Add($"{i}.Evil.Db.MaxLength", maxlength?.ToString());
                                          i++;
                                      }
                                 }
                              }
                         }
                  
                         return result;
                      }
                  
                  

                  【讨论】:

                    【解决方案14】:

                    我创建了以下方法:

                    1. 获取我们尝试进行此插入/更新的表的所有列的列宽。 (我直接从数据库中获取此信息。)
                    2. 将列宽与我们尝试插入/更新的值的宽度进行比较。

                    第 1 步:

                    直接从数据库中获取所有列的列宽

                    // I took HUGE help from this Microsoft docs website: - AshishK
                    // https://docs.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlconnection.getschema?view=netframework-4.7.2#System_Data_SqlClient_SqlConnection_GetSchema_System_String_System_String___
                    private static Dictionary<string, int> GetColumnSizesOfTableFromDatabase(string tableName, string connectionString)
                    {
                        var columnSizes = new Dictionary<string, int>();
                                
                        using (var connection = new SqlConnection(connectionString))
                        {
                            // Connect to the database then retrieve the schema information.  
                            connection.Open();
                    
                            // You can specify the Catalog, Schema, Table Name, Column Name to get the specified column(s).
                            // You can use four restrictions for Column, so you should create a 4 members array.
                            String[] columnRestrictions = new String[4];
                    
                            // For the array, 0-member represents Catalog; 1-member represents Schema;
                            // 2-member represents Table Name; 3-member represents Column Name.
                            // Now we specify the Table_Name and Column_Name of the columns what we want to get schema information.
                            columnRestrictions[2] = tableName;
                    
                            DataTable allColumnsSchemaTable = connection.GetSchema("Columns", columnRestrictions);
                    
                            foreach (DataRow row in allColumnsSchemaTable.Rows)
                            {
                                var columnName = row.Field<string>("COLUMN_NAME");
                                var dataType = row.Field<string>("DATA_TYPE");
                                var characterMaxLength = row.Field<int?>("CHARACTER_MAXIMUM_LENGTH");
                    
                                // I'm only capturing columns whose Datatype is "varchar" or "char", i.e. their CHARACTER_MAXIMUM_LENGTH won't be null.
                                if(characterMaxLength != null)
                                {
                                    columnSizes.Add(columnName, characterMaxLength.Value);
                                }
                            }
                    
                            connection.Close();
                        }
                    
                        return columnSizes;
                    }
                    

                    第 2 步:

                    将列宽与我们尝试插入/更新的值的宽度进行比较:

                    public static Dictionary<string, string> FindLongBinaryOrStringFields<T>(T entity, string connectionString)
                    {
                        var tableName = typeof(T).Name;
                        Dictionary<string, string> longFields = new Dictionary<string, string>();
                        var objectProperties = GetProperties(entity);
                        //var fieldNames = objectProperties.Select(p => p.Name).ToList();
                    
                        var actualDatabaseColumnSizes = GetColumnSizesOfTableFromDatabase(tableName, connectionString);
                                
                        foreach (var dbColumn in actualDatabaseColumnSizes)
                        {
                            var maxLengthOfThisColumn = dbColumn.Value;
                            var currentValueOfThisField = objectProperties.Where(f => f.Name == dbColumn.Key).First()?.GetValue(entity, null)?.ToString();
                    
                            if (!string.IsNullOrEmpty(currentValueOfThisField) && currentValueOfThisField.Length > maxLengthOfThisColumn)
                            {
                                longFields.Add(dbColumn.Key, $"'{dbColumn.Key}' column cannot take the value of '{currentValueOfThisField}' because the max length it can take is {maxLengthOfThisColumn}.");
                            }
                        }
                    
                        return longFields;
                    }
                    
                    public static List<PropertyInfo> GetProperties<T>(T entity)
                    {
                        //The DeclaredOnly flag makes sure you only get properties of the object, not from the classes it derives from.
                        var properties = entity.GetType()
                                                .GetProperties(System.Reflection.BindingFlags.Public
                                                | System.Reflection.BindingFlags.Instance
                                                | System.Reflection.BindingFlags.DeclaredOnly)
                                                .ToList();
                    
                        return properties;
                    }
                    

                    用法:

                    假设我们正在尝试插入在我们的应用中建模的 SomeTable 类的 someTableEntity,如下所示:

                    public class SomeTable
                    {
                        [Key]
                        public long TicketID { get; set; }
                        public string SourceData { get; set; }
                    }
                    

                    它在我们的SomeDbContext 里面,就像这样:

                    public class SomeDbContext : DbContext
                    {
                        public DbSet<SomeTable> SomeTables { get; set; }
                    }
                    

                    Db 中的此表具有 SourceData 字段为 varchar(16),如下所示:

                    现在我们将尝试在此字段中插入超过 16 个字符的值并捕获此信息:

                    public void SaveSomeTableEntity()
                    {
                        var connectionString = "server=SERVER_NAME;database=DB_NAME;User ID=SOME_ID;Password=SOME_PASSWORD;Connection Timeout=200";
                            
                        using (var context = new SomeDbContext(connectionString))
                        {
                            var someTableEntity = new SomeTable()
                            {
                                SourceData = "Blah-Blah-Blah-Blah-Blah-Blah"
                            };
                            
                            context.SomeTables.Add(someTableEntity);
                            
                            try
                            {
                                context.SaveChanges();
                            }
                            catch (Exception ex)
                            {
                                if (ex.GetBaseException().Message == "String or binary data would be truncated.\r\nThe statement has been terminated.")
                                {
                                    var badFieldsReport = "";
                                    List<string> badFields = new List<string>();
                                    
                                    // YOU GOT YOUR FIELDS RIGHT HERE:
                                    var longFields = FindLongBinaryOrStringFields(someTableEntity, connectionString);
                    
                                    foreach (var longField in longFields)
                                    {
                                        badFields.Add(longField.Key);
                                        badFieldsReport += longField.Value + "\n";
                                    }
                                }
                                else
                                    throw;
                            }
                        }
                    }
                    

                    badFieldsReport 将具有此值:

                    “SourceData”列不能取值 'Blah-Blah-Blah-Blah-Blah-Blah' 因为它可以采取的最大长度是 16.

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多