2017-10-21 137 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