Как я могу обнаружить состояние, которое вызывает исключение, прежде чем оно произойдет? - PullRequest
4 голосов
/ 17 февраля 2009

Мне не повезло с этим вопросом , поэтому я создал простой тестовый пример, чтобы продемонстрировать проблему.

В приведенном ниже коде можно ли обнаружить, что соединение не работает, прежде чем пытаться его использовать?

    SqlConnection c = new SqlConnection(myConnString);

    c.Open();  // creates pool

    setAppRole(c);  // OK

    c.Close(); // returns connection to pool

    c = new SqlConnection(myConnString); // gets connection from pool

    c.Open(); // ok... but wait for it...

    // ??? How to detect KABOOM before it happens?

    setAppRole(c); // KABOOM

KABOOM проявляется как ошибка в журнале событий Windows;

Соединение было разорвано, так как открывший его субъект впоследствии принял новый контекст безопасности, а затем попытался сбросить соединение в рамках своего олицетворенного контекста безопасности. Этот сценарий не поддерживается. См. «Обзор олицетворения» в электронной документации.

... плюс исключение в коде.

setAppRole - это простой метод для установки роли приложения в соединении. Это похоже на это ...

static void setAppRole(SqlConnection conn) {

    using (IDbCommand cmd = conn.CreateCommand())
        {
            cmd.CommandType = CommandType.Text;
            cmd.CommandText = "exec sp_setapprole ";
            cmd.CommandText += string.Format("@rolename='{0}'",myUser);
            cmd.CommandText += string.Format(",@password='{0}'",myPassword);
            cmd.ExecuteNonQuery();
        }
    }

В реальном коде делается попытка использовать sp_unsetapprole до закрытия соединения, но это не всегда может быть гарантировано (унаследованное глючное многопоточное приложение). В любом случае все еще кажется разумным ожидать, что он сможет обнаружить kaboom, прежде чем вызвать его.

Ответы [ 8 ]

4 голосов
/ 17 февраля 2009

Короче говоря, это не выглядит так просто.

Моей первой мыслью было запустить этот SQL:

SELECT CASE WHEN USER = 'MyAppRole' THEN 1 ELSE 0 END

Это работает, если вы используете SQL Server Management Studio, но не работает при запуске из кода C #. Проблема в том, что ошибка, которую вы получаете, не возникает при вызове sp_setapprole, а фактически возникает, когда пул соединений вызывает sp_reset_connection. Пул соединений вызывает это, когда вы впервые используете соединение, и до него нет способа войти.

Итак, я думаю, у вас есть четыре варианта:

  1. Отключите пул соединений, добавив «Pooling = false;» на строку подключения.
  2. Используйте другой способ подключения к SQL Server. Есть API более низкого уровня, чем ADO.Net, но, честно говоря, это, вероятно, не стоит проблем.
  3. Как говорит casperOne, вы можете исправить свой код, чтобы правильно закрыть соединение.
  4. Перехватить исключение и сбросить пул соединений. Я не уверен, что это будет делать с другими открытыми соединениями, хотя. Пример кода ниже:
class Program
{
    static void Main(string[] args)
    {
        SqlConnection conn = new SqlConnection("Server=(local);Database=Test;UID=Scrap;PWD=password;");

        setAppRole(conn);
        conn.Close();

        setAppRole(conn);
        conn.Close();
    }

    static void setAppRole(SqlConnection conn) 
    {
        for (int i = 0; i < 2; i++)
        {
            conn.Open();
            try
            {
                using (IDbCommand cmd = conn.CreateCommand())
                {
                    cmd.CommandType = CommandType.Text;
                    cmd.CommandText = "exec sp_setapprole ";
                    cmd.CommandText += string.Format("@rolename='{0}'", "MyAppRole");
                    cmd.CommandText += string.Format(",@password='{0}'", "password1");
                    cmd.ExecuteNonQuery();
                }
            }
            catch (SqlException ex)
            {
                if (i == 0 && ex.Number == 0)
                {
                    conn.Close();
                    SqlConnection.ClearPool(conn);
                    continue;
                }
                else
                {
                    throw;
                }
            }
            return;
        }
    }
}
1 голос
/ 13 октября 2011

это действительно происходит при вызовах пула соединений sp_reset_connection. Пул подключений вызывает это при первом использовании соединение, и до него нет возможности войти.

Основываясь на ответе Мартина Брауна , вы можете попробовать добавить "Connection Reset = False" в строку подключения в качестве способа "войти до" sp_reset_connection. (См. « Работа с« загрязненными »соединениями » для объяснения многих недостатков этого параметра.)

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

В наши дни (SQL 2005+) рекомендуется (в разделе Роли приложений и пулы соединений ) "воспользоваться преимуществами новых механизмов безопасности , которые можно использовать вместо ролей приложений. ", как EXECUTE AS.

0 голосов
/ 17 февраля 2009

@ edg: Вы говорите в комментарии: «... только тогда он попадает на реальный сервер и сталкивается с проблемой безопасности, как описано в цитате сообщения».

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

Поскольку изменение идентификатора является заданным, возможно, решение заключается в фильтрации журнала безопасности. В программе просмотра событий есть действие «Фильтровать текущий журнал», которое можно фильтровать по ключевому слову или даже по событию.

+ * Том 1009 *

0 голосов
/ 17 февраля 2009

Вы можете проверить c.State (объект ConnectionState), который должен быть одним из следующих:

System.Data.ConnectionState.Broken
System.Data.ConnectionState.Closed
System.Data.ConnectionState.Connecting
System.Data.ConnectionState.Executing
System.Data.ConnectionState.Fetching
System.Data.ConnectionState.Open
0 голосов
/ 17 февраля 2009

Я также опубликовал это в ответ на ваш предыдущий вопрос. При вызове sp_setapprole вы должны вызывать sp_unsetapprole, когда закончите, и предложенное мной решение поможет вам:

Обнаружение непригодных пулов SqlConnections


Казалось бы, вы вызываете sp_setapprole, но не вызываете sp_unsetapprole, а затем разрешаете просто вернуть соединение в пул.

Я бы предложил использовать структуру (или класс, если вам нужно использовать это в разных методах) с реализацией IDisposable, которая позаботится об этом за вас:

public struct ConnectionManager : IDisposable
{
    // The backing for the connection.
    private SqlConnection connection;

    // The connection.
    public SqlConnection Connection { get { return connection; } }

    public void Dispose()
    {
        // If there is no connection, get out.
        if (connection == null)
        {
            // Get out.
            return;
        }

        // Make sure connection is cleaned up.
        using (SqlConnection c = connection)
        {
            // See (1).  Create the command for sp_unsetapprole
            // and then execute.
            using (SqlCommand command = ...)
            {
                // Execute the command.
                command.ExecuteNonQuery();
            }
        }
    }

    public ConnectionManager Release()
    {
        // Create a copy to return.
        ConnectionManager retVal = this;

        // Set the connection to null.
        retVal.connection = null;

        // Return the copy.
        return retVal;        
    }

    public static ConnectionManager Create()
    {
        // Create the return value, use a using statement.
        using (ConnectionManager cm = new ConnectionManager())
        {
            // Create the connection and assign here.
            // See (2).
            cm.connection = ...

            // Create the command to call sp_setapprole here.
            using (SqlCommand command = ...)
            {
                // Execute the command.
                command.ExecuteNonQuery();

                // Return the connection, but call release
                // so the connection is still live on return.
                return cm.Release();
            }
        }
    }
}
  1. Вы создадите SqlCommand, который соответствует вызову хранимой процедуры sp_setapprole. Вы можете сгенерировать cookie и сохранить его в закрытой переменной-члене.
  2. Здесь вы создаете соединение.

Код клиента выглядит следующим образом:

using (ConnectionManager cm = ConnectionManager.Create())
{
    // Get the SqlConnection for use.
    // No need for a using statement, when Dispose is
    // called on the connection manager, the connection will be
    // closed.
    SqlConnection connection = cm.Connection;

    // Use connection appropriately.
}
0 голосов
/ 17 февраля 2009

Нет ли способа очистить пул от всех соединений. SqlPools.Clear или что-то.

Вы можете просто попытаться перехватить исключение и создать новое соединение, что должно заставить пул создать совершенно новое соединение.

0 голосов
/ 17 февраля 2009

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

c.Open();
blabla;
c.Close();
c.Open(); 
kaboom...

Вы бы сделали следующее:

using (new SqlConnection ...)
{
  c.Open();
  blabla;
}

using (new SqlConnection ... )
{
  c.Open();
  no kaboom?
}

(Пожалуйста, прости псевдокод ... Клавиатуру на моем eeepc невозможно использовать ...)

0 голосов
/ 17 февраля 2009

Попробуйте переместить sp_unsetapprole (действительно ли это имя sproc? Возможно, sp_dropapprole является правильным?) В setAppRole() и выполнить его до добавления роли приложения.

...