SQL Server和.NET:在代码中插入失败(静默!),但在手动运行时则失败

时间:2009-09-03 05:47:07

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

我的插入存储过程:

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

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

我能想到的最好的猜测是一个插入触发器,它会删除新插入的行,但是表上没有触发器(为什么手动完成时它会工作呢?)。我的其他尝试是猜测存储过程以某种方式回滚插入,因此将开始结束结束并且分号进入存储过程以正确分隔'insert'和'identity select'位。没有任何修复。

有什么想法吗?

更新

感谢所有迄今为止帮助过的人。关于Preet的建议(第一个答案)我学会了如何使用SQL Server Profiler(我简直不敢相信我以前从未了解过它 - 我认为它只对性能调优有用,没有意识到我可以确切地看到查询的内容带它的DB)。

它显示SqlCommand.ExecuteScalar()方法发送的SQL与我手动运行的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在该datetime参数中不能处理超过23个字符?这是Microsoft的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是可以为空的DateTime。请注意,此行中的sql类型曾经是SqlDbType.DateTime,但我将其更改为SqlDbType.SmallDateTime以生成一个手动工作的调用(从代码运行时仍然失败)。

更新:已解决 感谢Matt,我终于找到并修复了问题:自定义助手类在其中一个函数中缺少cmd.Transaction.Commit()!它有一堆不同的杂乱内容,我最初在完成代码时错过了这个。

非常感谢Matt和Preet以及其他所有贡献者。

经验教训
- SQL事件探查器非常有用。花点时间学习如何使用它 - 如果数据库吞咽错误,或者根据您的代码似乎正在执行某些操作但实际上并未出现在数据库中,请检查交易是否已提交。
- 不要如此信任别人写的数据库帮助程序类。甚至一开始看起来很简单。确保你完全明白它的作用,因为它可能是错误的,或者只是不按照你认为它们总是完成的方式做事。

6 个答案:

答案 0 :(得分:6)

只要尝试插入,标识列就会递增,即使失败也是如此,因此该部分不一定是异常的。

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

编辑:

我认为面包车在未提交的交易方面处于正确的轨道上。它就像你正在使用某种自定义帮助程序类来管理对数据库中的存储过程的调用(以及其他数据库访问,可能),并且可能是这段代码吞噬了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执行时是否有效,因此这让我觉得问题可能来自调用该应用程序的应用程序代码存储过程。

答案 1 :(得分:6)

您的交易未提交,或者抛出异常并且只是被吃掉(被忽略)。

答案 2 :(得分:3)

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

答案 3 :(得分:1)

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

答案 4 :(得分:0)

如果你在C#中有一个DateTime值,它将作为DATETIME传递给SQL Server。

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

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

答案 5 :(得分:0)

  

块引用

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

由于我是手动测试的,我只是将日期时间从'2009-09-03 16:20:11.7130000'缩短为'2009-09-03 16:20:11',这就解决了错误;现在排好了。

  

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