2010-09-28 140 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服务方法测试,同时它并记录每次通话没有任何问题。