#asp.net #oracle #oracle10g #connection-pooling #odp.net
#asp.net #Oracle #oracle10g #объединение в пул соединений #odp.net
Вопрос:
Сценарий: У нас есть приложение, использующее Oracle 10g и последнюю версию ODP.net в пределах ASP.net применение. Мы используем .Свойство ClientID WriteOnly для объекта OracleConnection для передачи определенного идентификатора пользователя в базу данных в целях аудита. Когда пул подключений отключен, это работает отлично.
Когда это включено, первый пользователь, который входит в систему (например, ПОЛЬЗОВАТЕЛЬ1), обновляет запись, и измененным ИМЕНЕМ_BY является USER1, но когда другой пользователь переходит на веб-сайт после этого, таким образом, захватывая объединенное соединение, измененным ИМЕНЕМ_BY по-прежнему является USER1, несмотря на передачу USER2 в ClientID.
Логика нашей базы данных следующая:
Мы сохраняем класс в ASP.net сеанс, в котором используется наша логика подключения к базе данных. При первоначальном вызове это наш конструктор:
Public Sub New(ByVal connection As String, Optional ByVal oracleClientID As String = "")
MyBase.New()
_oracleConnection = New OracleConnection(connection)
_clientID = oracleClientID
End If
End Sub
Вот суть кода для открытия соединения и закрытия, удаления:
Try
_OraCmd = New OracleCommand(command, _oracleConnection)
With _OraCmd
.BindByName = True
.Parameters.Clear()
.CommandType = CommandType.StoredProcedure
_oracleConnection.Open()
If _clientID <> "" Then _oracleConnection.ClientId = _clientID
Dim OraDadpt As New OracleDataAdapter(_OraCmd)
'' Logic to get data
OraDadpt.Fill(ds)
End With
Catch ex As Exception
Throw ex
Finally
ClearParameters()
_OraCmd.Dispose()
_oracleConnection.Close()
End Try
Идея заключается в том, что, поскольку соединение объединено, предполагается вызов триггера входа в систему, который никогда не выполняется, и идентификатор клиента никогда не устанавливается снова. Однако в документации ORACLE указано, что ClientID используется именно для того, что мы пытаемся сделать.
У кого-нибудь есть какие-либо мысли относительно того, почему SYS_CONTEXT (‘USERENV’, ‘CLIENT_IDENTIFIER’) не устанавливается на новый идентификатор пользователя, который передается в идентификатор клиента, когда пул подключений используется в нашем .СЕТЕВОЕ приложение с ODP.net ? Это настройка базы данных, настройка прослушивателя?
Обновить
Мы переадресовали проблему в Oracle. При этом нам пришлось создать небольшое тестовое приложение, которое имитировало проблему. При этом на моем локальном хостинге все работало отлично, используя встроенный веб-сервер Cassini Visual Studio. С IIS возникает проблема.
Обновить
Определено, что проблема не в IIS. Это были переменные пакета, которые не были удалены из-за повторного использования соединений, которые были объединены в пул, по сути, то, что должен делать пул. Мы решили это с помощью DBMS_SESSION.MODIFY_PACKAGE_STATE (DBMS_SESSION.ПОВТОРНАЯ ИНИЦИАЛИЗАЦИЯ).
Комментарии:
1. Вы упомянули триггер входа в систему. Какова именно цель триггера входа в систему в вашей настройке? И можете ли вы опубликовать его код.
2. У нас его нет. ODP.net следует обрабатывать это через . Свойство ClientID. Из всего, что я прочитал, когда происходит вход в базу данных — ODP устанавливает SYS_CONTEXT с помощью . ClientID.
3. Да, ODP.NET задает SYS_CONTEXT. Но это не имеет ничего общего с триггером входа в систему, который является чем-то другим. Можете ли вы показать строку подключения, которую вы используете для объединения в пул соединений? Возможно, вы не используете ODP.NET пул подключений (который сбрасывает идентификатор клиента, когда соединение возвращается в пул).
4. Строка подключения проста: «источник данных = DataSource; идентификатор пользователя =user; пароль = pass;» — Пул по умолчанию включен, насколько я понимаю. Я пытался использовать все свойства минимального и максимального размера пула и т.д., Но безуспешно.
5. Эй, как насчет установки DBMS_SESSION.SET_IDENTIFIER при открытии соединения
Ответ №1:
Попробуйте использовать DBMS.Rest_Package перед закрытием подключений.
Проблема, я думаю, в том, что объединение в пул, включенное в ODP, сохраняет соединение, таким образом, когда каждый пользователь открывает и закрывает соединение с помощью ODP, объединение сохраняет переменные пакета сеанса в памяти; до истечения времени ожидания соединения. Однако, поскольку время ожидания и повторное установление соединения из базы данных с пулом происходят только после возврата соединения в пул, вы работаете с данными сеанса другого пользователя.
Комментарии:
1. Как насчет DBMS_SESSION.MODIFY_PACKAGE_STATE (DBMS_SESSION. ПОВТОРНО ИНИЦИАЛИЗИРОВАТЬ)?
2. На самом деле это лучший подход. У него нет утечки памяти, которая была у DMBS.Reset_Package, или других накладных расходов. Вы также можете захотеть вызвать это ПЕРЕД выполнением вашего SQL, а не после и перед закрытием. Если по какой-либо причине вызов пакета зависает и вы не можете выполнить следующую строку кода, вы не будете сбрасывать сеанс. Если прослушиватель пула отключит соединение и вернет его обратно в пул, у вас будет соединение с глобальными переменными пакета, которые могут быть обнаружены следующим вызовом, который получит это соединение. Мне бы не хотелось отлаживать это позже.
3. asktom.oracle.com/pls/apex / … может помочь. Это указывает на то, что использование глобальных переменных пакета с объединением в пул соединений изначально нецелесообразно. Однако, если необходимо, использование повторной инициализации, о которой вы упомянули, должно устранить сохранение пакета при возврате соединения в пул.
4. После тщательного тестирования было установлено, что проблема заключается в переменных пакета. Хотя это и не далеко идущее решение, DBMS_SESSION.MODIFY_PACKAGE_STATE(DBMS_SESSION. ПОВТОРНАЯ ИНИЦИАЛИЗАЦИЯ) работает прямо сейчас.
5. Руководство: Не используйте глобальные общедоступные переменные пакета при объединении в пул. Почему? Потому что, когда вы закрываете соединение, оно на самом деле не закрывается. Он становится доступным для следующего запроса пулом; но пул поддерживает соединение. Поскольку соединение поддерживается, сохраняются все переменные сеанса DB. Сюда входят глобальные пакеты, временные таблицы и экземпляры объектов. Поэтому, когда второй пользователь получает соединение, которое вы только что выпустили, и пытается получить доступ к тем же пакетам, что и вы, они получают грязный сеанс DB. Это также означает, что блокировка anon больше не срабатывает!
Ответ №2:
Это отлично работает как при включении, так и при выключении пула. Идентификатор клиента и ClientInfo в сеансе Oracle не обновляются до тех пор, пока не будет выполнена команда.
Не могли бы вы проверить правильность вашего утверждения if? Если _clientID <> «» , то _oracleConnection.ClientID = _clientID. Даже когда вы закрываете соединение, идентификатор клиента все равно останется прежним. Не уверен, где ваша настройка / получение _clientId при передаче этого в ваш метод.
class Program
{
static void Main(string[] args)
{
TestClientId test = new TestClientId();
test.DoSomething("FirstClientId");
test.DoSomething("ChangedClientId");
}
}
public class TestClientId
{
/// <summary>
/// The connection string.
/// </summary>
private const string ConnString = "DATA SOURCE=//server:port/service_name;USER ID=user;PASSWORD=pswd;";
/// <summary>
/// The oracle connection.
/// </summary>
private OracleConnection connection;
/// <summary>
/// The oracle session id.
/// </summary>
private long sid;
/// <summary>
/// Initializes a new instance of the <see cref="TestClientId"/> class.
/// </summary>
public TestClientId()
{
this.connection = new OracleConnection(ConnString);
}
/// <summary>
/// Changes the client id of the oracle connection.
/// </summary>
/// <param name="clientId">The client id.</param>
public void DoSomething(string clientId)
{
this.connection.Open();
this.sid = this.GetSessionId(this.connection);
if (!string.IsNullOrEmpty(clientId))
{
this.connection.ClientInfo = clientId;
this.connection.ClientId = clientId;
}
OracleCommand command = new OracleCommand("select * from dual", this.connection);
command.ExecuteNonQuery();
this.connection.Close();
}
/// <summary>
/// Gets the session id.
/// </summary>
/// <param name="con">The connection object.</param>
/// <returns>The current oracle session id.</returns>
public int GetSessionId(OracleConnection con)
{
OracleCommand cmd = new OracleCommand();
cmd.Connection = con;
cmd.CommandText = "select SYS_CONTEXT('USERENV','SID') from dual";
object sid = cmd.ExecuteScalar();
return Convert.ToInt32(sid);
}
}
Комментарии:
1. В нашем случае это работает только при первом входе в базу данных. Все последующие вызовы в пределах времени ожидания пула — это имя первого пользователя, вошедшего в систему. Если мы допустим истечение периода ожидания, соединение прервется, и следующий пользователь получит свое имя в ClientID. Как будто соединение никогда не закрывается. Но я закрываю его.
2. У меня это действительно работает. Я пробовал api 10.x для базы данных 11g и api 11.x для базы данных 10g. Работают оба способа. Проблема, с которой я столкнулся, заключалась в том, что мне нужно выполнить команду до фактического обновления идентификатора клиента или ClientInfo. Я тестировал с объединением = true и объединением = false, оба работали.
3. Согласно вашему предыдущему вопросу, мы передаем ClientID в конструктор и устанавливаем для него закрытую переменную. Затем получите доступ к частной переменной и установите для нее значение ClientID при выполнении вызова Execute.
4. Это то, что наводит меня на мысль, что это, должно быть, какая-то проблема с сервером. Мы перенаправляем этот случай в Oracle, чтобы проверить, может ли это быть так. Я попробовал ваше решение выше, точно такое, как у вас, но безуспешно.
5. Ранее, просматривая Google, я увидел пользователя с аналогичной проблемой на форумах Oracle, и они упомянули, что переустановили odp.net или odac, и это решило проблему. Мне трудно в это поверить в данном случае. Сборка есть, она должна работать. Хотя, возможно, стоит попробовать.
Ответ №3:
Идентификатор клиента сбрасывается только при закрытии соединения, и если вы закрываетесь, то его обязательно нужно сбросить.
Пул подключений помогает серверу базы данных прерывать незанятый сеанс и использовать соединение для обслуживания активного сеанса. Незанятый логический сеанс остается открытым, и физическое соединение автоматически восстанавливается при поступлении следующего запроса из этого сеанса. Итак, соединение действительно закрыто?
Поэтому было бы неплохо установить идентификатор сеанса с помощью DBMS_SESSION.SET_IDENTIFIER
Надеюсь, это поможет
Комментарии:
1. Основываясь на приведенном выше коде, the . Close () выполняется при каждом запросе. Однако ваша логика кажется правильной. Это действует, если соединение никогда не закрывается. Из проведенного мной тестирования. Всегда нажимается Close.
2. Установка идентификатора, а затем его очистка должны были сработать, поэтому сейчас мы фокусируемся на том, что это потенциальная проблема с прослушивателем.
3. Вы правы, что он достигает закрытия , но при объединении в пул он никогда не закрывается, а возвращается в пул, и когда используется следующий запрос в том же соединении. Итак, в вашем текущем контексте всегда используется то же соединение, что и у USER1
4. За исключением того, что это именно то, что должен делать ClientID … сбрасывать идентификатор клиента, даже при захвате существующего объединенного соединения.
Ответ №4:
Мне кажется очень опасным сохранять ссылку на подключение к базе данных в вашем сеансе. Идея пула подключений заключается в том, что запрос использует соединение на время выполнения запроса. Когда запрос завершается, соединение возвращается в пул. Затем соединение будет повторно использовано для большего количества запросов от разных пользователей.
Я подозреваю, что всевозможные неприятные вещи случаются, когда вы сохраняете соединение в своем сеансе. Вероятно, вы используете соединение, когда оно одновременно используется другим запросом. Или вы можете получить закрытое соединение, потому что пул connectin должен решить закрыть его.
Более того, триггер ВХОДА в систему, вероятно, выполняется только при первом создании подключения к базе данных, но не выполняется снова, когда соединение повторно используется для другого запроса или другого пользователя.
Чтобы устранить вашу проблему, перехватывайте подключение к базе данных в начале каждого запроса и явно задавайте идентификатор клиента (и / или выполняйте код, который запускается триггером входа в систему). Затем используйте это соединение на время выполнения запроса. Но не сохраняйте его нигде после завершения запроса.
Таким образом, ваше соединение всегда правильно инициализируется контекстом текущего пользователя. И вы придерживаетесь правил пула подключений.
Комментарии:
1. Соединение фактически не поддерживается открытым в сеансе. Это просто класс, который содержит функциональность. Соединение открывается и закрывается для каждой операции.
2. На самом деле мы перехватываем подключение к базе данных в начале каждого запроса, устанавливаем . ClientID, запустите любую операцию, затем закройте соединение.
3. Я только что попробовал это и вижу ту же проблему, что и вы. Я не вижу ничего плохого в способе вашего подключения. Сейчас я изучаю ответ.
4. Я полагаю, вопрос в том, является ли новейший ODP.net в этом качестве версия работает вместе с 10g Release 2.
5. Что-то в этом, кажется, терпимо. У меня было старое приложение, которое при подключении к базе данных 11g без проблем обновляло идентификатор клиента сразу после открытия соединения. Теперь у меня версия Oracle 11.2. API доступа к данным для базы данных 11g, и не повезло с обновлением идентификатора клиента или ClientInfo. Предыдущее приложение, которое работало, возможно, использовало ссылку на доступ к данным 10.2.
Ответ №5:
Когда объединение в пул подключений включено, что хорошо и, конечно, путь в ASP.NET сценарий (и фактически в большинстве сценариев), вы не должны хранить какое-либо подключение к БД. Вы должны открывать и закрывать соединения, когда вам это нужно.
Вот ссылка о SQL Server, но это то же самое с Oracle, что объясняет это: класс SqlConnection
Таким образом, код, который вам нужно использовать при вызове Oracle, должен быть примерно таким, в любом месте вашего приложения, когда вам это нужно:
Using connection As New OracleConnection(connectionString)
connection.Open()
' Do work here; connection closed on following line.
End Using
Дело в том, что у вас не может быть включен пул подключений с информацией о конкретном пользователе в строке подключения. Поэтому я предлагаю вам реализовать свой код аудита без использования строки подключения.