簡體   English   中英

SQLException:字符串或二進制數據將被截斷

[英]SQLException : String or binary data would be truncated

我有一個 C# 代碼,它批量執行大量插入語句。 執行這些語句時,出現“字符串或二進制數據將被截斷”錯誤和事務 roledback。

為了找出是哪個插入語句導致的,我需要在 SQLServer 中一個一個地插入,直到我遇到錯誤。

有沒有聰明的方法來找出哪個語句和哪個字段使用異常處理導致這個問題? (SQLException)

通常,沒有辦法確定哪個特定語句導致了錯誤。 如果您正在運行多個,您可以查看分析器並查看最后完成的語句,看看之后的語句可能是什么,盡管我不知道這種方法對您是否可行。

無論如何,您的參數變量之一(以及其中的數據)對於它嘗試存儲數據的字段來說太大了。根據列大小檢查您的參數大小,並且相關字段應該很快就顯而易見了。

當 SQL Server 列的數據類型的長度小於輸入到條目表單中的數據長度時,就會發生這種類型的錯誤。

這種類型的錯誤通常發生在您必須輸入的字符或值多於您在數據庫表中指定的字符或值時,例如在這種情況下:您指定 transaction_status varchar(10) 但您實際上嘗試存儲包含 19 個字符的 _transaction_status。 這就是您在此代碼中遇到此類錯誤的原因

通常是您插入的值大於允許的最大值。 例如,數據列最多只能容納 200 個字符,但您插入的是 201 個字符的字符串

  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 的表,您可以將它與目標表結構進行比較,即您可以比較兩個表上的列以查看它們的不同之處。 這是一種解決方法,但它是我找到的最快的方法。

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

這取決於您如何進行插入調用。 全部作為一個調用,還是作為事務中的單個調用? 如果單個調用,則是(當您遍歷調用時,捕獲失敗的調用)。 如果一個大電話,那么沒有。 SQL 正在處理整個語句,因此它不受代碼控制。

我創建了一種通過以下方式查找違規字段的簡單方法:

  1. 獲取我們試圖進行此插入/更新的表中所有列的列寬。 (我直接從數據庫中獲取此信息。)
  2. 將列寬與我們嘗試插入/更新的值的寬度進行比較。

假設/限制:

  1. 數據庫中表的列名與 C# 實體字段匹配。 例如:如果您在數據庫中有這樣的列:

    您需要讓您的實體具有相同的列名:

     public class SomeTable { // Other fields public string SourceData { get; set; } }
  2. 您一次要插入/更新 1 個實體。 在下面的演示代碼中會更清楚。 (如果您正在進行批量插入/更新,您可能想要修改它或使用其他解決方案。)

步驟1:

直接從數據庫中獲取所有列的列寬:

// For this, I took help from Microsoft docs website:
// https://learn.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。

也可能是因為您試圖將null值放回數據庫中。 因此,您的一項交易中可能包含空值。

使用 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]

在我們自己的例子中,我增加了 sql 表允許的字符或字段大小,它小於從前端發布的總字符數。 因此,解決了這個問題。

這里的大多數答案都是做明顯的檢查,即數據庫中定義的列的長度不小於您嘗試傳遞給它的數據。

好幾次我都被 SQL Management Studio 咬到了,做一個快速的:

sp_help 'mytable'

並困惑幾分鍾,直到我意識到有問題的列是nvarchar ,這意味着 sp_help 報告的長度實際上是支持的實際長度的兩倍,因為它是雙字節(unicode)數據類型。

即,如果 sp_help 報告 nvarchar 長度為 40,則最多可以存儲 20 個字符。

簡單地使用這個: MessageBox.Show(cmd4.CommandText.ToString()); 在 c#.net 中,這將顯示主查詢,復制它並在數據庫中運行。

查看這個要點。 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;
    }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM