SQL Server Scope_identity存储过程在C#中返回null

时间:2017-10-21 18:14:32

标签: c# sql sql-server c#-4.0 scope-identity

我有一个名为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)
)

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

提前感谢您的帮助。

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:' Subquery返回的值超过1。当子查询遵循=,!=,<,< =,>,> =或子查询用作表达式时,不允许这样做。 该声明已被终止。'"

here is the image of my VS in debug mode:

这只是一个黑暗的镜头,但我的问题可能在于我插入一个新导演的子查询?

4 个答案:

答案 0 :(得分:1)

调用ExecuteNonQuery()方法之后,需要获取ouput参数的值并按以下方式读取:

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

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

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

答案 1 :(得分:0)

如果我没记错的话,如果您尝试通过参数返回值,那么您需要在SqlCommand中使用输出参数并在执行查询后读取它。如果你想使用ExecuteScalar,那么SQL的最后一行只需要" SELECT SCOPE_IDENTITY()"然后,这将是我们的对象"。 我没试过这个尝试,所以如果这样做,请告诉我。

答案 2 :(得分:0)

我相信这可能是我的问题:

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语句。

我仍然需要测试这个理论,不过

... UPDATE

事实证明我的理论是正确的

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语句只返回1个值。在编写最佳实践方面,这可能不是最好的,但是对SQL的基本理解是我现在拥有的最佳解决方案。

答案 3 :(得分:0)

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

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

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

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