假设我有以下代码将用户插入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的主要性能问题。
显然,用户会遇到很大的延迟,并经常三次点击触发此代码的按钮。
问题是,.Count()在不同的Http请求中同时触发.SaveChanges()在第一个Request中完成。因此,我们看到的联系人具有相同的电子邮件地址。
虽然我已经从客户端添加了一个修复程序,但我想看看是否可以在服务器端完成此操作。
什么是使这个线程安全的好策略?
虽然在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;
}
}
它通过我的单元测试,似乎没有任何问题。
答案 0 :(得分:5)
这种验证通常应该由底层数据存储中的唯一约束支持。如果可以在CRM数据库中创建约束,那就是应该进行修复的地方。
您的代码段显示了需要某种锁定的典型位置。检查(使用Count()
)和SaveChanges()
应受锁保护。我建议你先从锁定一个静态的对象开始 - 这意味着它将是一个防止同时注册的全局锁。如果这证明是一个问题,你可以修改锁定策略。
关于电话花费大量时间 - 这是你应该解决的问题。向电子邮件列添加唯一约束将强制将其编入索引,这可能会大大提高性能。如果可能(再次,我不知道CRM),您应该使用linq的Any()
运算符而不是Count()
来检查是否存在。前者可以在第一次打击时打破,而后者则必须继续扫描。
答案 1 :(得分:1)
systemuser
实体上的预创建插件可以在将每个事务传递到平台之前检查您的约束(即电子邮件地址是唯一的),并且是推荐/支持的方式来执行此操作。
答案 2 :(得分:0)
您可以在开头添加以下行以在数据库中执行写锁定:
crm.ExecuteCommand("select ID from contacts with (updlock, holdlock) where EMail = {0}", EMail);
您需要围绕该方法包装事务。这实现了独特性并且无死锁。