【问题标题】:DbUpdateException: Which field is causing "String or binary data would be truncated"DbUpdateException:哪个字段导致“字符串或二进制数据将被截断”
【发布时间】:2017-04-13 16:01:18
【问题描述】:

我收到了带有消息的DbUpdateException

字符串或二进制数据将被截断

我了解实体中的某个字段不适合数据库中列的长度。而且我可以下去手动检查它们。

然而,我想要做的是得到一个合理的错误消息,它可能会告诉我它实际上是哪个字段!例如。

字符串或二进制将在字段 ProspectName 处被截断。

我可以打印出很多随机信息。并尝试了各种东西。但没有任何东西指向字段名称。

请注意,这不是DbEntityValidationException 类型的不是,而是DbUpdateException

// DbUpdateException exception
foreach (var entry in exception.Entries)
{ 
    builder.AppendLine(String.Format("Error at: Type {0}", entry.Entity.GetType().Name));

    if ((exception.InnerException is System.Data.Entity.Core.UpdateException) &&
        (exception.InnerException.InnerException is System.Data.SqlClient.SqlException))
    {
        var updateException = (System.Data.Entity.Core.UpdateException)exception.InnerException;

        var sqlException = (System.Data.SqlClient.SqlException)exception.InnerException.InnerException;
        var result = new List<ValidationResult>();

        for (int i = 0; i < sqlException.Errors.Count; i++)
        {
            builder.AppendLine(String.Format("Error code: {0} ", sqlException.Errors[i].Number));
            builder.AppendLine(String.Format("Source: {0} ", sqlException.Errors[i].Source));
            builder.AppendLine(String.Format("Message: {0} ", sqlException.Errors[i].Message));
            builder.AppendLine(String.Format("State: {0} ", sqlException.Errors[i].State));
            builder.AppendLine(String.Format("Procedure: {0} ", sqlException.Errors[i].Procedure));
        } 
    }
}

完全错误:

字符串或二进制数据将被截断。该语句已终止。

错误:类型 tree_1ECACDBB4458C7A9DEC7CD183FD8B8C3473502FEFFACF160E17AD47718DCE5EA
错误代码:8152
来源:.Net SqlClient 数据提供者
消息:字符串或二进制数据将被截断。
状态:14
程序:
错误代码:3621
来源:.Net SqlClient 数据提供者
消息:语句已终止。
状态:0
程序:

【问题讨论】:

  • 您可以考虑暂时关闭 SET ANSI_WARNINGS。然后运行查询并检查写入数据库的内容,您可能能够直观地识别出有问题的列。
  • @Joe 我如何以编程方式让它告诉我哪个字段?我知道我可以手动检查。但是我们有许多具有不同字段长度的旧数据库部署。如果程序可以告诉我哪个字段,那就太好了,因为它知道一个字段正在溢出。
  • 当您运行更新数据库时会发生这种情况?是在数据库中播种数据的时候吗?
  • 应该遵守最大长度并抛出验证错误。也许不是那个导致问题的列?也许你有一些触发器?鉴于您提供的信息,很难说。你必须像往常一样调试它(删除代码,直到你得到最少的代码来重现它)。

标签: c# entity-framework exception entity-framework-6


【解决方案1】:

一个“丑陋”的解决方案(但功能强大且仅使用 C# 代码)来准确找出哪个属性给您该错误将是:

如果您要进行更新,请执行以下操作:

 var myDBObj = db.Mytables.Where(x=>x.Id == myId).FirstOrDefaul();
 if(myDBObj == null) return false; // or something else with the error msg

 myDBObj.Property1 = myObjToSave.Property1;
 db.SaveChanges();

 myDBObj.Property2 = myObjToSave.Property2;
 db.SaveChanges();

 myDBObj.Property3 = myObjToSave.Property3;
 db.SaveChanges();
 .... // EACH PROPERTY....
 myDBObj.PropertyX = myObjToSave.PropertyX;
 db.SaveChanges();

现在,使用制动点或增量变量来跟踪“位置”等......您将确切知道在这种特定情况下您有异常......

注意:这是一个丑陋的解决方案,只是为了追踪它失败的地方......永远不要在生产中使用它......当然,IMO 还有其他更友好的东西,比如拥有一个 sql profiler 并查看生成的 SQL 然后尝试在 sql management studio 上运行它然后看到那里的错误......

更新 #1

类似这样的东西(注意:我没有编译它):P

Type type = obj.GetType();
PropertyInfo[] properties = type.GetProperties();  
string lastPropertyWithError = ""; // You can replace this with a list or so  
foreach (PropertyInfo property in properties)
{
   try{
    property.SetValue(myDBObj, property.GetValue(myObj, null));
    db.SaveChanges();
   }catch()
   {
     lastPropertyWithError  = property.Name;
   }
}

【讨论】:

  • 正如你所说,这不是一个很好的解决方案。并且每个实体都需要大量编码。但它确实回答了这个问题。 (并且是唯一的答案)+1
  • @Doomsknight 是的,我相信我可以为您编写一些代码来自动为您执行此操作(通过反射左右):) 但我认为这不是您想要的。跨度>
  • 假设没有理想的答案,并且我认为元数据存在问题,因为它应该在尝试保存之前捕获。但是,鉴于这是最好的情况,我已授予它赏金/正确答案以结束问题。
  • 感谢您接受的答案,我还将给您一些反思代码:等待几秒钟:P
  • 完成了,我想你明白了....我只是不知道这是否可行:P理论上应该:P
【解决方案2】:

使用实体框架分析器从实体框架中捕获 sql 查询,找到您的更新查询并直接针对数据库运行它,您很有可能找到该字段。

【讨论】:

    【解决方案3】:

    ProspectName 是可以截断的字段。不过,我不知道它是如何映射到您的数据库的。您可以在 FluentApi 或 DataAnnotation 中查找,具体取决于您的设置方式。

    您需要查看为此字段设置的 MaxLength,并确保它与您的数据库最大长度匹配。如果是这样,那么您需要确保不会在此字段上传递太长的字符串。也可能是实体和数据库之间的数据类型不匹配,所以也要检查一下。

    目前,我没有太多其他信息可以进一步帮助您。

    【讨论】:

    • 首先是数据库,EDMX 部署。我将如何在语法上找到问题所在的字段。出现此错误时。
    • 我会右键单击实体类并转到定义。在那里,您会看到 ProspectName。右键单击查找所有引用。您应该看到在 Property() 或其他东西中使用它的地方。去那里,你应该看到它使用的最大长度。如果您的代码允许在该属性上设置比该更长的字符串,那么您每次都会收到此错误。
    【解决方案4】:

    继续@Shaik:

    public MyDbContext(string connectionString, ILogger<PeopleHrDbContext> logger)
        : base(connectionString)
    {
        _logger = logger;
        Database.Log = logger.Debug;
    }
    

    我正在使用 NLog 和 Ioc,因此您可以将记录器传递给您的 ef 上下文,并且在您配置的日志输出中,您可以看到生成的查询是什么样的。对我来说,这是调试输出窗口。我已粘贴在我的 NLog 设置下方以支持这一点。

      <targets>
        <target xsi:type="Debugger" name="d" 
                layout="${longdate} ${logger} ${uppercase:${level}} ${message}" />
      </targets>
    
      <rules>
        <logger name="*" minlevel="Trace" writeTo="d" />
      </rules>
    

    【讨论】:

    • 我知道了,但它不能帮助您找到 DB 列。
    • 我认为这个想法是将您的 sql 查询打印到输出窗口,然后手动复制并粘贴到 SSMS 并在本地运行它以查看 sql 出了什么问题。
    • 嗯,我有查询,我有值。由于它是 EF 核心,它以随机数量对更新进行批处理(嗯,我认为不是 random,而是一种对程序员不透明的算法),所以我只能说它在83 行之一。由于它不是第一行,我知道它不是 all 行。当然,我可以通过不超过 9 步的二分搜索来缩小范围。由于我实际上并不 需要 所有 25 列,因此我通过二分搜索缩小了 那些 的范围,幸运的是发现我不必有问题列。这仍然很乏味,应该是不必要的..
    • @Auspex - 对不起伙计 - 对 efcore 没有太多经验。我的帖子适用于在 EF6 调试列问题方面需要帮助的任何人。它会在后台自动打印 sql 查询到定义的记录器。
    【解决方案5】:

    我创建了以下方法:

    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 some form of "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.

    【讨论】:

    • 如果模型名称与数据库名称不同,这一切都会崩溃,而且它仅在仅保存一个实体而不是包含许多不同类型实体的对象图的极少数情况下有效。如果仅保存一个实体时发生异常,则不难找出哪个字段是罪魁祸首。
    • @GertArnold 感谢您的洞察力。您能否提出修改建议以使该解决方案更加强大?我没有找到任何适用于 Entity Framework 6 的答案,所以我想出了这个解决方案。谢谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-04
    • 2010-10-21
    • 2022-01-11
    相关资源
    最近更新 更多