簡體   English   中英

如何解決“字符串或二進制數據將被截斷。\r\n語句已終止。” 錯誤?

[英]How to solve the "String or binary data would be truncated.\r\nThe statement has been terminated." error?

我有一個更新一些記錄的程序。 當我執行它時,我得到以下異常

"字符串或二進制數據將被截斷。\r\n語句已終止。"

我發現當參數長度大於變量長度時會發生這種情況。 我再次檢查更改大小。 但是沒有用。 再次出現同樣的異常。 我該如何解決這個問題? 請幫忙

這是我的更新代碼

            bool isFinished = dba.update(desingnation, title, initials, surname, fullname, callingName, civilSatatus, natinality, nic, birthday, passport,
                                          hometp, mobiletp, province, district, division, electorate, gramaNiladhari, takafull, p_city,
                                          c_city, p_hno, c_hno, tokens_P, tokens_C, previousEmployeements, bank, branch, type, account, gender, educatinalQ, languageE, languageS, languageT, empNo, appNo);
            if (isFinished)
            {
                WebMsgBox.Show("Successfully Inserted!");
            }
            else
            {
                WebMsgBox.Show("Some Errors Occured");
            }
        }
        else
        {
            WebMsgBox.Show("Some feilds are not valid");
        }
    }
}              

這是將參數傳遞給存儲過程的代碼

            try
            {
                using (SqlCommand cmd = new SqlCommand())
                {
                    cmd.CommandType = CommandType.Text;
                    cmd.CommandType = CommandType.StoredProcedure;
                    cmd.Connection = connection;
                    cmd.CommandTimeout = 0;
                    cmd.Transaction = transactions;

                    /*=======================Update employee details================================*/
                    cmd.CommandText = "update_HS_HR_EMPLOYEE_AADM";

                    cmd.Parameters.Add("@appNo", SqlDbType.Int).Value = appNo;
                    cmd.Parameters.Add("@CALLING_NAME", SqlDbType.VarChar).Value = callingName;
                    cmd.Parameters.Add("@INITIALS", SqlDbType.VarChar).Value = initials;
                    cmd.Parameters.Add("@SURNAME", SqlDbType.VarChar).Value = surname;
                    cmd.Parameters.Add("@TITLE", SqlDbType.VarChar).Value = title;
                    cmd.Parameters.Add("@NAME", SqlDbType.VarChar).Value = fullname;
                    cmd.Parameters.Add("@FULLNAME", SqlDbType.VarChar).Value = fullname + " " + surname;
                    cmd.Parameters.Add("@NIC", SqlDbType.VarChar).Value = nic;
                    cmd.Parameters.Add("@BDY", SqlDbType.VarChar).Value = birthday;
                    cmd.Parameters.Add("@GENDER", SqlDbType.VarChar).Value = gender;
                    cmd.Parameters.Add("@NATIONALITY", SqlDbType.VarChar).Value = natinality;
                    cmd.Parameters.Add("@CIVILSTATUS", SqlDbType.VarChar).Value = civilSatatus;
                    cmd.Parameters.Add("@DESIGNATION", SqlDbType.VarChar).Value = desingnation;
                    cmd.Parameters.Add("@P_ADD1", SqlDbType.VarChar).Value = p_hno;
                    cmd.Parameters.Add("@P_ADD2", SqlDbType.VarChar).Value = tokens_P[0];

                    if (tokens_P.Length > 1)
                        cmd.Parameters.Add("@P_ADD3", SqlDbType.VarChar).Value = tokens_P[1];
                    else
                        cmd.Parameters.Add("@P_ADD3", SqlDbType.VarChar).Value = "";

                    cmd.Parameters.Add("@P_CITY", SqlDbType.VarChar).Value = p_city;
                    cmd.Parameters.Add("@TP_HOME", SqlDbType.VarChar).Value = hometp;
                    cmd.Parameters.Add("@TP_MOBILE", SqlDbType.VarChar).Value = mobiletp;
                    cmd.Parameters.Add("@PROVINCE", SqlDbType.VarChar).Value = province;
                    cmd.Parameters.Add("@DISTRICT", SqlDbType.VarChar).Value = district;
                    cmd.Parameters.Add("@C_ADD1", SqlDbType.VarChar).Value = c_hno;
                    cmd.Parameters.Add("@C_ADD2", SqlDbType.VarChar).Value = tokens_C[0];
                    cmd.Parameters.Add("@PER_GNDIV_CODE", SqlDbType.VarChar).Value = gramaNiladhari;
                    cmd.Parameters.Add("@PER_DSDIV_CODE", SqlDbType.VarChar).Value = division;
                    cmd.Parameters.Add("@TAKAFUL", SqlDbType.VarChar).Value = takafull;
                    cmd.Parameters.Add("@PASSPORT_NO", SqlDbType.VarChar).Value = passport;

                    if (tokens_C.Length > 1)
                        cmd.Parameters.Add("@C_ADD3", SqlDbType.VarChar).Value = tokens_C[1];
                    else
                        cmd.Parameters.Add("@C_ADD3", SqlDbType.VarChar).Value = "";

                    cmd.Parameters.Add("@C_CITY", SqlDbType.VarChar).Value = c_city;
                    cmd.Parameters.Add("@ELECTORATE", SqlDbType.VarChar).Value = electorate;

                    //int appNO = int.Parse((cmd.ExecuteScalar().ToString()));
                    cmd.ExecuteNonQuery();
                    cmd.Parameters.Clear();         


  }     
}         

這是存儲過程

ALTER PROCEDURE [dbo].[update_HS_HR_EMPLOYEE_AADM] 
@appNo Int,
@CALLING_NAME VARCHAR(50),
@INITIALS VARCHAR(50),
@SURNAME VARCHAR(50),
@TITLE VARCHAR(50),
@NAME VARCHAR(50),
@FULLNAME VARCHAR(100),
@NIC VARCHAR(15),
@BDY VARCHAR(50),
@GENDER CHAR(1),
@NATIONALITY VARCHAR(50),
@CIVILSTATUS VARCHAR(50),
@DESIGNATION VARCHAR(50),
@P_ADD1 VARCHAR(50),
@P_ADD2 VARCHAR(50),
@P_ADD3 VARCHAR(50),
@P_CITY VARCHAR(50),
@TP_HOME VARCHAR(50),
@TP_MOBILE VARCHAR(50),
@PROVINCE VARCHAR(50),
@DISTRICT VARCHAR(50),
@C_ADD1 VARCHAR(50),
@C_ADD2 VARCHAR(50),
@C_ADD3 VARCHAR(50),
@C_CITY VARCHAR(50),
@ELECTORATE VARCHAR(50),
@PER_GNDIV_CODE VARCHAR(50),
@PER_DSDIV_CODE VARCHAR(50),
@TAKAFUL VARCHAR(50),
@PASSPORT_NO VARCHAR(50)

AS

BEGIN

update [HS_HR_EMPLOYEE_AADM]
SET
       [EMP_CALLING_NAME]=@CALLING_NAME
       ,[EMP_MIDDLE_INI]=@INITIALS
       ,[EMP_SURNAME]=@SURNAME
       ,[EMP_TITLE]=@TITLE
       ,[EMP_NAMES_BY_INI]=@NAME
       ,[EMP_FULLNAME]=@FULLNAME
       ,[EMP_NIC_NO]=@NIC
       ,[EMP_BIRTHDAY]=@BDY
       ,[EMP_GENDER]=@GENDER
       ,[NAT_CODE]=@NATIONALITY
       ,[EMP_MARITAL_STATUS]=@CIVILSTATUS
       ,[EMP_DATE_JOINED]=GETDATE()
       ,[EMP_CONFIRM_FLG]=0
       ,[CT_CODE]='000008'
       ,[DSG_CODE]=@DESIGNATION
       ,[CAT_CODE]='000001'
       ,[EMP_PER_ADDRESS1]=@P_ADD1
       ,[EMP_PER_ADDRESS2]=@P_ADD2
       ,[EMP_PER_ADDRESS3]=@P_ADD3
       ,[EMP_PER_CITY]=@P_CITY
       ,[EMP_PER_TELEPHONE]=@TP_HOME
       ,[EMP_PER_MOBILE]=@TP_MOBILE
       ,[EMP_PER_PROVINCE_CODE]=@PROVINCE
       ,[EMP_PER_DISTRICT_CODE]=@DISTRICT
       ,[EMP_TEM_ADDRESS1]=@C_ADD1
       ,[EMP_TEM_ADDRESS2]=@C_ADD2
       ,[EMP_PER_ELECTORATE_CODE]=@ELECTORATE
       ,[EMP_TEM_ADDRESS3]=@C_ADD3
       ,[EMP_TEM_CITY]=@C_CITY
       ,[EMP_PER_GNDIV_CODE]=@PER_GNDIV_CODE
       ,[EMP_PER_DSDIV_CODE]=@PER_DSDIV_CODE
       ,[EMP_PASSPORT_NO]=@TAKAFUL
       ,[EMP_TAK]=@PASSPORT_NO
       where App_no = @appNo

END

在 C# 代碼中的 SqlDBType.Varchar 中指定與存儲過程中指定的大小相匹配的 varchar 大小,例如。

cmd.Parameters.Add("@CALLING_NAME", SqlDbType.VarChar, 50).Value = callingName;

對應於存儲過程中的參數@CALLING_NAME VARCHAR(50) 這確保了在傳遞給存儲過程時不會超過大小。

如果未為字符串參數指定長度,ADO.NET 將獲取可能超過存儲過程 VARCHAR 參數中指定的大小的任意長度值。

同樣在前端確保在文本框中輸入的字符數不超過相應的參數大小。 這可以使用MaxLength屬性來完成,或者如果大小超過,則使用 JQuery/Javascript 提示用戶消息。

對其他參數執行此操作並檢查。

指定的錯誤"String or binary data would be truncated.\r\nThe statement has been terminated." 當您嘗試插入高於指定列大小的值時顯示,當我們查看給定過程時,我們無法識別每列的大小,因此最好交叉檢查具有您提供的值的列。

我可以說@GENDER可能會導致類似的問題,因為它在過程中被定義為@GENDER CHAR(1),但是您將一個字符串傳遞給該方法並作為SqlDbType.VarChar傳遞,而不是因為您必須給出值作為字符。 對於這個特定領域

String or binary data would be truncated錯誤告訴您您正在丟失數據。 關於這個錯誤的一個惱人的事情是它沒有告訴你問題與哪一列有關,並且在這樣的場景中(有很多列),它很難診斷。

如果您有合適的 SQL Server 版本(請參閱下面的超鏈接頁面),您可以打開 Trace Flag 460 (這可能需要重新啟動)以准確告訴您問題與哪個表和列有關。

如果沒有,這是我的更多手動方法......之后有一些關於如何在沒有此錯誤的情況下靜默截斷參數的信息(這不好)。


請注意,對於每一列,C# 變量中都有一個值、聲明的參數類型(在 C# 代碼和存儲過程中)和表中列的大小(問題中缺少列的定義 -這可以解釋為什么還沒有一個公認的答案)。 對於所有列,所有這些最大長度和類型都需要綁定。 你真的需要檢查所有這些; 但我們都喜歡捷徑,所以...

我查找哪些列有問題的提示是找到它發生的場景,以便您可以輕松地重復它 - 這特別容易做到,您對此方法進行了單元測試。 現在修改存儲過程以注釋掉一半的列,然后重試。

  • 如果它有效,那么您知道未注釋的列很好(對於這組特定的數據),問題出在被注釋掉的列中,所以取消注釋一半的行,然后重試。
  • 如果它不起作用,則問題出在未注釋的列上,因此請注釋掉其余列的一半並重試。
  • 重復,直到您確定哪些列有問題。 我說“列”,因為雖然它可能只有一列有這個問題,但可能不止於此。
  • 現在把一切都恢復到你開始時的樣子。

現在您已經確定了哪些列有問題,對照存儲的 proc 參數定義、C# 參數定義和 C# 變量中的值檢查表中每個列的定義。 您可能需要一直跟蹤到在用戶界面中輸入值的位置,並確保有適當的限制以防止值太大。


作為獎勵提示,我喜歡讓我的參數大小與它們相關的列的類型和大小相對應的單元測試。 我還有表示每個字符串字段的最大長度和數字字段的最大值的常量。 這些常量針對數據庫中的列進行單元測試,並在限制用戶在用戶界面中給出的值時使用。 它們也可以用於該方法的單元測試,以證明為每列插入最大可能值確實有效。

但是,請注意,由於參數強制會發生靜默截斷,因此值得使您的varcharnvarcharvarbinary參數大於列大小:

SQL 服務器會默默地強制你的值是參數的任何類型。 例如...

 DECLARE @Varchar VARCHAR(8) = 'I will be truncated'; DECLARE @Decimal92 DECIMAL(9,2) = 123.456; DECLARE @Int INT = 123.456; SELECT @Varchar, @Decimal92, @Int;

將輸出...

 I will b 123.46 123

這可能會讓人感到意外,因為 SQL 會抱怨這樣的事情:

 BEGIN TRANSACTION; CREATE TABLE tbl_Test(MyColumn NVARCHAR(5) NOT NULL); INSERT INTO dbo.tbl_Test (MyColumn) VALUES (N'I will be truncated'); ROLLBACK TRANSACTION;

通過說String or binary data would be truncated. 然而下面的代碼並沒有抱怨,默默地強制值並插入記錄:

 BEGIN TRANSACTION; CREATE TABLE tbl_Test(MyColumn NVARCHAR(5) NOT NULL); DECLARE @MyColumn NVARCHAR(5)=N'I will be truncated' INSERT INTO dbo.tbl_Test (MyColumn) VALUES (@MyColumn); ROLLBACK TRANSACTION;

因此,如果您想了解發生的截斷問題,您需要確保您的參數具有比它要進入的列更大的容量。 例如,如果我們只更改一個字符...

 BEGIN TRANSACTION; CREATE TABLE tbl_Test(MyColumn NVARCHAR(5) NOT NULL); DECLARE @MyColumn NVARCHAR(6)=N'I will be truncated' INSERT INTO dbo.tbl_Test (MyColumn) VALUES (@MyColumn); ROLLBACK TRANSACTION;

...會給出截斷錯誤。 但是,請注意這不是一個完整的解決方案,因為如果我嘗試使用不同的值......

CREATE TYPE dbo.MyTableType AS TABLE (MyColumn NVARCHAR(5) NOT NULL); 
GO 
BEGIN TRANSACTION; 
DECLARE @MyColumn NVARCHAR(5)=N'I will be truncated' 
DECLARE @MyTable AS dbo.MyTableType; 
INSERT INTO @MyTable (MyColumn) VALUES (@MyColumn); 
CREATE TABLE dbo.tbl_Test (MyColumn NVARCHAR(5) NOT NULL); 
INSERT INTO dbo.tbl_Test SELECT MyColumn FROM @MyTable; 
ROLLBACK TRANSACTION; 
GO 
DROP TYPE dbo.MyTableType; 

然后它會默默地強制,在空格之后截斷,然后(因為它是一個 varchar)該值刪除了尾隨空格,並且它插入而不抱怨。

所以唯一可以確定的方法是讓你的參數比它需要的大幾個字符,因為可能不會有多個空格連續。 您可以對所有內容使用 VARCHAR(MAX),但有人擔心這可能會對性能產生影響。

這一點特別重要的一個地方是加密值。 如果加密值被截斷,則您無法解密它們。 因此,您需要確保您的 VARBINARY 參數的大小大於相關列,以便您得到錯誤而不是插入截斷的值。 在這種情況下,我相信一個更大的字符就足夠了,因為沒有修剪 VARBINARY。 好吧,顯然 VarBinarys 會從末尾修剪尾隨的“nul”(ASCII = 0)字符; 但僅當 ANSI_PADDING 設置為 OFF 時,但正如 Microsoft 所說,它應該始終設置為“ON”。 本節還介紹了具有不同設置的不同字段類型會發生什么修剪。

值得一提的是,SQL 甚至與它的執行方式不一致。 如果我們用 DECIMAL 重試原始示例...

 BEGIN TRANSACTION; CREATE TABLE tbl_Test(MyColumn DECIMAL(9,2) NOT NULL); INSERT INTO dbo.tbl_Test (MyColumn) VALUES (123.456); SELECT * FROM tbl_Test ROLLBACK TRANSACTION;

它不會抱怨值有太多小數位,它只是默默地強制它。 如果我通過一個比列具有更多小數位的參數來執行此操作,情況也是如此......

 BEGIN TRANSACTION; CREATE TABLE tbl_Test(MyColumn DECIMAL(9,2) NOT NULL); DECLARE @MyColumn DECIMAL(9,3)=123.456 SELECT @MyColumn INSERT INTO dbo.tbl_Test (MyColumn) VALUES (@MyColumn); SELECT * FROM tbl_Test ROLLBACK TRANSACTION;

然而,如果我給它一個太大的價值......

 BEGIN TRANSACTION; CREATE TABLE tbl_Test(MyColumn DECIMAL(9,2) NOT NULL); INSERT INTO dbo.tbl_Test (MyColumn) VALUES (1234567890.456); SELECT * FROM tbl_Test ROLLBACK TRANSACTION;

然后它會抱怨Arithmetic overflow error converting numeric to data type numeric. (就像我先將該值放入參數中一樣)。

要提到的另一件事與參數強制和加密有關。 想象一個場景,您有一個類型為 DECIMAL(9,2) 的 SQL 列和一個相同類型的參數,並且您從 C# 代碼中為其提供了一個點網“十進制”。 如果您的代碼中的“小數”有很多小數位,那么這種無聲強制將有效地要求 SQL 為您進行舍入。 這很好......直到您決定加密該列,因為現在您正在加密的值將比 SQL DECIMAL 列能夠保存的值長得多,因此可能比您允許的要大(在您的 VARBINARY 長度)。 在這種情況下,您需要確保在加密之前將該值四舍五入到正確的小數位數。

關於參數的修剪尾隨空格。 它只是根據需要修剪...這表明它已經從參數中修剪了一個空格,但在 N 之后留下了剩余的 4 個空格。

 BEGIN TRANSACTION; CREATE TABLE tbl_Test(MyColumn NVARCHAR(5) NOT NULL); DECLARE @MyColumn NVARCHAR(6)=N'N complain' SELECT @MyColumn +'|',LEN(@MyColumn),DATALENGTH(@MyColumn) INSERT INTO dbo.tbl_Test (MyColumn) VALUES (@MyColumn); SELECT MyColumn +'|',LEN(MyColumn),DATALENGTH(MyColumn) FROM dbo.tbl_Test ROLLBACK TRANSACTION;

關於此的另一個學習點...相同的概念適用於與表定義相關的用戶定義的表類型。

這是一個示例腳本來演示該問題。 請注意,類型的創建和刪除必須在事務之外完成。

 CREATE TYPE dbo.MyTableType AS TABLE (MyColumn NVARCHAR(5) NOT NULL); GO BEGIN TRANSACTION; DECLARE @MyColumn NVARCHAR(5)=N'I will be truncated' DECLARE @MyTable AS dbo.MyTableType; INSERT INTO @MyTable (MyColumn) VALUES (@MyColumn); CREATE TABLE dbo.tbl_Test (MyColumn NVARCHAR(5) NOT NULL); INSERT INTO dbo.tbl_Test SELECT MyColumn FROM @MyTable; ROLLBACK TRANSACTION; GO DROP TYPE dbo.MyTableType;

我有幾個建議來確定這一點:(選擇任何 1,然后移動到其他,如果問題仍然存在。順序無關緊要,無論是哪個建議,你都輕松了)。

  • 建議 1:打開 sql profiler 並跟蹤請求。 從探查器獲取查詢並直接在 SQL Server 上運行。 你可能會得到更多的錯誤細節。

    腳步:

    • 開始調試,並在您要調用 db 之前停止。
    • 打開 Profiler,連接到數據庫。
    • 單擊清除跟蹤窗口。
    • 開始調試。
    • 只要您收到很少的請求,就停止探查器。
    • 識別查詢,並在 SQL Server 上運行。 在此處輸入圖像描述
  • 建議 2:嘗試使用 SQL Server management studio 插入數據。
    腳步:

    • 右鍵單擊table -> 單擊Script Table as -> 插入到。
    • 現在,如果您正確通過,請與您的輸入進行比較。
  • 建議3:使用此代碼獲取數據長度與傳遞的數據之間的差異或在傳遞給DB時直接截斷。
    腳步:

    • 創建此類DbContextExtension
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;

namespace Data.Context
{
    public class DbContextExtension
    {
        private Dictionary<IProperty, int> _maxLengthMetadataCache;

        public void AutoTruncateStringToMaxLength(DbContext db)
        {
            var entries = db?.ChangeTracker?.Entries();
            if (entries == null)
            {
                return;
            }

            var maxLengthMetadata = PopulateMaxLengthMetadataCache(db);

            foreach (var entry in entries)
            {
                var propertyValues = entry.CurrentValues.Properties.Where(p => p.ClrType == typeof(string));

                foreach (var prop in propertyValues)
                {
                    if (entry.CurrentValues[prop.Name] != null)
                    {
                        var stringValue = entry.CurrentValues[prop.Name].ToString();
                        if (maxLengthMetadata.ContainsKey(prop))
                        {
                            var maxLength = maxLengthMetadata[prop];
                            stringValue = TruncateString(stringValue, maxLength);
                        }

                        entry.CurrentValues[prop.Name] = stringValue;
                    }
                }
            }
        }

        private Dictionary<IProperty, int> PopulateMaxLengthMetadataCache(DbContext db)
        {
            _maxLengthMetadataCache ??= new Dictionary<IProperty, int>();

            var entities = db.Model.GetEntityTypes();
            foreach (var entityType in entities)
            {
                foreach (var property in entityType.GetProperties())
                {
                    var annotation = property.GetAnnotations().FirstOrDefault(a => a.Name == "MaxLength");
                    if (annotation != null)
                    {
                        var maxLength = Convert.ToInt32(annotation.Value);
                        if (maxLength > 0 && !_maxLengthMetadataCache.ContainsKey(property))
                        {
                            _maxLengthMetadataCache[property] = maxLength;
                        }
                    }
                }
            }
            
            return _maxLengthMetadataCache;
        }

        private static string TruncateString(string value, int maxLength)
        {
            if (string.IsNullOrEmpty(value)) return value;
            return value.Length <= maxLength ? value : value.Substring(0, maxLength);
        }
    }
}
  • 在調用 SaveChanges 之前像這樣使用它:
    public class DocumentRepository : IDocumentRepository 
    {
        private readonly DbContext _context;
        public DocumentRepository(DbContext context)
        {
            _context = context;
        }

        public async Task CreateDocument(Document obj)
        {
            //Feel free to make it extension method or use it as DI. To make the example easier, I am creating the object here.
            var dbExtensions = new DbContextExtension();
            dbExtensions.AutoTruncateStringToMaxLength(_context);
            await _context.Documents.AddAsync(obj);
            await _context.SaveChangesAsync();
        }

暫無
暫無

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

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