SQL/C#:DataTable 到存储过程(从用户定义的表类型插入)- 转换错误

Posted

技术标签:

【中文标题】SQL/C#:DataTable 到存储过程(从用户定义的表类型插入)- 转换错误【英文标题】:SQL/C#: DataTable to stored procedure (INSERT from user-defined table type) - Converting error 【发布时间】:2021-11-10 20:45:56 【问题描述】:

我正在尝试将我的DataTable 传递给存储过程。 DataTable 保存 Excel 工作表的内容。 Excel 工作表有两个空列,五个包含文本,五个包含十进制数字。 空列是OppdragsMVAkodeOppdragsgebyrMVAkode

如果我运行我的应用程序,我会收到以下异常:

System.ArgumentException:输入字符串的格式不正确。无法将 存储在 OppdragsMVAkode 列中。预期类型是 Decimal。

如果我添加一个临时号码,我会收到以下异常:

System.Data.SqlClient.SqlException:将数据类型 nvarchar 转换为数字时出错。

我不明白为什么它将它作为字符串读取。

我的存储过程(spGetTrips):

ALTER PROCEDURE [dbo].[spGetTrips]
    @trips udtTripsPerMonth readonly
    
AS
BEGIN
    INSERT INTO tblTripsPerMonth
        SELECT 
        [KjøretøyID], 
        SUBSTRING([År], 7, 4), 
        SUBSTRING([Måned], 4, 2), 
        [Betaling (brutto)], 
        [Betaling (netto)], 
        [Bestillingsgebyr (netto)], 
        [Betalingsgebyr (netto)], 
        [OppdragsMVAkode], 
        CONCAT(LøyvehaverFakturaID, + 'UF-' + SUBSTRING([År], 9, 2) + SUBSTRING([Måned], 4, 2)), 
        [Oppdragsgebyr (netto)], 
        [OppdragsgebyrMVAkode], 
        CONCAT([RidelRegionFakturaID], + 'UF-' + SUBSTRING([År], 9, 2) + SUBSTRING([Måned], 4, 2))
    FROM @trips

    UPDATE tblTripsPerMonth
    SET [OppdragsMVAkode] = (
        SELECT [ID]
        FROM [tblMVAkoder]
        WHERE [ID] = 'MVAkode2'
        );

    UPDATE tblTripsPerMonth
    SET [OppdragsgebyrMVAkode] = (
        SELECT [ID]
        FROM [tblMVAkoder]
        WHERE [ID] = 'MVAkode5'
        );
END

正如您在上面看到的,我在存储过程中使用UPDATE 子句设置两个空列的值。无论它们在 Excel 工作表中是空的,还是具有一些初步值,然后被覆盖,我不在乎 - 我只想让它工作。

这是我的用户定义表类型 (udtTripsPerMonth):

CREATE TYPE [dbo].[udtTripsPerMonth] AS TABLE(
    [KjøretøyID] [nvarchar](50) NULL,
    [År] [nvarchar](50) NULL,
    [Måned] [nvarchar](50) NULL,
    [Betaling (brutto)] [decimal](10, 2) NULL,
    [Betaling (netto)] [decimal](10, 2) NULL,
    [Bestillingsgebyr (netto)] [decimal](10, 2) NULL,
    [Betalingsgebyr (netto)] [decimal](10, 2) NULL,
    [OppdragsMVAkode] [decimal](10, 2) NULL,
    [LøyvehaverFakturaID] [nvarchar](50) NULL,
    [Oppdragsgebyr (netto)] [decimal](10, 2) NULL,
    [OppdragsgebyrMVAkode] [decimal](10, 2) NULL,
    [RidelRegionFakturaID] [nvarchar](50) NULL
)
GO

还有我的桌子 (tblTripsPerMonth):

CREATE TABLE [dbo].[tblTripsPerMonth](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [KjøretøyID] [nvarchar](50) NULL,
    [År] [nvarchar](50) NULL,
    [Måned] [nvarchar](50) NULL,
    [Betaling (brutto)] [decimal](10, 2) NULL,
    [Betaling (netto)] [decimal](10, 2) NULL,
    [Bestillingsgebyr (netto)] [decimal](10, 2) NULL,
    [Betalingsgebyr (netto)] [decimal](10, 2) NULL,
    [OppdragsMVAkode] [decimal](10, 2) NULL,
    [LøyvehaverFakturaID] [nvarchar](50) NULL,
    [Oppdragsgebyr (netto)] [decimal](10, 2) NULL,
    [OppdragsgebyrMVAkode] [decimal](10, 2) NULL,
    [RidelRegionFakturaID] [nvarchar](50) NULL,
PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO

我在UPDATE 子句中引用的另一个表(tblMVAkoder):

CREATE TABLE [dbo].[tblMVAkoder](
    [ID] [nvarchar](50) NOT NULL,
    [Startdato] [date] NULL,
    [Sluttdato] [date] NULL,
    [MVAsats] [decimal](10, 2) NULL,
PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO

这是我的 C# 代码(Excel 到 DataTable):

private void btnExport_Click(object sender, RoutedEventArgs e) 

    OpenFileDialog of = new();
    of.Filter = "Excel Files | *.xlsx;";
    of.Title = "Importer Excel fil.";

    if (of.ShowDialog() == true) 

        dgPaidTrip.ItemsSource = ImportExceltoDataTable(of.FileName).DefaultView;
        btnClearForm.IsEnabled = true;
    

    using (SqlConnection con = new(ConnectionString.connectionString))
    using (var cmd = new SqlCommand("spGetTrips", con)  CommandType = CommandType.StoredProcedure ) 

        con.Open();
        DataTable dt = ImportExceltoDataTable(of.FileName);
        cmd.Parameters.Add(new SqlParameter("@trips", dt));
        cmd.ExecuteNonQuery();
    


public static DataTable ImportExceltoDataTable(string filePath) 

    using (XLWorkbook wb = new(filePath)) 

        IXLWorksheet ws = wb.Worksheet(1);
        int tl_Row = ws.FirstCellUsed().Address.RowNumber;
        int tl_Col = ws.FirstCellUsed().Address.ColumnNumber;
        int br_Row = ws.LastCellUsed().Address.RowNumber;
        int br_Col = ws.LastCellUsed().Address.ColumnNumber;

        DataTable dt = new();

        dt.Columns.Add("KjøretøyID", typeof(string));
        dt.Columns.Add("År", typeof(string));
        dt.Columns.Add("Måned", typeof(string));
        dt.Columns.Add("Betaling (brutto)", typeof(decimal));
        dt.Columns.Add("Betaling (netto)", typeof(decimal));
        dt.Columns.Add("Bestillingsgebyr (netto)", typeof(decimal));
        dt.Columns.Add("Betalingsgebyr (netto)", typeof(decimal));
        dt.Columns.Add("OppdragsMVAkode", typeof(decimal));
        dt.Columns.Add("LøyvehaverFakturaID", typeof(string));
        dt.Columns.Add("Oppdragsgebyr (netto)", typeof(decimal));
        dt.Columns.Add("OppdragsgebyrMVAkode", typeof(decimal));
        dt.Columns.Add("RidelRegionFakturaID", typeof(string));

        IXLRow currentRow;
    
        for (int dtRow = 0; dtRow < br_Row - tl_Row; dtRow++) 

            currentRow = ws.Row(tl_Row + dtRow + 1);
            dt.Rows.Add();

            for (int dtCol = 0; dtCol < br_Col - tl_Col + 1; dtCol++) 

                dt.Rows[dtRow][dtCol] = currentRow.Cell(tl_Col + dtCol).Value;
            
        

        return dt;
    

据我所知,我的所有列类型以及它们的顺序都匹配。 我将所有类型更改为nvarchar,并且代码“有效”。 但是比我聪明的人告诉我,伪造列类型不是一个好主意。

我错过了什么?

【问题讨论】:

您是否尝试过将您要发送的DataTableSqlDBType 设置为Structured?第一个错误与 ImportExceltoDataTable => dt.Rows[dtRow][dtCol] = currentRow.Cell(tl_Col + dtCol).Value; FWIW 有关 对于第二个问题,我会在您的程序中加入某种 try/catch/begin catch 语句,以帮助准确追踪问题发生的位置。您可以获取过程名称、行号、确切消息等,并将该错误记录在另一个表中或返回并检查。 正如所指出的,问题在于dt.Rows[dtRow][dtCol] = currentRow.Cell(tl_Col + dtCol).Value,这是因为它将所有单元格值添加为字符串数据。如果 Betaling (brutto)Betaling (netto)Bestillingsgebyr (netto)Betalingsgebyr (netto)OppdragsMVAkodeOppdragsgebyr (netto)Oppdragsgebyr (netto)OppdragsgebyrMVAkode 的 Cell.Values 为空白,或者看起来不像十进制数字,您应该发送 @ 987654351@ 而不是逐字传递字符串。 @zaggler 感谢您的评论。我确实尝试将SqlDBType 设置为Structured,但异常仍然存在。我一直在尝试更改您和@AlwaysLearning(也感谢您的评论)正在引用的行,但我没有成功。我将如何发送 DBNull.Value?我尝试在我的dt.Columns.Add 部分中将AllowDBNull = true 添加到有问题的列中,但这不起作用。 【参考方案1】:

要传递空值,您需要将列值设置为DBNull.Value。你可以这样设置DBNull

dt.Rows[dtRow][dtCol] = currentRow.Cell(tl_Col + dtCol).Value ?? (object)DBNull.Value;

您还必须设置 SqlDBType TypeNameDirection 属性

cmd.Parameters.Add(
    new SqlParameter("@trips", SqlDBType.Structured)
    
        TypeName = "dbo.udtTripsPerMonth",
        Direction = ParameterDirection.Input,
        Value = dt
    );

【讨论】:

谢谢你!这是澄清。实施此操作后,如果我向列添加临时数值,现在将不会出现异常。没关系,因为我可以使用UPDATE 子句来编辑它们。但我想了解为什么它仍然给我“输入字符串的格式不正确。无法将 存储在 OppdragsMVAkode 列中。预期的类型是十进制。”如果我将列保持为空,则例外。有什么想法吗? 如果值为&lt;&gt;,我不知道如何将其转换为decimal。您需要自己检查该值并将其设为空。 只是意味着它是一个空字符串,对吧?该特定列的单元格中没有内容。 不知道。我看不到你看到的价值。要么是空字符串(== "" 为真),要么不是(== "&lt;&gt;" 为真),您需要自己解决。空字符串不等于null,也不能转换成decimal,所以需要检查一下 感谢您的帮助!

以上是关于SQL/C#:DataTable 到存储过程(从用户定义的表类型插入)- 转换错误的主要内容,如果未能解决你的问题,请参考以下文章

存储技术发展过程

存储过程传入datatable

oracle存储过程中参数datatable如何接收,求高手!

存储过程 传 datatable

将存储过程中的数据填充到数据表中

DataTable / DataHelper 行通过存储过程不为空