2012-09-28 50 views
2

我創建了一個方法,而回的是:如何使用SqlClient在ADO.NET/C#4.0中解開SQL Server 2008表?

  1. 鎖定表
  2. 讀值從它
  3. 寫道更新值回
  4. 解鎖表

代碼就職於甲骨文。現在我無法得到它的SQL Server 2008的方法如下,並用文本執行的SqlException我的解鎖命令結果的工作:

「NOLOC」是不是一個公認的表提示選項。如果它的目的是作爲一個 參數表值函數或CHANGETABLE函數, 確保您的數據庫兼容模式設置爲90

代碼:

public static int GetAndSetMaxIdTable(DbProviderFactory factory, DbConnection cnctn, DbTransaction txn, int tableId, string userName, int numberOfIds) 
{ 
     bool isLocked = false; 
     string sql = string.Empty; 
     string maxIdTableName; 

     if (tableId == 0) 
      maxIdTableName = "IdMax"; 
     else 
      maxIdTableName = "IdMaxTable"; 

     try 
     { 
      bool noPrevRow = false; 
      int realMaxId; 

      if (factory is OracleClientFactory) 
       sql = string.Format("lock table {0} in exclusive mode", maxIdTableName); 
      else if (factory is SqlClientFactory) 
       sql = string.Format("select * from {0} with (TABLOCKX)", maxIdTableName); 
      else 
       throw new Exception(string.Format("Unsupported DbProviderFactory -type: {0}", factory.GetType().ToString())); 

      using (DbCommand lockCmd = cnctn.CreateCommand()) 
      { 
       lockCmd.CommandText = sql; 
       lockCmd.Transaction = txn; 
       lockCmd.ExecuteNonQuery(); 
       isLocked = true; 
      } 

      using (DbCommand getCmd = cnctn.CreateCommand()) 
      { 
       getCmd.CommandText = CreateSelectCommand(factory, tableId, userName, getCmd, txn); 

       object o = getCmd.ExecuteScalar(); 
       if (o == null) 
       { 
        noPrevRow = true; 
        realMaxId = 0; 
       } 
       else 
       { 
        realMaxId = Convert.ToInt32(o); 
       } 
      } 

      using (DbCommand setCmd = cnctn.CreateCommand()) 
      { 
       if (noPrevRow) 
        setCmd.CommandText = CreateInsertCommand(factory, tableId, userName, numberOfIds, realMaxId, setCmd, txn); 
       else 
        setCmd.CommandText = CreateUpdateCommand(factory, tableId, userName, numberOfIds, realMaxId, setCmd, txn); 

       setCmd.ExecuteNonQuery(); 
      } 
      if (factory is OracleClientFactory) 
       sql = string.Format("lock table {0} in share mode", maxIdTableName); 
      else if (factory is SqlClientFactory) 
       sql = string.Format("select * from {0} with (NOLOC)", maxIdTableName);    

      using (DbCommand lockCmd = cnctn.CreateCommand()) 
      { 
       lockCmd.CommandText = sql; 
       lockCmd.Transaction = txn; 
       lockCmd.ExecuteNonQuery(); 
       isLocked = false; 
      } 

      return realMaxId; 
     } 
     catch (Exception e) 
     { 
      ... 
     } 
} 

那麼什麼這裏出錯了?這個錯誤來自哪裏?服務器或客戶端?我複製了C代碼中的聲明,它應該在那裏工作。不幸的是,我無法調試並檢查它是否適用於我。

編輯:只是想鎖定和解鎖(不讀或更新),在同樣的異常結果。

感謝& BR -Matti

+2

那麼它的一件事NOLOCK。你真的想要鎖定整個表中的其他所有可能的過程嗎?通常做這種事情的方法是在事務中,它可以在Oracle和SQL Server上工作。如果你想要這條路線,你不需要Oracle或SQL Server Specific Sql,並且使用IDbConnection,IDbCommand等,你可以擺脫大部分如果Oracle的東西,並得到接近於提供者不可知論 –

+0

謝謝4你回答託尼。是的,每個試圖訪問表的客戶端都必須被鎖定。我想首先得到這項工作,但我也接受更復雜的方法。如果你不知道爲什麼我會得到這個例外,你可以發表一個例子來說明如何用交易來做到這一點。事實上,我已經在這裏使用了一個交易,但顯然你提出的方法是別的。 –

+0

如果您始終鎖定整個表格,那將會是一個緩慢的應用程序。 –

回答

2

的TABLOCKX提示鎖定表爲你打算,但你不能手動解鎖。鎖持續時間取決於您的交易級別。如果您的連接沒有活動事務,則在SELECT執行時保持鎖定狀態,並在此後丟棄。

如果你想實現序列「鎖表 - >做了桌上的東西 - >解除鎖定」,你將需要實現ADO.NET相當於這個T-SQL腳本:

BEGIN TRAN 
    SELECT TOP (1) 1 FROM myTable (TABLOCKX, KEEPLOCK) 
    -- do something with the table 
COMMIT -- This will release the lock, if there is no outer transaction present 

您可以執行「BEGIN TRAN」 /「提交」通過的DbCommand對象,或者您可以使用System.Data.SqlClient.SqlTransaction類來啓動一個事務並提交。

注意:此方法僅如果您的連接不是在一個事務中已經工作! SQL Server不支持嵌套事務,所以COMMIT不會執行任何操作,鎖定將被保留。如果您的事務已經在運行,那麼在事務結束之前您無法釋放該鎖。在這種情況下,通過sp_getapplock/sp_releaseapplock進行同步可能會有所幫助。

編輯:如果你要教育自己的交易,鎖定和攔截,我建議這兩個視頻:http://technet.microsoft.com/en-us/sqlserver/gg545007.aspxhttp://technet.microsoft.com/en-us/sqlserver/gg508892.aspx

+0

這是鬼鬼祟祟的,不能說我會想到這一點,但我會試圖設計任何方式的獨家鎖的需要。 –

+0

@Toni:謝謝你的回答!我明天在工作中試着放棄「解鎖」部分並提交/回滾DbTransaction。很高興知道這個限制,因爲它是一個。感謝您在這裏添加它,所以我不必知道出了什麼問題。我可以嘗試的時候接受這個。再次感謝! –

+0

@Tony:我認爲你是對的。我實際上是模仿傳統的C代碼,這總是一個壞主意。我很想聽聽你如何做到這一點。我對SQL Server的知識和DB中的「固有鎖定」通常是零。通過固有的鎖定,我的意思是當我打開光標/開始事務並關閉/結束它們(如果在這種或其他情況下甚至發生任何類型的鎖定)時會發生什麼。 –

0

下面是對的SqlClient有一個代碼表的回答我提出基於TToni的回答是:

public static int GetAndSetMaxIdTable(DbProviderFactory factory, DbConnection cnctn, DbTransaction txn, int numberOfIds) 
    { 
      bool noPrevRow = false; 
      int realMaxId; 


      using (DbCommand getCmd = cnctn.CreateCommand()) 
      { 
       getCmd.CommandText = "SELECT MaxId FROM IdMax WITH (TABLOCKX)" 
       getCmd.Transaction = txn; 

       object o = getCmd.ExecuteScalar(); 
       if (o == null) 
       { 
        noPrevRow = true; 
        realMaxId = 0; 
       } 
       else 
       { 
        realMaxId = Convert.ToInt32(o); 
       } 
      } 

      using (DbCommand setCmd = cnctn.CreateCommand()) 
      { 
       if (noPrevRow) 
        setCmd.CommandText = CreateInsertCommand(factory, tableId, userName, numberOfIds, realMaxId, setCmd, txn); 
       else 
        setCmd.CommandText = CreateUpdateCommand(factory, tableId, userName, numberOfIds, realMaxId, setCmd, txn); 

       setCmd.ExecuteNonQuery(); 
      } 

      return realMaxId; 
    } 

而且我是這樣的:

 ... 

     try 
     { 
      using (txn = cnctn.BeginTransaction()) 
      { 
       oldMaxId = GetAndSetMaxIdTable(factory, cnctn, txn, 5); 
       for (i = 0; i < 5; i++) 
       { 
        UseNewIdToInsertStuff(factory, cnctn, txn, oldMaxId + i + 1) 
       } 
       txn.Commit(); 
       return true; 
      } 
     } 
     catch (Exception e) 
     { 
      // don't know if this is needed 
      if (txn != null && cnctn.State == ConnectionState.Open) 
       txn.Rollback(); 

      throw e; 
     } 

     ... 

對於Oracle客戶端這似乎是希望有:

SELECT MaxId from IdMax WHERE ... FOR UPDATE OF MaxId 

-m