【问题标题】:SQL Server and .NET: insert fails (silently!) in code but not when run manuallySQL Server 和 .NET:在代码中插入失败(静默!),但在手动运行时不会
【发布时间】:2009-09-03 05:47:06
【问题描述】:

我的插入存储过程:

ALTER procedure proj_ins_all 
(
@proj_number INT, 
@usr_id INT, 
@download DATETIME, 
@status INT 
)
as
INSERT INTO project
(proj_number, usr_id, date_download, status_id)
VALUES
(@proj_number, @usr_id, @download, @status)
select SCOPE_IDENTITY()

... 像这样手动调用时运行良好:

exec proj_ins_all 9001210, 2, '2009-09-03', 2 

...但是当从代码中调用时:

_id = data.ExecuteIntScalar("proj_ins_all", arrParams);

...插入不会发生。现在,标识列 does 增加,_id does 设置为它的值。但该行本身从未出现在表中。

我能想到的最好的猜测是一个插入触发器,它删除了新插入的行,但表上没有触发器(为什么手动完成它会起作用?)。我的其他尝试是围绕存储过程以某种方式回滚插入的猜测,因此将 beginendgo 的和分号到存储过程中以正确分隔“插入”和“身份选择”位。这没有解决任何问题。

有什么想法吗?

更新:

感谢迄今为止提供帮助的所有人。根据 Preet 的建议(第一个答案),我学会了如何使用 SQL Server Profiler(我不敢相信我以前从来不知道它 - 我认为它只对性能调整有用,没有意识到我可以确切地看到查询正在发生什么到数据库)。

它显示 SqlCommand.ExecuteScalar() 方法发送的 SQL 与我手动运行的略有不同。它正在发送:

exec proj_ins_all @proj_number=9001810,@usr_id=2,@download='2009-09-03 16:20:11.7130000',@status=2

我手动运行了它,瞧!一个实际的 SQL 服务器错误(!):

将数据类型 varchar 转换为 datetime 时出错。

由于我是手动测试的,我只是将日期时间从 '2009-09-03 16:20:11.7130000' 缩短为 '2009-09-03 16:20:11 ' 这修复了错误;现在该行插入正常。

但这引出了一个问题:为什么 Microsoft 的 SQL Server 不能在该日期时间参数中处理超过 23 个字符?是微软的 SqlCommand.ExecuteScalar() 方法构造了这样的查询,而不是我。这是一个问题,因为我的代码仍然无法运行。

当我手动让它工作时,我研究了如何在代码中为日期设置 SqlParameter,以便它发送一个与有效值类似的值。我尝试将数据类型从 SqlDbType.DateTime 更改为 SqlDbType.SmallDateTime。分析器显示这确实产生了更短的日期时间值“2009-09-03 17:15:00”,但插入仍然默默失败(原始问题)。但是,当我从探查器复制粘贴 sql 并手动尝试时 - 它起作用了。没有错误。发送它作为 varchar 的相同交易 - SSMS 查询窗口喜欢它,通过 .net 的相同查询静默失败。

:(

还有其他想法吗? SQL 服务器中的一些“环境”类型设置,例如“set ansi_nulls off”或手动连接和直通代码连接之间可能不同的任何设置?

(还有一个问题是为什么每次出现这个错误时sql server不给我错误信息并产生异常?)

.Net 代码

根据 van 的要求,这里是相关的 .NET 代码(C# ASP.NET):

data.MakeInParam("@proj_number", SqlDbType.Int, _projNo),
data.MakeInParam("@usr_id", SqlDbType.Int, _usr.Id),
data.MakeInParam("@download", SqlDbType.SmallDateTime, _downloadDate),
data.MakeInParam("@status", SqlDbType.Int, (int)_status),

并且 MakeInParam 有这一行:

param = new SqlParameter(paramName, DbType);

它会像这样执行:

SqlCommand cmd = CreateCommand(procName, prams);
object result = cmd.ExecuteScalar();

注意:在上面的参数中,@download 是问题所在。 _downloadDate 是一个可为空的日期时间。请注意,此行中的 sql 类型曾经是 SqlDbType.DateTime,但我将其更改为 SqlDbType.SmallDateTime 以生成手动调用(从代码运行时仍然失败)。

更新:已解决 感谢马特,我终于找到并解决了这个问题:自定义助手类在它的一个函数中缺少一个 cmd.Transaction.Commit() !它里面有一堆不同的乱七八糟的东西,我在最初浏览代码时错过了这一点。

感谢 Matt 和 Preet 以及其他所有做出贡献的人。

经验教训
- SQL Profiler 真的很有用。花点时间学习如何使用它。
- 如果数据库正在吞噬错误,或者似乎正在根据您的代码执行某些操作,但实际上并没有出现在数据库中,请检查事务是否正在提交。
- 不要太相信别人写的数据库助手类。即使是一开始看起来很简单的一个。确保您完全了解它的作用,因为它可能有问题,或者只是没有按照您认为的方式做事。

【问题讨论】:

    标签: .net sql sql-server ado.net


    【解决方案1】:

    每当尝试插入时,标识列都会递增,即使它失败,所以该部分不一定是异常的。

    我会在 arrParams 中查找与您的参数值和/或类型有关的问题。什么样的对象是“数据”? (我几乎不敢问,但我没有在 msdn 上获得 ExecuteIntScalar 的任何点击)

    编辑:

    我认为 van 在未提交事务方面走在正确的轨道上。看起来您正在使用某种自定义帮助程序类来管理对数据库中存储过程的调用(可能还有其他数据库访问),并且可能是该代码正在吞噬 SQL 服务器引发的错误。我创建了一个小测试应用程序,并且能够重现您描述的行为。由于我们无法判断您的类如何捕获异常等。这可能不是您的实际问题,但这是存储过程调用可能按照您描述的方式失败的一种方式。

    // call the proj_ins_all SP every time a button is clicked.
    protected void Button1_Click(object sender, EventArgs e)
    {
        using (SqlConnection conn = new SqlConnection(myConnectionString))
        using (SqlCommand cmd = new SqlCommand("proj_ins_all", conn))
        {
            try
            {
                cmd.CommandType = CommandType.StoredProcedure;
                cmd.Parameters.Add(new SqlParameter("@proj_number", SqlDbType.Int));
                cmd.Parameters["@proj_number"].Value = 9001810;
                cmd.Parameters.Add(new SqlParameter("@usr_id", SqlDbType.Int));
                cmd.Parameters["@usr_id"].Value = 2;
                cmd.Parameters.Add(new SqlParameter("@download", SqlDbType.SmallDateTime));
                cmd.Parameters["@download"].Value = "2009-09-03 16:20:11";
                cmd.Parameters.Add(new SqlParameter("@status", SqlDbType.Int));
                cmd.Parameters["@status"].Value = 2;
    
                conn.Open();
                cmd.Transaction = conn.BeginTransaction();
    
                object _id = cmd.ExecuteScalar();
    
                // _id now contains the value of the Identity column for
                // the row just inserted by proj_ins_all
    
                // Assume (or simulate) an error is raised after the SP is called but
                // before the transaction is committed.
                // (Comment this line out and the DB update succeeds, as expected.)
                throw new Exception();
    
                // If the transaction is not committed, it'll be rolled back when
                // the connection is closed and the inserted row won't be in the
                // table even though the incremented Identity value was returned above.
                cmd.Transaction.Commit();
    
            }
            catch (Exception)
            {
                // "swallow" (i.e. just ignore) any errors raised by the DB code.
                //throw;
            }
        }
    
    }
    

    事务不必显式声明发生。例如,如果您从上面的代码中删除了对 BeginTransaction() 和 Transaction.Commit() 的调用,仍然会有一个隐式事务,理论上可能会被中断并导致回滚。所以你的问题的根源可能比这个例子更微妙,它需要一个显式的事务来展示这个概念。

    更实际的是,您可以(通过 Profiler)查看应用程序发送到 SQL 服务器的实际 SQL,并验证它在从 SSMS 执行时是否有效,因此我认为问题可能出在调用存储过程。

    【讨论】:

    • 我没有在 msdn 上获得 ExecuteIntScalar 的任何点击该死,抱歉!我应该提到这是一个基本上只是调用 SqlCommand.ExecuteScalar() 的方法。很抱歉造成混乱!
    • 就是这样! Helper 类在它的一个函数中缺少一个 cmd.Transaction.Commit()!
    【解决方案2】:

    要么您的事务未提交,要么抛出异常并被吃掉(忽略)。

    【讨论】:

    • 知道为什么它会被忽略吗?我可以打开或关闭 sql server 中的变量以使其不隐藏重要错误吗?
    • 第一件事:确保您提交事务。如果仍然相同,请调试 .net 代码以查看是否抛出异常(在执行期间...(...))。或者只是将您的呼叫包装在 try-catch 中并记录错误。我没有看到 SqlServer 只是默默地失败并且没有通知客户端/调用者。因此,如果它不是 COMMIT,那么它很可能在您的 .NET 代码上,而不是 SQLServer。
    • 对不起,我的错 - 毕竟是交易(或者更确切地说是我的前任的坏事,他将交易用于他的一些方法而不是其他方法,因此让我相信我使用的代码没有使用交易,实际上是)。
    • NP,唯一重要的是您找到了问题所在。干杯。
    【解决方案3】:

    打开 sql server profiler 看看 db 中发生了什么。

    【讨论】:

    • 好主意,谢谢,这很有帮助(请参阅问题更新)。
    【解决方案4】:

    您可能正在回滚交易。这可以解释为什么该行不存在。如果您能够观察到设置了 _id 变量,我认为 proj_ins_all 不会失败。如果抛出异常,则不会发生这种情况。所以一定是事务生效并且正在回滚的情况。

    【讨论】:

    • _id 变量已设置。如果抛出异常,这将不会发生。 - 没错,我也是这么想的,但事实证明这就是发生的事情。非常奇怪的恕我直言(请参阅更新的问题)。
    • 对不起,我的错 - 毕竟是交易(或者更确切地说是我的前任的坏事,他将交易用于他的一些方法而不是其他方法,因此让我相信我使用的代码没有使用交易,实际上是)。
    【解决方案5】:

    如果您在 C# 中有 DateTime 值,它将按原样作为 DATETIME 传递给 SQL Server。

    如果您在 C# 中有一个字符串值要作为 DATETIME 参数传递,它将根据您的操作系统和数据库的语言/文化设置进行一些本地化转换。

    我建议您确保您的代码确实传递了 DateTime 值(您可以在调用 SP 之前检查其正确性)。

    【讨论】:

      【解决方案6】:

      块引用

      将数据类型 varchar 转换为 datetime 时出错。

      由于我是手动测试它,我只是将日期时间从 '2009-09-03 16:20:11.7130000' 缩短到 '2009-09-03 16:20:11' 并修复了错误;现在该行插入正常。

      块引用 这是 SQL 分析器中的一个错误。 http://support.microsoft.com/kb/974289/

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-08-31
        • 1970-01-01
        • 2018-06-03
        • 2011-10-29
        • 1970-01-01
        相关资源
        最近更新 更多