SQL 服务器 Scope_identity 在 C# 中返回 null 的存储过程

SQL Server Scope_identity stored procedure returning null in C#

我有一个名为 DvdInsert 的存储过程,如下所示:

IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.ROUTINES
          WHERE ROUTINE_NAME = 'DvdInsert')
    DROP PROCEDURE DvdInsert
GO

CREATE PROCEDURE DvdInsert 
     (@RatingName char(10),
      @FName nvarchar(30),
      @LName nvarchar(30),
      @Title nvarchar(125),
      @ReleaseYear int,
      @Notes nvarchar(150),
      @DvdId int OUTPUT)
AS
BEGIN
    INSERT INTO Director (FName, LName)
    VALUES (@FName, @LName)

    INSERT INTO Dvd (DirectorId, RatingId, Title, ReleaseYear, Notes)
    VALUES ((SELECT DirectorId
             FROM Director
             WHERE FName = @FName AND LName = @LName),
            (SELECT RatingId
             FROM Rating
             WHERE RatingName = @RatingName), @Title, @ReleaseYear, @Notes)

    SET @DvdId = CAST(SCOPE_IDENTITY() AS INT);
END
GO

它应该 return 身份证号码,但在 Visual Studio 2017 我有代码:

public int Insert(DvdItem dvdItem)
{
    using (var cn = new SqlConnection(Settings.GetConnectionString()))
    {
        SqlCommand cmd = new SqlCommand("DvdInsert", cn);
        cmd.CommandType = CommandType.StoredProcedure;

        SqlParameter param = new SqlParameter("@DvdId", SqlDbType.Int);
        param.Direction = ParameterDirection.Output;
        cmd.Parameters.Add(param);

        string[] names = dvdItem.Director.ToString().Trim().Split(new char[] 
        { ' ' }, 2);

        if (names.Length == 1)
        {
            cmd.Parameters.AddWithValue("FName", "");
            cmd.Parameters.AddWithValue("LName", names[0]);
        }
        else
        {
            cmd.Parameters.AddWithValue("FName", names[0]);
            cmd.Parameters.AddWithValue("LName", names[1]);
        }

        cmd.Parameters.AddWithValue("RatingName", dvdItem.Rating);
        cmd.Parameters.AddWithValue("Title", dvdItem.Title);
        cmd.Parameters.AddWithValue("ReleaseYear", dvdItem.RealeaseYear);
        cmd.Parameters.AddWithValue("Notes", dvdItem.Notes);

        cn.Open();

        int i = 0;
        object a = cmd.ExecuteScalar();

        if (a != null)
            i = (int)a;

        if (cn.State == System.Data.ConnectionState.Open)
            cn.Close();

        return i;
    }
}

我有一个 Nunit 测试来验证功能,但在调试模式下我得到 returned 值 0 而不是 4

我的测试代码:

[Test]
public void CanAddDvd()
{
        DvdItem dvdItem = new DvdItem();
        var repo = new DvdRepositoryADO();

        dvdItem.Rating = "R";
        dvdItem.Director = "Hello";
        dvdItem.Title = "World";
        dvdItem.RealeaseYear = "2004";
        dvdItem.Notes = "TESTING";

        repo.Insert(dvdItem);

        Assert.AreEqual(4, dvdItem.DvdId);
}

在我添加之前:

int i = 0;
object a = cmd.ExecuteScalar();

if (a != null)
    i = (int)a;

if (cn.State == System.Data.ConnectionState.Open)
    cn.Close();

我在这里遇到空引用异常:

object a = cmd.ExecuteScalar();

我的 table 在 SQL 服务器看起来如下:

CREATE TABLE Dvd 
(
    DvdId INT NOT NULL IDENTITY(1,1),
    DirectorId INT NOT NULL,
    RatingId INT NOT NULL,
    Title NVARCHAR(125) NOT NULL,
    ReleaseYear int NOT NULL,
    Notes VARCHAR(150) NULL,

    CONSTRAINT PK_Dvd_DvdId PRIMARY KEY (DvdId),
    CONSTRAINT FK_Dvd_DirectorId
        FOREIGN KEY (DirectorId) REFERENCES Director(DirectorId),
    CONSTRAINT FK_Dvd_RatingId
        FOREIGN KEY (RatingId) REFERENCES Rating(RatingId)
)

我不明白为什么我没有从存储过程中获取 return 值。有任何想法吗?我是初学者,所以如果愿意,请分解您的解释。

提前感谢您的帮助。

I have a screenshot of the error I receive in postman if that helps click here

我的POST代码:

[Route("dvd/")]
[AcceptVerbs("POST")]
public IHttpActionResult Add(DvdItem dvdItem)
{
        repo.Insert(dvdItem);
        return Created($"dvd/{dvdItem.DvdId}", dvdItem);
}

我在调试时遇到一条错误消息,“System.Data.SqlClient.SqlException:'子查询 returned 超过 1 个值。当子查询跟随 =, !=, < 时,这是不允许的, <= , >, >= 或当子查询用作表达式时。 该语句已被终止。'""

here is the image of my VS in debug mode:

这只是瞎猜,但我的问题可能出在我插入新导演的子查询上吗?

如果我没记错的话,如果您试图通过参数 return 一个值,那么您需要在 SqlCommand 中使用输出参数并在执行查询后读取它。如果你想使用 ExecuteScalar,那么你的 SQL 的最后一行只需 "SELECT SCOPE_IDENTITY()" 那么我们的 "object a" 就会出现。 我还没有 运行 这个来尝试,所以请告诉我这是否有效。

我认为这可能是我的问题:

INSERT INTO Dvd (DirectorId, RatingId, Title, ReleaseYear, Notes)
VALUES ((SELECT DirectorId
         FROM Director
         WHERE FName = @FName AND LName = @LName),
        (SELECT RatingId
         FROM Rating
         WHERE RatingName = @RatingName), @Title, @ReleaseYear, @Notes)

select DirectorId 子查询返回多个值 RatingId select 语句也是如此,它可以解释异常。

不过我还需要验证这个理论

更新...

原来我的理论是正确的

INSERT INTO Dvd (DirectorId, RatingId, Title, ReleaseYear, Notes)
VALUES ((SELECT TOP 1 DirectorId
         FROM Director
         WHERE FName = @FName AND LName = @LName),
        (SELECT TOP 1 RatingId
         FROM Rating
         WHERE RatingName = @RatingName), @Title, @ReleaseYear, @Notes)

仅将 TOP 1 添加到 select 语句 returns 1 个值。就编码最佳实践而言,这可能不是最好的,但如果对 SQL 有非常基本的了解,它是我目前拥有的最佳解决方案。

调用完ExecuteNonQuery()方法后,需要获取输出参数的值,像这样读取:

int dvdID = 
     Convert.ToInt32(cmd.Parameters["@DvdId"].Value);

或分配给dvdItem.DvdId = dvdId;.

顺便说一句,您的测试是集成测试而不是单元测试。即使对于集成测试,它也非常脆弱,因为 dvd id 不会总是 4,所以它会失败。尽管如此,它还是比使用调试器手动测试要好。

听起来你的问题比异常更深一些,更多的是关于你的方法。

  1. 您在没有首先检查该指令是否已经存在的情况下插入到导向器 table。这会创建重复项,这意味着您的第一个子查询可能 return 多个结果。也许更改查询以检查直接是否存在是合适的,如果存在,则使用 ID,否则插入并从 scope_identity()

  2. 获取它
  3. 与其将评级名称传递给过程,不如传递评级 ID 会好得多。这意味着不需要第二个子查询,通过 ID 而不是评级名称查找,也会更有效率。

所以看起来您遇到了一系列问题,我的第一个答案将解决 return 值未被 returned 并且此答案应该可以帮助您解决异常并帮助您创建一个更有效的解决方案。