2017-10-21 90 views
0

我有一个存储过程调用DvdInsert看起来像这样:的SQL Server SCOPE_IDENTITY存储过程返回null在C#

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

我不明白为什么我没有从s中获得回报价值tored程序。有任何想法吗?我是初学者,所以如果愿意,请打破你的解释。

非常感谢您的帮助。

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

我邮编:

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

我碰到一个错误信息,同时调试,上面写着:“System.Data.SqlClient.SqlException:“子查询返回多个值这是当子查询如下=,!=,<,< =,>,> =,或当子查询用作表达。 该语句已终止。”'

不允许

这只是一个黑暗中的镜头,但我的问题可能与我插入新导演的子查询有关吗?

+0

你为什么试图在单元测试中与数据库交谈? – Shyju

+0

@Shyju,这是错误的吗?我的答案会是因为我的教练向我展示了这种方式并用于测试目的,但通过您的问题,我认为这是错误的方式? – Student

+0

为什么你不能在sp中选择scope_identity()并调用sp来理解它是否返回正确的值。 –

回答

0

听起来像你的问题有点深刻,只是异常和更多关于你的方法。

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

  2. 而不是将评级名称传递给过程,它将是要更好地传递评级ID。这意味着第二个子查询将不再需要,并且通过查找ID而不是评级名称,也会更加高效。

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

+0

非常感谢你的优秀建议,我一定会将它们应用到我的项目中。我并没有考虑验证导演是否先存在。非常感谢您的建议。 – Student

+0

我的荣幸和感谢我的第一个确认答案。我一直在使用这个网站多年来得到建议,并意识到是时候我来这里回答问题了! :) – Toad

0

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

+0

谢谢您的回答,我正在尝试您的建议,但我似乎也遇到了同样的错误 – Student

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) 

的选择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的基本理解是我现在最好的解决方案。

+0

'TOP 1'隐藏了问题,但并未真正解决问题,因为DVD可能与错误相关联导演或评级。真正的解决方案是从Director和/或Rating表中删除重复的行,并在自然键列上创建主键或唯一约束,以防止未来的发展。 –

+0

@丹Guzman,优点,我会改变我的数据库,以防止重复。谢谢你让我意识到这个问题 – Student

1

你叫ExecuteNonQuery()方法后,您就需要得到输出中参数的值,这样阅读:

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

或指定给dvdItem.DvdId = dvdId;

顺便说一下,您的测试是一个集成测试,而不是单元测试。即使对于集成测试,它也非常脆弱,因为DVD ID不一定是4,所以它会失败。尽管如此,它优于使用调试器进行手动测试。

+0

谢谢你的建议,我已经应用你的建议来获得输出参数的价值,我一定会修改我的测试,使其不那么具体和更强 – Student