在创建用户时,必须在 User 和 Email 表中插入一行。它可能在其中任何一个中失败(唯一约束)。如何找出失败的原因?我的想法是在插入或解析返回的 SqlException 之前使用锁并查询数据库(我不想这样做)。
编辑:我应该提到这将同时在多台机器上运行,我希望它支持不同的数据库。
编辑 2:我的解决方案最终是使用锁定检查重复项。存储过程是一种选择,但我不想将业务逻辑放入数据库中。我为其他人评论说我知道网络农场中的竞争条件,但这种情况的罕见性并不需要进一步的工作。
在创建用户时,必须在 User 和 Email 表中插入一行。它可能在其中任何一个中失败(唯一约束)。如何找出失败的原因?我的想法是在插入或解析返回的 SqlException 之前使用锁并查询数据库(我不想这样做)。
编辑:我应该提到这将同时在多台机器上运行,我希望它支持不同的数据库。
编辑 2:我的解决方案最终是使用锁定检查重复项。存储过程是一种选择,但我不想将业务逻辑放入数据库中。我为其他人评论说我知道网络农场中的竞争条件,但这种情况的罕见性并不需要进一步的工作。
应使用异常处理来捕获非主要场景,例如数据库已关闭或命令超出超时。如果您对用户唯一性和电子邮件唯一性有限制,您应该在提交数据之前真正测试它们。从长远来看,依赖检查/索引约束作为处理这些场景的一种方式会造成混乱。此外,错误处理的一个关键最佳实践是永远不要让最终用户知道发生错误的细节。
使用存储过程,并检查使事务在事务内部失败的已知条件,例如:
BEGIN TRANSACTION
IF EXISTS (SELECT UserID FROM User WHERE UserID = @UserID)
BEGIN
ROLLBACK
SELECT 'User already exists in the User table.'
RETURN 1
END
IF EXISTS (SELECT UserID FROM Email WHERE UserID = @UserID)
BEGIN
ROLLBACK
SELECT 'User already exists in the Email table.'
RETURN 2
END
INSERT INTO User ...
INSERT INTO Email ...
COMMIT
RETURN 0
这实际上是使用两种机制来返回错误(返回代码和结果集);通常只使用一个才有意义。
您应该在某处的业务逻辑中捕捉到这种情况,而不是仅仅依靠数据库来为您提供您正在寻找的错误。
我不会依赖表约束来进行数据验证。在插入之前使用查询验证数据。异常是要创建的昂贵对象。此外,我更喜欢设置约束以防止无效数据但不进行验证。我认为约束是桌子的安全带。只有在发生错误时才应该调用它。业务逻辑应在插入之前验证所有数据。如果您的目标数据库可能不支持存储过程,请不要依赖它们。
这是我处理它的一般方式。
private void Form1_Load(object sender, EventArgs e)
{
OleDbConnection conn = null;
OleDbTransaction t = null;
try
{
conn = new OleDbConnection("a database");
conn.Open();
//query both tables to prevent insert fail,
//obviously UserID should be parameter.
var cmd = new OleDbCommand("select count(*) from User where UserID = 1", conn);
var count = (double)cmd.ExecuteScalar();
cmd.CommandText = "select count(*) from Email where UserID = 1";
count += (double)cmd.ExecuteScalar();
if (count != 0)
{
MessageBox.Show("Record exists");
return;
}
t = conn.BeginTransaction();
//insert logic goes here
t.Commit();
}
catch (Exception x)
{
//we still need catch block, someone else may have updated the
//data after you checked but before you insert or db open may
//fail
MessageBox.Show(x.Message);
if (t != null)
t.Rollback();
}
finally
{
if (conn != null)
conn.Close();
}
}