Как правильно проверить ошибки каскадного сохранения?
При создании пользователя строка должна быть вставлена в таблицу User и Email. Он может потерпеть неудачу в любом из них (уникальные ограничения). Как я могу узнать, что является причиной отказа? Я думал об использовании блокировки и запросов к базе данных до вставки или разбора возвращаемого SqlException (что я предпочел бы не делать).
Изменить: я должен был упомянуть, что это будет работать на нескольких машинах одновременно, и я хотел бы, чтобы он поддерживал разные базы данных.
Редактировать 2: Мое решение в конечном итоге использовать блокировку вокруг проверки на наличие дубликатов. Хранимые процедуры были опцией, но я не хотел помещать бизнес-логику в базу данных. Я прокомментировал для других, что я знал об условиях гонки на веб-ферме, но редкость обстоятельств не требовала дальнейшей работы.
4 ответа
Обработка исключений должна использоваться для захвата не простых сценариев, таких как отключение базы данных или превышение времени ожидания команды. Если у вас есть ограничения в отношении уникальности пользователя и уникальности электронной почты, вы должны действительно проверить их перед отправкой данных. Использование ограничений проверки / индексации в качестве способа обработки этих сценариев приведет к путанице в долгосрочной перспективе. Кроме того, ключевой лучший метод в обработке ошибок - никогда не сообщать конечному пользователю подробности того, почему произошла ошибка.
Используйте хранимую процедуру и проверьте известные условия, которые приведут к сбою транзакции внутри транзакции, например:
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();
}
}
Вы должны ловить подобные случаи в своей бизнес-логике, а не полагаться исключительно на базу данных, чтобы получить искомую ошибку.