Обнаружение Расположения () от исключения в использовании блока

У меня есть следующий код в моем приложении:

using (var database = new Database()) {
    var poll = // Some database query code.

    foreach (Question question in poll.Questions) {
        foreach (Answer answer in question.Answers) {
            database.Remove(answer);
        }

        // This is a sample line  that simulate an error.
        throw new Exception("deu pau"); 

        database.Remove(question);
    }

    database.Remove(poll);
}

Эти триггеры кода класс Базы данных Располагает () метод, как обычно, и этот метод автоматически, передают транзакцию базе данных, но это оставляет мою базу данных в непоследовательном состоянии, поскольку ответы стираются, но вопрос и опрос не.

Существует какой-либо способ, которым я могу обнаружить в Расположении () метод, что это называемый из-за исключения вместо регулярного конца заключительного блока, таким образом, я могу автоматизировать откат?

Я не хочу вручную добавлять, что попытка... ловит блок, моя цель состоит в том, чтобы использовать блок использования в качестве логического безопасного менеджера транзакций, таким образом, это соглашается на базу данных, если выполнение было чистым или откаты, если какое-либо исключение произошло.

У Вас есть некоторые мысли об этом?

21
задан jball 13 May 2010 в 20:31
поделиться

8 ответов

Как говорили другие, использование вами шаблона Disposable для именно эта цель вызывает у вас проблемы. Если шаблон работает против вас, я бы изменил его. Делая фиксацию поведением блока using по умолчанию, вы предполагаете, что каждое использование базы данных приводит к фиксации, что явно не так, особенно если возникает ошибка. Явная фиксация, возможно, в сочетании с блоком try / catch, будет работать лучше.

Однако , если вы действительно хотите сохранить использование шаблона как есть , вы можете использовать:

bool isInException = Marshal.GetExceptionPointers() != IntPtr.Zero
                        || Marshal.GetExceptionCode() != 0;

в своей реализации Displose, чтобы определить, было ли создано исключение (подробнее здесь ).

26
ответ дан 29 November 2019 в 20:09
поделиться

Посмотрите на дизайн TransactionScope в System.Transactions. Их метод требует, чтобы вы вызывали Complete () в области транзакции, чтобы зафиксировать транзакцию. Я бы подумал о том, чтобы спроектировать ваш класс базы данных, следуя тому же шаблону:

using (var db = new Database()) 
{
   ... // Do some work
   db.Commit();
}

Вы, возможно, захотите ввести концепцию транзакций вне вашего объекта базы данных. Что произойдет, если потребители захотят использовать ваш класс и не хотят использовать транзакции и все будут автоматически фиксировать?

7
ответ дан 29 November 2019 в 20:09
поделиться

Все остальные уже написали, каким должен быть правильный дизайн вашего класса Database , поэтому я выиграл » Я повторяю это. Однако я не видел никаких объяснений, почему то, что вы хотите, невозможно. Итак, вот оно.

Что вы хотите сделать, так это определить во время вызова Dispose , что этот метод вызывается в контексте исключения. Когда вы сможете это сделать, разработчикам не придется явно вызывать Commit . Однако проблема здесь в том, что в .NET нет способа надежно обнаружить это. Хотя существуют механизмы для запроса последней выданной ошибки (например, HttpServerUtility.GetLastError ), эти механизмы зависят от хоста (поэтому ASP.NET имеет другой механизм, например, формы Windows). И хотя вы можете написать реализацию для конкретной реализации хоста, например, реализацию, которая будет работать только в ASP.NET, есть еще одна более важная проблема: что, если ваш класс Database используется или создается в контексте исключения? Вот пример:

try
{
    // do something that might fail
}
catch (Exception ex)
{
    using (var database = new Database())
    {
        // Log the exception to the database
        database.Add(ex);
    } 
}

Когда ваш ] Класс базы данных используется в контексте исключения , как в примере выше, как ваш метод Dispose должен знать, что он все еще должен выполнить фиксацию? Я могу придумать способы обойти это, но это будет довольно хрупко и подвержено ошибкам. Приведу пример.

Во время создания базы данных вы могли проверить, вызывается ли она в контексте исключения, и, если это так, сохранить это исключение. Во время вызова Dispose вы проверяете, отличается ли последнее созданное исключение от кэшированного исключения. Если отличается, следует выполнить откат. Если нет, сделайте коммит.

Хотя это кажется хорошим решением, как насчет этого примера кода?

var logger = new Database();
try
{
    // do something that might fail
}
catch (Exception ex)
{
    logger.Add(ex);
    logger.Dispose();
}

В этом примере вы видите, что экземпляр Database создается перед блоком try. Поэтому он не может правильно определить, что не может откатиться. Хотя это может быть надуманный пример, он показывает трудности, с которыми вы столкнетесь при попытке спроектировать свой класс таким образом, чтобы явный вызов Commit не требовался.

В конце концов, вы сделаете свой класс Database сложным для проектирования и поддержки, и вы никогда не поймете это правильно.

Как уже говорили все остальные, проект, который требует явного вызова Commit или Complete , будет проще реализовать, легче получить правильные, проще в обслуживании и дает возможность использовать код более читабельный (например, потому что он выглядит так, как ожидают разработчики).

Последнее замечание, если вы беспокоитесь о том, что разработчики забывают вызвать этот метод Commit : вы можете выполнить некоторую проверку в методе Dispose , чтобы узнать, вызывается ли он без ] Commit вызывается и записывает в консоль или устанавливает точку останова во время отладки. Кодировать такое решение было бы намного проще, чем пытаться вообще избавиться от Commit .

Обновление: Адриан написал интересную альтернативу использованию HttpServerUtility.GetLastError. Как отмечает Адриан, вы можете использовать Marshal.GetExceptionPointers () , который является общим способом, который будет работать на большинстве хостов. Обратите внимание, что это решение имеет те же недостатки, о которых говорилось выше, и что вызов класса Marshal возможен только при полном доверии

10
ответ дан 29 November 2019 в 20:09
поделиться
1
ответ дан 29 November 2019 в 20:09
поделиться

Как указывает Энтони выше, проблема заключается в том, что вы ошиблись в использовании предложения using в этом сценарии. Парадигма IDisposable предназначена для обеспечения очистки ресурсов объекта независимо от результата сценария (поэтому исключение, возврат или другое событие, которое покидает блок using, по-прежнему запускает метод Dispose). Но вы изменили его назначение, чтобы оно означало что-то другое, чтобы совершить транзакцию.

Мое предложение было бы таким, как заявляли другие, и использовать ту же парадигму, что и TransactionScope. Разработчик должен явно вызвать Commit или аналогичный метод в конце транзакции (до закрытия блока using), чтобы явно сказать, что транзакция исправна и готова к фиксации. Таким образом, если исключение заставляет выполнение покинуть блок using, метод Dispose в этом случае может вместо этого выполнить откат. Это все еще соответствует парадигме, поскольку выполнение отката было бы способом «очистки» объекта базы данных, чтобы он не оставался в недопустимом состоянии.

Этот сдвиг в дизайне также значительно упростил бы выполнение того, что вы хотите, поскольку вы не будете бороться с дизайном .NET, пытаясь «обнаружить» исключение.

1
ответ дан 29 November 2019 в 20:09
поделиться

Вы можете унаследовать от класса Database, а затем переопределить метод Dispose () (убедившись, что ресурсы базы данных закрыты), тогда это может вызвать настраиваемое событие на который вы можете подписаться в своем коде.

0
ответ дан 29 November 2019 в 20:09
поделиться

Вы должны заключить содержимое вашего блока using в блок try / catch и откатить транзакцию в блоке catch:

using (var database = new Database()) try
{
    var poll = // Some database query code.

    foreach (Question question in poll.Questions) {
        foreach (Answer answer in question.Answers) {
            database.Remove(answer);
        }

        // This is a sample line  that simulate an error.
        throw new Exception("deu pau"); 

        database.Remove(question);
    }

    database.Remove(poll);
}
catch( /*...Expected exception type here */ )
{
    database.Rollback();
}
1
ответ дан 29 November 2019 в 20:09
поделиться

Короче: я думаю, что это невозможно, НО

Что вы можете сделать, так это установить флаг в своем классе базы данных со значением по умолчанию "false" (это нехорошо) и дальше в последней строке внутри блока using вы вызываете метод, который устанавливает для него значение «true», затем в методе Dispose () вы можете проверить, имеет ли флаг «исключение» или нет.

using (var db = new Database())
{
    // Do stuff

    db.Commit(); // Just set the flag to "true" (it's good to go)
}

И класс базы данных

public class Database
{
    // Your stuff

    private bool clean = false;

    public void Commit()
    {
        this.clean = true;
    }

    public void Dispose()
    {
        if (this.clean == true)
            CommitToDatabase();
        else
            Rollback();
    }
}
2
ответ дан 29 November 2019 в 20:09
поделиться
Другие вопросы по тегам:

Похожие вопросы: