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

MGO*_*wen 8 .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()
Run Code Online (Sandbox Code Playgroud)

...手动调用时运行正常,如下所示:

exec proj_ins_all 9001210, 2, '2009-09-03', 2 
Run Code Online (Sandbox Code Playgroud)

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

_id = data.ExecuteIntScalar("proj_ins_all", arrParams);
Run Code Online (Sandbox Code Playgroud)

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

我能想到的最好的猜测是一个插入触发器,它删除了新插入的行,但是表上没有触发器(为什么它会在手动完成时工作呢?).我的其他尝试是围绕猜测存储过程以某种方式回滚插入,因此将开始结束并且分号和分号放入存储过程以正确分离"插入"和"身份选择"位.没有修复.

有任何想法吗?

更新:

谢谢所有迄今为止帮助过的人.关于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
Run Code Online (Sandbox Code Playgroud)

我手动跑了,瞧!实际的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),
Run Code Online (Sandbox Code Playgroud)

和MakeInParam有这一行:

param = new SqlParameter(paramName, DbType);
Run Code Online (Sandbox Code Playgroud)

它会像这样执行:

SqlCommand cmd = CreateCommand(procName, prams);
object result = cmd.ExecuteScalar();
Run Code Online (Sandbox Code Playgroud)

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

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

感谢Matt和Preet以及其他所有贡献者.

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

Mat*_*att 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;
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

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

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


van*_*van 6

您的事务未提交或抛出异常并且只是被吃掉(被忽略).