Linq to sql Linq到SQL并发问题

Linq to sql Linq到SQL并发问题,linq-to-sql,concurrency,Linq To Sql,Concurrency,你好 我有一个可以调用多个方法的web服务。每次调用其中一个方法时,我都会记录对统计数据库的调用,这样我们就知道每个方法每月调用多少次以及平均处理时间 每次记录统计数据时,我都会首先检查数据库,看看当前月份的方法是否已经存在,如果不存在,则创建并添加行。如果它已经存在,我会将所需的列更新到数据库中 我的问题是,有时当我更新一行时,我会得到“未找到或更改的行”异常,是的,我知道这是因为我读取该行后该行已被修改 为了解决这个问题,我尝试使用以下方法,但没有成功: 在我的datacontext周围使

你好

我有一个可以调用多个方法的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();
        }
    }

我建议让SQL Server处理并发性。

以下是方法:

  • 创建一个接受日志值(方法名、月/日和执行统计信息)作为参数的存储过程

  • 在存储过程中,在执行任何其他操作之前,先获取应用程序锁。现在可以确保一次只运行一个存储过程实例。(免责声明!我自己没有尝试过
    sp_getapplock
    。只是说说而已。但考虑到互联网上的所有示例,这似乎相当简单。)

  • 接下来,在存储过程中,查询日志表中当前月份的方法条目,以确定是插入还是更新,然后执行插入或更新

  • 您可能知道,在VS中,您可以将存储过程从服务器资源管理器拖到DBML设计器中,以便使用LINQtoSQL轻松访问


  • 如果您试图避免存储过程,那么这个解决方案显然不适合您,但这是我轻松快速地解决它的方法。希望有帮助

    我建议让SQL Server处理并发性。

    以下是方法:

  • 创建一个接受日志值(方法名、月/日和执行统计信息)作为参数的存储过程

  • 在存储过程中,在执行任何其他操作之前,先获取应用程序锁。现在可以确保一次只运行一个存储过程实例。(免责声明!我自己没有尝试过
    sp_getapplock
    。只是说说而已。但考虑到互联网上的所有示例,这似乎相当简单。)

  • 接下来,在存储过程中,查询日志表中当前月份的方法条目,以确定是插入还是更新,然后执行插入或更新

  • 您可能知道,在VS中,您可以将存储过程从服务器资源管理器拖到DBML设计器中,以便使用LINQtoSQL轻松访问


  • 如果您试图避免存储过程,那么这个解决方案显然不适合您,但这是我轻松快速地解决它的方法。希望有帮助

    如果您不想使用存储过程方法,那么处理它的一种粗略方法就是在特定异常上重试。例如:

    int maxRetryCount = 5;
    for (int i = 0; i < maxRetryCount; i++)
    {
       try
       {
         QueryAndUpdateDB();
         break;
       }
       catch(RowUpdateException ex)
       {
         if (i == maxRetryCount) throw;
       }
    }
    
    int maxRetryCount=5;
    对于(int i=0;i
    如果您不想使用存储过程方法,处理它的一种粗略方法就是在特定异常上重试。例如:

    int maxRetryCount = 5;
    for (int i = 0; i < maxRetryCount; i++)
    {
       try
       {
         QueryAndUpdateDB();
         break;
       }
       catch(RowUpdateException ex)
       {
         if (i == maxRetryCount) throw;
       }
    }
    
    int maxRetryCount=5;
    对于(int i=0;i
    我没有使用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 
    
    作为 开始 不计数

    BEGIN TRAN
    
        UPDATE dbo.[Statistics]
        WITH (HOLDLOCK, ROWLOCK)
        SET Count = Count + @Count
        WHERE MethodName=@MethodName and CountryID=@CountryID and TokenType=@TokenType and Year=@Year and Month=@Month
        IF @@ROWCOUNT=0
            INSERT INTO dbo.[Statistics] (MethodName, CountryID, TokenType, TotalTimeValue, Year, Month, Count) values (@MethodName, @CountryID, @TokenType, @TotalTimeValue, @Year, @Month, @Count)
    
    COMMIT TRAN
    
    结束 去


    我通过多个线程同时调用我的web服务方法进行了测试,每次调用都记录在案,没有出现任何问题。

    我没有使用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 
    
    作为 开始 不计数

    BEGIN TRAN
    
        UPDATE dbo.[Statistics]
        WITH (HOLDLOCK, ROWLOCK)
        SET Count = Count + @Count
        WHERE MethodName=@MethodName and CountryID=@CountryID and TokenType=@TokenType and Year=@Year and Month=@Month
        IF @@ROWCOUNT=0
            INSERT INTO dbo.[Statistics] (MethodName, CountryID, TokenType, TotalTimeValue, Year, Month, Count) values (@MethodName, @CountryID, @TokenType, @TotalTimeValue, @Year, @Month, @Count)
    
    COMMIT TRAN
    
    结束 去


    我通过多线程同时调用我的web服务方法进行了测试,每次调用都记录在案,没有任何问题。

    感谢您的回复。我通过创建存储过程解决了这个问题!工作起来像个魔术师!只是好奇,您是否在存储过程中使用了sp_getapplock?(因为将逻辑移动到存储过程将大大减少并发问题的发生。)我使用了HOLDLOCK和ROWLOCK,请检查我的答案。感谢您的回复。我通过创建存储过程解决了这个问题!工作起来像个魔术师!只是好奇,您是否在存储过程中使用了sp_getapplock?(因为将逻辑移动到存储过程将大大减少并发问题的发生。)我使用了HOLDLOCK和ROWLOCK,请检查我的答案。