问题描述
当我阅读 Compare Exhange for RavenDB 时,我在用于保留电子邮件的文档中发现了以下用户案例。基本上是一种强制执行 UNIQUE 约束的方法。如果您只想对一个属性强制执行此约束,但如果您引入多个属性(电子邮件和用户名),它不再按预期工作,则此方法非常有效。
在文档中查看:Link
class Program
{
public class User
{
public string Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
static void Main(string[] args)
{
var store = new DocumentStore()
{
Urls = new[] {
"http://127.0.0.1:8080/"
},Database = "example",}.Initialize();
string name = "admin";
string email = "admin@example.com";
var user = new User
{
Name = name,Email = email
};
using (IDocumentSession session = store.OpenSession())
{
session.Store(user);
// Try to reserve a new user email
// Note: This operation takes place outside of the session transaction,// It is a cluster-wide reservation
CompareExchangeResult<string> namePutResult
= store.Operations.Send(
new PutCompareExchangeValueOperation<string>("names/" + name,user.Id,0));
if (namePutResult.Successful == false)
{
throw new Exception("Name is already in use");
}
else
{
// Try to reserve a new user email
// Note: This operation takes place outside of the session transaction,// It is a cluster-wide reservation
CompareExchangeResult<string> emailPutResult
= store.Operations.Send(
new PutCompareExchangeValueOperation<string>("emails/" + email,0));
// Unlock name again (Because if we dont the name wil be locked)
if (emailPutResult.Successful == false)
{
// First,get existing value
CompareExchangeValue<string> readResult =
store.Operations.Send(
new GetCompareExchangeValueOperation<string>("names/" + name));
// Delete the key - use the index received from the 'Get' operation
CompareExchangeResult<string> deleteResult
= store.Operations.Send(
new DeleteCompareExchangeValueOperation<string>("names/" + name,readResult.Index));
// The delete result is successful only if the index has not changed between the read and delete operations
if (deleteResult.Successful == false)
{
throw new Exception("The name is forever lost");
}
else
{
throw new Exception("Email is already in use");
}
}
}
// At this point we managed to reserve/save both the user name and email
// The document can be saved in SaveChanges
session.SaveChanges();
}
}
}
在上面的示例中,您可以看到为什么这不再按预期工作。因为现在,如果电子邮件比较交换失败或已经被采用,则名称比较交换无法撤消/删除,因为删除比较交换理论上可能会失败。现在因为这个有一个更改,用户名将被永久锁定,不能再次使用。当您尝试更新用户名时也会发生同样的问题,因为一旦保留了新用户名,您就必须解锁/删除旧用户名的比较交换。
解决此类问题的最佳方法是什么?这种情况发生了哪些变化?
解决方法
如果您在 namePutResult.Successful
上下文中,那么您肯定知道 namePutResult.Index
是用于创建 CompareExchange 的唯一索引,因此如果电子邮件被占用,您可以直接使用 { {1}} 以删除 CompareExchange,如果失败,您可以处理异常(重新发送 DeleteCompareExchangeValueOperation`)。
namePutResult.Index
,
您是否尝试使用集群范围的事务会话在单个事务中存储两个比较交换值?