【发布时间】:2014-06-21 12:29:33
【问题描述】:
我有一个基于 Dynamics CRM 2011 的大型应用程序,它在各个地方都有代码,必须根据某些条件查询记录,如果不存在则创建它,否则更新它。
我所说的这种事情的一个例子类似于这样:
stk_balance record = context.stk_balanceSet.FirstOrDefault(x => x.stk_key == id);
if(record == null)
{
record = new stk_balance();
record.Id = Guid.NewGuid();
record.stk_value = 100;
context.AddObject(record);
}
else
{
record.stk_value += 100;
context.UpdateObject(record);
}
context.SaveChanges();
就 CRM 2011 实施而言(尽管与此问题不严格相关),代码可以从同步或异步插件触发。问题是代码不是线程安全的,在检查记录是否存在和创建记录之间,另一个线程可能会进入并首先执行相同的操作,从而导致重复记录。
由于系统架构的原因,普通锁定方法不可靠,使用多个线程的各种服务可能都使用相同的代码,并且这些多个服务也在多台机器之间进行负载平衡。
在试图找到解决这个问题的方法时,它不会增加大量额外的复杂性,也不会影响不存在单点故障或可能发生瓶颈的单点的想法,我遇到了使用 SQL Server 应用程序锁的想法。
我想出了以下课程:
public class SQLLock : IDisposable
{
//Lock constants
private const string _lockMode = "Exclusive";
private const string _lockOwner = "Transaction";
private const string _lockDbPrincipal = "public";
//Variable for storing the connection passed to the constructor
private SqlConnection _connection;
//Variable for storing the name of the Application Lock created in SQL
private string _lockName;
//Variable for storing the timeout value of the lock
private int _lockTimeout;
//Variable for storing the SQL Transaction containing the lock
private SqlTransaction _transaction;
//Variable for storing if the lock was created ok
private bool _lockCreated = false;
public SQLLock (string lockName, int lockTimeout = 180000)
{
_connection = Connection.GetMasterDbConnection();
_lockName = lockName;
_lockTimeout = lockTimeout;
//Create the Application Lock
CreateLock();
}
public void Dispose()
{
//Release the Application Lock if it was created
if (_lockCreated)
{
ReleaseLock();
}
_connection.Close();
_connection.Dispose();
}
private void CreateLock()
{
_transaction = _connection.BeginTransaction();
using (SqlCommand createCmd = _connection.CreateCommand())
{
createCmd.Transaction = _transaction;
createCmd.CommandType = System.Data.CommandType.Text;
StringBuilder sbCreateCommand = new StringBuilder();
sbCreateCommand.AppendLine("DECLARE @res INT");
sbCreateCommand.AppendLine("EXEC @res = sp_getapplock");
sbCreateCommand.Append("@Resource = '").Append(_lockName).AppendLine("',");
sbCreateCommand.Append("@LockMode = '").Append(_lockMode).AppendLine("',");
sbCreateCommand.Append("@LockOwner = '").Append(_lockOwner).AppendLine("',");
sbCreateCommand.Append("@LockTimeout = ").Append(_lockTimeout).AppendLine(",");
sbCreateCommand.Append("@DbPrincipal = '").Append(_lockDbPrincipal).AppendLine("'");
sbCreateCommand.AppendLine("IF @res NOT IN (0, 1)");
sbCreateCommand.AppendLine("BEGIN");
sbCreateCommand.AppendLine("RAISERROR ( 'Unable to acquire Lock', 16, 1 )");
sbCreateCommand.AppendLine("END");
createCmd.CommandText = sbCreateCommand.ToString();
try
{
createCmd.ExecuteNonQuery();
_lockCreated = true;
}
catch (Exception ex)
{
_transaction.Rollback();
throw new Exception(string.Format("Unable to get SQL Application Lock on '{0}'", _lockName), ex);
}
}
}
private void ReleaseLock()
{
using (SqlCommand releaseCmd = _connection.CreateCommand())
{
releaseCmd.Transaction = _transaction;
releaseCmd.CommandType = System.Data.CommandType.StoredProcedure;
releaseCmd.CommandText = "sp_releaseapplock";
releaseCmd.Parameters.AddWithValue("@Resource", _lockName);
releaseCmd.Parameters.AddWithValue("@LockOwner", _lockOwner);
releaseCmd.Parameters.AddWithValue("@DbPrincipal", _lockDbPrincipal);
try
{
releaseCmd.ExecuteNonQuery();
}
catch {}
}
_transaction.Commit();
}
}
我将在我的代码中使用它来创建一个 SQL Server 应用程序锁,使用我正在查询的唯一键作为锁名称,如下所示
using (var sqlLock = new SQLLock(id))
{
//Code to check for and create or update record here
}
现在这种方法似乎可行,但我绝不是任何类型的 SQL Server 专家,并且对将它放在生产代码附近的任何地方持谨慎态度。
我的问题真的有 3 个部分
1.由于我没有考虑过,这是一个非常糟糕的主意吗?
SQL Server 应用程序锁是否完全不适合此目的?
您一次可以拥有的应用程序锁(具有不同名称)的最大数量是多少?
如果创建了可能大量的锁,是否有性能考虑? 一般方法还有什么问题?
2.上面的解决方案真的实现了吗?
如果 SQL Server 应用程序锁可以像这样使用,我是否真的正确使用了它们?
有没有更好的方法使用 SQL Server 来实现相同的结果?
在上面的代码中,我连接到主数据库并在那里创建锁。这可能会导致其他问题吗?我应该在不同的数据库中创建锁吗?
3.是否可以使用不使用 SQL Server 应用程序锁的完全替代方法?
我无法使用存储过程来创建和更新记录(在 CRM 2011 中不受支持)。
我不想添加单点故障。
【问题讨论】:
标签: c# sql-server sql-server-2008-r2 dynamics-crm-2011