2010-09-28 138 views
1

Hallo,Linq to SQL併發問題

我有可以調用多個方法的web服務。每次調用其中一種方法時,我都會將調用記錄到統計數據庫中,以便我們知道每個方法每個月調用多少次以及平均處理時間。

每次我登錄統計數據時,首先檢查數據庫以查看當前月份的方法是否已經存在,如果不存在,則創建並添加該行。如果它已經存在,我將需要的列更新到數據庫。

我的問題是,有時當我更新一行時,我得到「行未找到或更改」異常,是的,我知道這是因爲該行已被修改,因爲我讀它。

爲了解決這個問題我已經嘗試使用沒有成功如下:使用在我的DataContext

  • 使用。
  • 在TransactionScope中使用。
  • 使用互斥量,這是行不通的,因爲Web服務是(不確定我稱之爲正確的想法)複製出來在不同的PC上的性能,但仍然使用相同的數據庫。
  • 解決異常中的併發衝突,這不起作用,因爲我需要獲取新的數據庫值併爲其添加值。

下面我添加了用於記錄統計數據的代碼。任何幫助將非常感激。

public class StatisticsGateway : IStatisticsGateway 
{ 
    #region member variables 
    private StatisticsDataContext db; 
    #endregion 

    #region Singleton 
    [ThreadStatic] 
    private static IStatisticsGateway instance; 
    [ThreadStatic] 
    private static DateTime lastEntryTime = DateTime.MinValue; 

    public static IStatisticsGateway Instance 
    { 
     get 
     { 
      if (!lastEntryTime.Equals(OperationState.EntryTime) || instance == null) 
      { 
       instance = new StatisticsGateway(); 
       lastEntryTime = OperationState.EntryTime; 
      } 

      return instance; 
     } 
    } 
    #endregion 

    #region constructor/initialize 
    private StatisticsGateway() 
    { 
     var configurationAppSettings = new System.Configuration.AppSettingsReader(); 
     var connectionString = ((string)(configurationAppSettings.GetValue("sqlConnection1.ConnectionString", typeof(string)))); 

     db = new StatisticsDataContext(connectionString); 
    } 
    #endregion 

    #region IStatisticsGateway members 
    public void AddStatisticRecord(StatisticRecord record) 
    { 
     using (db) 
     { 
      var existing = db.Statistics.SingleOrDefault(p => p.MethodName == record.MethodName && 
                   p.CountryID == record.CountryID && 
                   p.TokenType == record.TokenType && 
                   p.Year == record.Year && 
                   p.Month == record.Month); 

      if (existing == null) 
      { 
       //Add new row 
       this.AddNewRecord(record); 
       return; 
      } 

      //Update 
      existing.Count += record.Count; 
      existing.TotalTimeValue += record.TotalTimeValue; 

      db.SubmitChanges(); 
     } 
    } 

回答

1

我會建議讓SQL Server處理併發。

方法如下:

  1. 創建一個接受你的日誌值(方法名,月/日,並執行統計數據)作爲參數的存儲過程。

  2. 在存儲過程中,任何事情之前,得到應用程序鎖定作爲described hereand here。現在您可以確定只有一個存儲過程實例會一次運行。 (免責聲明!我還沒有嘗試過sp_getapplock自己。只是說,但似乎相當簡單,因爲所有的interwebs例子在那裏。)

  3. 接下來,在存儲過程中,查詢日誌表的當前月份的方法來確定是插入還是更新,然後執行插入或更新。

  4. 如您所知,在VS中,您可以將存儲過程從服務器資源管理器拖動到DBML設計器中,以便使用LINQ to SQL輕鬆訪問。

如果你想避免存儲過程,那麼這個解決方案顯然不適合你,但這是我如何輕鬆快速地解決它。希望能幫助到你!

+0

感謝您的回覆。我通過創建一個存儲過程來解決問題!像魅力一樣工作 – Michael 2010-09-29 12:34:35

+0

非常棒!只是好奇,你在存儲過程中是否打擾了sp_getapplock? (因爲只是將邏輯移動到存儲過程將大大降低併發問題的機會。) – shaunmartin 2010-09-29 15:14:55

+0

我使用了HOLDLOCK和ROWLOCK,請檢查我的答案。 – Michael 2010-09-30 10:56:36

0

如果你不想使用存儲過程的方法,處理它的一種粗糙的方式就是重試那個特定的異常。 E.g:

int maxRetryCount = 5; 
for (int i = 0; i < maxRetryCount; i++) 
{ 
    try 
    { 
    QueryAndUpdateDB(); 
    break; 
    } 
    catch(RowUpdateException ex) 
    { 
    if (i == maxRetryCount) throw; 
    } 
} 
0

我沒有用過sp_getapplock,而不是我用HOLDLOCK和ROWLOCK,如下圖所示:

CREATE PROCEDURE [dbo].[UpdateStatistics] 
@MethodName as varchar(50) = null, 
@CountryID as varchar(2) = null, 
@TokenType as varchar(5) = null, 
@Year as int, 
@Month as int, 
@Count bigint, 
@TotalTimeValue bigint 

AS BEGIN SET NOCOUNT ON;

BEGIN TRAN 

    UPDATE dbo.[Statistics] 
    WITH (HOLDLOCK, ROWLOCK) 
    SET Count = Count + @Count 
    WHERE [email protected] and [email protected] and [email protected] and [email protected] and [email protected] 
    IF @@ROWCOUNT=0 
     INSERT INTO dbo.[Statistics] (MethodName, CountryID, TokenType, TotalTimeValue, Year, Month, Count) values (@MethodName, @CountryID, @TokenType, @TotalTimeValue, @Year, @Month, @Count) 

COMMIT TRAN 

END GO

我已經通過調用多個線程我的web服務方法測試,同時它並記錄每次通話沒有任何問題。