70

I have a C# code which does lot of insert statements in a batch. While executing these statements, I got "String or binary data would be truncated" error and transaction roledback.

To find out the which insert statement caused this, I need to insert one by one in the SQLServer until I hit the error.

Is there clever way to findout which statement and which field caused this issue using exception handling? (SqlException)

GEOCHET
  • 21,119
  • 15
  • 74
  • 98
  • 2
    You'd think .Net would add an inner exception for an error like this, with offending table & column information. Alas, no. SMH. – pwrgreg007 Nov 23 '20 at 23:31
  • @pwrgreg007 I agree and since I didn't find any answers that compared column width directly from the database table to the widths of values we're trying to insert, I went ahead and created methods to do just that. Take a look below: https://stackoverflow.com/a/71329954/8644294 – Ash K Mar 02 '22 at 22:44

14 Answers14

84

In general, there isn't a way to determine which particular statement caused the error. If you're running several, you could watch profiler and look at the last completed statement and see what the statement after that might be, though I have no idea if that approach is feasible for you.

In any event, one of your parameter variables (and the data inside it) is too large for the field it's trying to store data in. Check your parameter sizes against column sizes and the field(s) in question should be evident pretty quickly.

Adam Robinson
  • 182,639
  • 35
  • 285
  • 343
  • 1
    This *is possible*, see my [answer below](http://stackoverflow.com/questions/779082/sqlexception-string-or-binary-data-would-be-truncated/16533835#16533835) – profMamba Feb 28 '17 at 08:49
  • In my case, the parameter value to be stored was too large for the DB table field – Umar T. Mar 26 '18 at 10:39
  • [Now supported in > 2016 SP2](https://stackoverflow.com/a/52482685/185123) – spottedmahn Sep 10 '19 at 21:10
  • 1
    @profMamba answer will help root cause the issue. I was able to detect a column I didn't realise was being updated and noticed that it was being updated incorrectly. – jamcoupe Aug 03 '21 at 08:34
27

This type of error occurs when the datatype of the SQL Server column has a length which is less than the length of the data entered into the entry form.

Pete Carter
  • 2,691
  • 3
  • 23
  • 34
Jasmin Chauhan
  • 289
  • 3
  • 2
9

this type of error generally occurs when you have to put characters or values more than that you have specified in Database table like in that case: you specify transaction_status varchar(10) but you actually trying to store _transaction_status which contain 19 characters. that's why you faced this type of error in this code

jaideep
  • 1,631
  • 17
  • 19
5

Generally it is that you are inserting a value that is greater than the maximum allowed value. Ex, data column can only hold up to 200 characters, but you are inserting 201-character string

ismail baig
  • 861
  • 2
  • 11
  • 39
4

For SQL 2016 SP2 or higher follow this link

For older versions of SQL do this:

  1. Get the query that is causing the problems (you can also use SQL Profiler if you dont have the source)
  2. Remove all WHERE clauses and other unimportant parts until you are basically just left with the SELECT and FROM parts
  3. Add WHERE 0 = 1 (this will select only table structure)
  4. Add INTO [MyTempTable] just before the FROM clause

You should end up with something like

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

This will create a table called MyTempTable in your DB that you can compare to your target table structure i.e. you can compare the columns on both tables to see where they differ. It is a bit of a workaround but it is the quickest method I have found.

profMamba
  • 988
  • 1
  • 14
  • 29
4
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
KM.
  • 101,727
  • 34
  • 178
  • 212
  • This assumes he's using something greater than MS SQL 2000 though ;) – Nick DeVore Apr 22 '09 at 21:04
  • @Nick DeVore, the question is vague, doesn't explain the context, and leave a lot to guess at. it does ask for "exception handling". – KM. Apr 22 '09 at 21:12
  • Yes, but he says SqlException, which I would assume to be System.Data.SqlClient.SqlException ;) – Adam Robinson Apr 22 '09 at 21:14
  • @Adam Robinson, but if SqlException is what he wanted, he could have looked up the syntax himself, I think the question was asked because they were looking for ideas to address the problem. – KM. Apr 23 '09 at 12:43
3

It depends on how you are making the Insert Calls. All as one call, or as individual calls within a transaction? If individual calls, then yes (as you iterate through the calls, catch the one that fails). If one large call, then no. SQL is processing the whole statement, so it's out of the hands of the code.

CodeMonkey1313
  • 15,717
  • 17
  • 76
  • 109
2

I have created a simple way of finding offending fields by:

  1. Getting the column width of all the columns of a table where we're trying to make this insert/ update. (I'm getting this info directly from the database.)
  2. Comparing the column widths to the width of the values we're trying to insert/ update.

Assumptions/ Limitations:

  1. The column names of the table in the database match with the C# entity fields. For eg: If you have a column like this in database:

    You need to have your Entity with the same column name:

    public class SomeTable
    {
       // Other fields
       public string SourceData { get; set; }
    }
    
  2. You're inserting/ updating 1 entity at a time. It'll be clearer in the demo code below. (If you're doing bulk inserts/ updates, you might want to either modify it or use some other solution.)

Step 1:

Get the column width of all the columns directly from the database:

// 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;
}

Step 2:

Compare the column widths with the width of the values we're trying to insert/ update:

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;
}

Demo:

Let's say we're trying to insert someTableEntity of SomeTable class that is modeled in our app like so:

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

And it's inside our SomeDbContext like so:

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

This table in Db has SourceData field as varchar(16) like so:

Now we'll try to insert value that is longer than 16 characters into this field and capture this information:

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;
        }
    }
}

The badFieldsReport will have this value:

'SourceData' column cannot take the value of 'Blah-Blah-Blah-Blah-Blah-Blah' because the max length it can take is 16.

Ash K
  • 1,802
  • 17
  • 44
1

It could also be because you're trying to put in a null value back into the database. So one of your transactions could have nulls in them.

Fandango68
  • 4,461
  • 4
  • 39
  • 74
1

Most of the answers here are to do the obvious check, that the length of the column as defined in the database isn't smaller than the data you are trying to pass into it.

Several times I have been bitten by going to SQL Management Studio, doing a quick:

sp_help 'mytable'

and be confused for a few minutes until I realize the column in question is an nvarchar, which means the length reported by sp_help is really double the real length supported because it's a double byte (unicode) datatype.

i.e. if sp_help reports nvarchar Length 40, you can store 20 characters max.

Chris Amelinckx
  • 4,334
  • 2
  • 23
  • 21
1

Checkout this gist. 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;
    }

Rameez Raja
  • 302
  • 1
  • 13
0

With Linq To SQL I debugged by logging the context, eg. Context.Log = Console.Out Then scanned the SQL to check for any obvious errors, there were two:

-- @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]

the last one I found by scanning the table schema against the values, the field was nvarchar(20) but the value was 22 chars

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

0

In our own case I increase the sql table allowable character or field size which is less than the total characters posted from the front end. Hence that resolve the issue.

0

Simply Used this: MessageBox.Show(cmd4.CommandText.ToString()); in c#.net and this will show you main query , Copy it and run in database .