0

我有一个名为的存储过程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

它应该返回 ID 号,但在 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 测试来验证功能,但在调试模式下我得到返回值 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();

我在 SQL Server 中的表如下所示:

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

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

预先感谢您的帮助。

如果有帮助,我有我在邮递员中收到的错误的屏幕截图,请单击此处

我的邮政编码:

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

我在调试时遇到一条错误消息,上面写着“System.Data.SqlClient.SqlException:'子查询返回的值超过 1 个。当子查询遵循 =、!=、<、<=、>、>= 时,这是不允许的或当子查询用作表达式时。语句已终止。'"

这是我的 VS 在调试模式下的图像:

这只是在黑暗中拍摄,但我的问题可能与我插入新导演的子查询有关吗?

4

4 回答 4

1

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

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

或将其分配给dvdItem.DvdId = dvdId;.

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

于 2017-10-21T19:15:02.410 回答
0

如果我没记错的话,如果您尝试通过参数返回值,那么您需要在 SqlCommand 中使用输出参数并在执行查询后读取它。如果您想使用 ExecuteScalar,那么您的 SQL 的最后一行只需是“SELECT SCOPE_IDENTITY()”,那么它将在我们的“对象 a”中。我还没有运行它来尝试它,所以请让我知道这是否有效。

于 2017-10-21T18:56:47.203 回答
0

听起来你的问题比例外更深一点,更多的是关于你的方法。

  1. 您插入到 Director 表中,而不首先检查该直接是否已经存在。这可能会创建重复项,这意味着您的第一个子查询可能会返回多个结果。也许更改查询以检查直接是否存在是合适的,如果存在,则使用 ID,否则插入并从 scope_identity() 获取它

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

所以看起来你有问题的组合,我的第一个答案将解决没有返回的返回值,这个答案应该可以帮助你解决异常,还可以帮助你创建一个更有效的解决方案。

于 2017-10-21T19:40:55.407 回答
0

I believe this may be my issue:

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)

The select DirectorId subquery is returning multiple values as is the RatingId select statement which would explain the exception.

I still need to test this theory, though

UPDATE...

So it turns out my theory was correct

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)

adding TOP 1 to the select statement only returns 1 value. This may not be the best in terms of coding best practices, but with a very basic understanding of SQL its the best solution I have right now.

于 2017-10-21T19:05:46.310 回答