【问题标题】:Strategy for thread-safe database access线程安全数据库访问策略
【发布时间】:2012-07-24 21:13:06
【问题描述】:

假设我有以下代码在 MVC 应用程序中将用户插入 MS Dynamics:

public bool CreateContact(string email)
{
    if (crm.contacts.Count(x => x.Email == email) > 0)
         return false; //Email already exist in the Crm. Skip

    var contact = new contact {Email = email};
    crm.AddTocontacts(contact);
    crm.SaveChanges();

    return true;
}

它非常适合阻止用户使用相同的电子邮件地址注册,直到最近我们遇到了 Dynamics 的主要性能问题。

显然,用户遇到了巨大的延迟,并且经常三次点击触发这段代码的按钮。

问题是,在 .SaveChanges() 在第一个请求中完成之前,.Count() 在不同的 Http 请求中同时触发。因此,我们看到的联系人使用相同的电子邮件地址。

虽然我已经从客户端添加了一个修复程序,但我想看看这是否也可以在服务器端完成。

什么是使这个线程安全的好策略?


编辑:

虽然在 CRM 上添加约束是这里许多人建议的最佳解决方案,但我目前无法实施该解决方案,因为早在发现此问题之前,CRM 中就已经存在重复项。显然,与 CRM 对话的应用程序不止一个。

由于对锁定和线程的经验很少,我最终做了以下事情:

internal static class ContactLock
{
    internal static readonly object Locker = new object();
}

public bool CreateContact(string email)
{
    lock(ContactLock.Locker)
    {
        if (crm.contacts.Any(x => x.Email == email))
            return false; //Email already exist in the Crm. Skip

        var contact = new contact {Email = email};
        crm.AddTocontacts(contact);
        crm.SaveChanges();

        return true;
    }
}

它通过了我的单元测试,似乎没有任何问题。

【问题讨论】:

  • 在此工作时禁用该按钮?
  • Austin,是的,我已经在客户端这样做了。但我想看看我是否也可以从服务器上做到这一点。
  • 对于 a) 意图和 b) 可能的优化,首选 .Any(x => x.Email == email) 而不是 Count(x => x.Email == email) > 0
  • 您使用的是 CRM 4 还是 CRM 2011?您是否使用直接数据库访问?我不知道 Linq 提供程序允许使用 Count
  • @PeterMajeed:我们使用的是 CRM 4.0,您可以在这里下载一个 SDK:microsoft.com/en-us/download/details.aspx?id=38。它提供了一个代码生成工具,允许您使用 Linq 访问 CRM。这是一个写得不好的软件,应该避免 IMO。 (不能说 2011 年可能有所改善)

标签: c# asp.net-mvc multithreading dynamics-crm dynamics-crm-4


【解决方案1】:

这种验证通常应由底层数据存储中的唯一约束支持。如果可以在 CRM 数据库中创建约束,那就应该进行修复。

您的代码 sn-p 显示了需要某种锁定的典型位置。支票(带有Count())和SaveChanges() 应受锁保护。我建议您从锁定一个静态对象开始 - 这意味着它将是一个防止同时注册的全局锁。如果这被证明是一个问题,您可以修改锁定策略。

关于花费大量时间的通话 - 这是您应该解决的问题。向电子邮件列添加唯一约束将强制对其进行索引,这可能会大大提高性能。如果可能(同样,我不了解 CRM),您应该使用 linq 的 Any() 运算符而不是 Count() 来检查是否存在。前者可以在第一次命中时中断,而后者则必须继续扫描。

【讨论】:

  • 我不认为应用程序锁是个好主意。如果应用程序是负载平衡的怎么办?它不会起作用。
  • 不,在负载均衡的情况下它不会很好地工作,在这种情况下,数据库约束是唯一的方法。
  • 好吧,你是对的 - 数据库约束不是 only 方式,但仍然是我的建议。当约束自动处理它时,在数据库上手动获取锁是很尴尬的。
  • 约束迫使您捕获异常。它还阻止您在该数据上下文中应用任何其他更改,因为您无法摆脱 LINQ to SQL 中的插入。您的数据上下文已损坏。
  • @usr:Dynamics CRM 不允许以您建议的方式直接访问数据库,因此数据库唯一索引(带有处置的数据上下文)可能是唯一的方法。
【解决方案2】:

systemuser 实体上的预创建插件可以在将每笔交易传递到平台之前检查您的约束(即电子邮件地址是唯一的),并且是推荐/支持的方式来执行此操作。

【讨论】:

  • +1 用于服务器端解决方案,我们实际上也实现了这一点,但是如果使用相同的电子邮件地址同时发送两个 Create 请求,在大量情况下会发生什么?我可以看到他们通过Count/Any 测试的场景,由于时间原因,允许重复的电子邮件,而数据库上的索引完全不允许这样做。不过,这绝对是仅在线部署中的最佳解决方案。
  • 一个好问题,一个我不知道答案的问题(尽管我怀疑你的预感是正确的!)。也许在本地场景中,可以在插件代码中引入Mutex
【解决方案3】:

您可以在开头添加以下行以在数据库中获取写锁:

crm.ExecuteCommand("select ID from contacts with (updlock, holdlock) where EMail = {0}", EMail);

并且您需要围绕该方法包装一个事务。这实现了唯一性并且没有死锁。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多