diff --git a/ChangeLog/6.0.7_dev.txt b/ChangeLog/6.0.7_dev.txt
index 2f5be49ea7..3c42620964 100644
--- a/ChangeLog/6.0.7_dev.txt
+++ b/ChangeLog/6.0.7_dev.txt
@@ -1,3 +1,4 @@
[main] Fixed issue of actual NULL constant being treated as a caching value
[main] Fixed rare case of infinite loop on batching commands
-[main] Improved VS compatibility by not processing design-time builds
\ No newline at end of file
+[main] Improved VS compatibility by not processing design-time builds
+[main] Introduced IDbConnectionAccessor that gives access to certain stages of connection opening
\ No newline at end of file
diff --git a/Orm/Xtensive.Orm.Firebird/Sql.Drivers.Firebird/DriverFactory.cs b/Orm/Xtensive.Orm.Firebird/Sql.Drivers.Firebird/DriverFactory.cs
index ebef162ec7..375e7bac1a 100644
--- a/Orm/Xtensive.Orm.Firebird/Sql.Drivers.Firebird/DriverFactory.cs
+++ b/Orm/Xtensive.Orm.Firebird/Sql.Drivers.Firebird/DriverFactory.cs
@@ -1,6 +1,6 @@
-// Copyright (C) 2003-2010 Xtensive LLC.
-// All rights reserved.
-// For conditions of distribution and use, see license.
+// Copyright (C) 2011-2021 Xtensive LLC.
+// This code is distributed under MIT license terms.
+// See the License.txt file in the project root for more information.
// Created by: Csaba Beer
// Created: 2011.01.08
@@ -34,24 +34,36 @@ public class DriverFactory : SqlDriverFactory
protected override SqlDriver CreateDriver(string connectionString, SqlDriverConfiguration configuration)
{
using (var connection = new FbConnection(connectionString)) {
- connection.Open();
- SqlHelper.ExecuteInitializationSql(connection, configuration);
- var dataSource = new FbConnectionStringBuilder(connectionString).DataSource;
+ if (configuration.DbConnectionAccessors.Count > 0)
+ OpenConnectionWithNotification(connection, configuration);
+ else
+ OpenConnectionFast(connection, configuration);
var defaultSchema = GetDefaultSchema(connection);
- var coreServerInfo = new CoreServerInfo {
- ServerVersion = GetVersionFromServerVersionString(connection.ServerVersion),
- ConnectionString = connectionString,
- MultipleActiveResultSets = true,
- DatabaseName = defaultSchema.Database,
- DefaultSchemaName = defaultSchema.Schema,
- };
-
- if (Int32.Parse(coreServerInfo.ServerVersion.Major.ToString() + coreServerInfo.ServerVersion.Minor.ToString()) < 25)
- throw new NotSupportedException(Strings.ExFirebirdBelow25IsNotSupported);
- if (coreServerInfo.ServerVersion.Major==2 && coreServerInfo.ServerVersion.Minor==5)
- return new v2_5.Driver(coreServerInfo);
- return null;
+ return CreateDriverInstance(
+ connectionString, GetVersionFromServerVersionString(connection.ServerVersion), defaultSchema);
+ }
+ }
+
+ private static SqlDriver CreateDriverInstance(
+ string connectionString, Version version, DefaultSchemaInfo defaultSchema)
+ {
+ var coreServerInfo = new CoreServerInfo {
+ ServerVersion = version,
+ ConnectionString = connectionString,
+ MultipleActiveResultSets = true,
+ DatabaseName = defaultSchema.Database,
+ DefaultSchemaName = defaultSchema.Schema,
+ };
+
+ if (coreServerInfo.ServerVersion < new Version(2, 5)) {
+ throw new NotSupportedException(Strings.ExFirebirdBelow25IsNotSupported);
}
+
+ if (coreServerInfo.ServerVersion.Major == 2 && coreServerInfo.ServerVersion.Minor == 5) {
+ return new v2_5.Driver(coreServerInfo);
+ }
+
+ return null;
}
///
@@ -94,6 +106,29 @@ protected override DefaultSchemaInfo ReadDefaultSchema(DbConnection connection,
return SqlHelper.ReadDatabaseAndSchema(DatabaseAndSchemaQuery, connection, transaction);
}
+ private void OpenConnectionFast(FbConnection connection, SqlDriverConfiguration configuration)
+ {
+ connection.Open();
+ SqlHelper.ExecuteInitializationSql(connection, configuration);
+ }
+
+ private void OpenConnectionWithNotification(FbConnection connection, SqlDriverConfiguration configuration)
+ {
+ var accessors = configuration.DbConnectionAccessors;
+ SqlHelper.NotifyConnectionOpening(accessors, connection);
+ try {
+ connection.Open();
+ if (!string.IsNullOrEmpty(configuration.ConnectionInitializationSql))
+ SqlHelper.NotifyConnectionInitializing(accessors, connection, configuration.ConnectionInitializationSql);
+ SqlHelper.ExecuteInitializationSql(connection, configuration);
+ SqlHelper.NotifyConnectionOpened(accessors, connection);
+ }
+ catch (Exception ex) {
+ SqlHelper.NotifyConnectionOpeningFailed(accessors, connection, ex);
+ throw;
+ }
+ }
+
private Version GetVersionFromServerVersionString(string serverVersionString)
{
var matcher = new Regex(ServerVersionParser);
diff --git a/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/DriverFactory.cs b/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/DriverFactory.cs
index 0ed434f692..a7adc6adcf 100644
--- a/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/DriverFactory.cs
+++ b/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/DriverFactory.cs
@@ -1,6 +1,6 @@
-// Copyright (C) 2003-2010 Xtensive LLC.
-// All rights reserved.
-// For conditions of distribution and use, see license.
+// Copyright (C) 2011-2021 Xtensive LLC.
+// This code is distributed under MIT license terms.
+// See the License.txt file in the project root for more information.
// Created by: Malisa Ncube
// Created: 2011.02.25
@@ -66,8 +66,10 @@ private static Version ParseVersion(string version)
protected override SqlDriver CreateDriver(string connectionString, SqlDriverConfiguration configuration)
{
using (var connection = new MySqlConnection(connectionString)) {
- connection.Open();
- SqlHelper.ExecuteInitializationSql(connection, configuration);
+ if (configuration.DbConnectionAccessors.Count > 0)
+ OpenConnectionWithNotification(connection, configuration);
+ else
+ OpenConnectionFast(connection, configuration);
var versionString = string.IsNullOrEmpty(configuration.ForcedServerVersion)
? connection.ServerVersion
: configuration.ForcedServerVersion;
@@ -76,32 +78,60 @@ protected override SqlDriver CreateDriver(string connectionString, SqlDriverConf
var builder = new MySqlConnectionStringBuilder(connectionString);
var dataSource = string.Format(DataSourceFormat, builder.Server, builder.Port, builder.Database);
var defaultSchema = GetDefaultSchema(connection);
- var coreServerInfo = new CoreServerInfo {
- ServerVersion = version,
- ConnectionString = connectionString,
- MultipleActiveResultSets = false,
- DatabaseName = defaultSchema.Database,
- DefaultSchemaName = defaultSchema.Schema,
- };
-
- if (version.Major < 5)
- throw new NotSupportedException(Strings.ExMySqlBelow50IsNotSupported);
- if (version.Major==5 && version.Minor==0)
- return new v5_0.Driver(coreServerInfo);
- if (version.Major==5 && version.Minor==1)
- return new v5_1.Driver(coreServerInfo);
- if (version.Major==5 && version.Minor==5)
- return new v5_5.Driver(coreServerInfo);
- if (version.Major==5 && version.Minor==6)
- return new v5_6.Driver(coreServerInfo);
- return new v5_6.Driver(coreServerInfo);
+ return CreateDriverInstance(connectionString, version, defaultSchema);
}
}
+ private static SqlDriver CreateDriverInstance(string connectionString, Version version, DefaultSchemaInfo defaultSchema)
+ {
+ var coreServerInfo = new CoreServerInfo {
+ ServerVersion = version,
+ ConnectionString = connectionString,
+ MultipleActiveResultSets = false,
+ DatabaseName = defaultSchema.Database,
+ DefaultSchemaName = defaultSchema.Schema,
+ };
+
+ if (version.Major < 5) {
+ throw new NotSupportedException(Strings.ExMySqlBelow50IsNotSupported);
+ }
+
+ return version.Major switch {
+ 5 when version.Minor == 0 => new v5_0.Driver(coreServerInfo),
+ 5 when version.Minor == 1 => new v5_1.Driver(coreServerInfo),
+ 5 when version.Minor == 5 => new v5_5.Driver(coreServerInfo),
+ 5 when version.Minor == 6 => new v5_6.Driver(coreServerInfo),
+ _ => new v5_6.Driver(coreServerInfo)
+ };
+ }
+
///
protected override DefaultSchemaInfo ReadDefaultSchema(DbConnection connection, DbTransaction transaction)
{
return SqlHelper.ReadDatabaseAndSchema(DatabaseAndSchemaQuery, connection, transaction);
}
+
+ private void OpenConnectionFast(MySqlConnection connection, SqlDriverConfiguration configuration)
+ {
+ connection.Open();
+ SqlHelper.ExecuteInitializationSql(connection, configuration);
+ }
+
+ private void OpenConnectionWithNotification(MySqlConnection connection, SqlDriverConfiguration configuration)
+ {
+ var accessors = configuration.DbConnectionAccessors;
+ SqlHelper.NotifyConnectionOpening(accessors, connection);
+ try {
+ connection.Open();
+ if (!string.IsNullOrEmpty(configuration.ConnectionInitializationSql))
+ SqlHelper.NotifyConnectionInitializing(accessors, connection, configuration.ConnectionInitializationSql);
+ SqlHelper.ExecuteInitializationSql(connection, configuration);
+ SqlHelper.NotifyConnectionOpened(accessors, connection);
+ }
+ catch (Exception ex) {
+ SqlHelper.NotifyConnectionOpeningFailed(accessors, connection, ex);
+ throw;
+ }
+ }
}
}
\ No newline at end of file
diff --git a/Orm/Xtensive.Orm.Oracle/Sql.Drivers.Oracle/DriverFactory.cs b/Orm/Xtensive.Orm.Oracle/Sql.Drivers.Oracle/DriverFactory.cs
index 56de8f860f..f316d27726 100644
--- a/Orm/Xtensive.Orm.Oracle/Sql.Drivers.Oracle/DriverFactory.cs
+++ b/Orm/Xtensive.Orm.Oracle/Sql.Drivers.Oracle/DriverFactory.cs
@@ -1,6 +1,6 @@
-// Copyright (C) 2003-2010 Xtensive LLC.
-// All rights reserved.
-// For conditions of distribution and use, see license.
+// Copyright (C) 2009-2021 Xtensive LLC.
+// This code is distributed under MIT license terms.
+// See the License.txt file in the project root for more information.
// Created by: Denis Krjuchkov
// Created: 2009.07.16
@@ -66,34 +66,66 @@ protected override string BuildConnectionString(UrlInfo url)
protected override SqlDriver CreateDriver(string connectionString, SqlDriverConfiguration configuration)
{
using (var connection = new OracleConnection(connectionString)) {
- connection.Open();
- SqlHelper.ExecuteInitializationSql(connection, configuration);
+ if (configuration.DbConnectionAccessors.Count > 0)
+ OpenConnectionWithNotification(connection, configuration);
+ else
+ OpenConnectionFast(connection, configuration);
var version = string.IsNullOrEmpty(configuration.ForcedServerVersion)
? ParseVersion(connection.ServerVersion)
: new Version(configuration.ForcedServerVersion);
var dataSource = new OracleConnectionStringBuilder(connectionString).DataSource;
var defaultSchema = GetDefaultSchema(connection);
- var coreServerInfo = new CoreServerInfo {
- ServerVersion = version,
- ConnectionString = connectionString,
- MultipleActiveResultSets = true,
- DatabaseName = defaultSchema.Database,
- DefaultSchemaName = defaultSchema.Schema,
- };
- if (version.Major < 9 || version.Major==9 && version.Minor < 2)
- throw new NotSupportedException(Strings.ExOracleBelow9i2IsNotSupported);
- if (version.Major==9)
- return new v09.Driver(coreServerInfo);
- if (version.Major==10)
- return new v10.Driver(coreServerInfo);
- return new v11.Driver(coreServerInfo);
+ return CreateDriverInstance(connectionString, version, defaultSchema);
}
}
+ private static SqlDriver CreateDriverInstance(string connectionString, Version version, DefaultSchemaInfo defaultSchema)
+ {
+ var coreServerInfo = new CoreServerInfo {
+ ServerVersion = version,
+ ConnectionString = connectionString,
+ MultipleActiveResultSets = true,
+ DatabaseName = defaultSchema.Database,
+ DefaultSchemaName = defaultSchema.Schema,
+ };
+ if (version.Major < 9 || (version.Major == 9 && version.Minor < 2)) {
+ throw new NotSupportedException(Strings.ExOracleBelow9i2IsNotSupported);
+ }
+
+ return version.Major switch {
+ 9 => new v09.Driver(coreServerInfo),
+ 10 => new v10.Driver(coreServerInfo),
+ _ => new v11.Driver(coreServerInfo)
+ };
+ }
+
///
protected override DefaultSchemaInfo ReadDefaultSchema(DbConnection connection, DbTransaction transaction)
{
return SqlHelper.ReadDatabaseAndSchema(DatabaseAndSchemaQuery, connection, transaction);
}
+
+ private void OpenConnectionFast(OracleConnection connection, SqlDriverConfiguration configuration)
+ {
+ connection.Open();
+ SqlHelper.ExecuteInitializationSql(connection, configuration);
+ }
+
+ private void OpenConnectionWithNotification(OracleConnection connection, SqlDriverConfiguration configuration)
+ {
+ var accessors = configuration.DbConnectionAccessors;
+ SqlHelper.NotifyConnectionOpening(accessors, connection);
+ try {
+ connection.Open();
+ if (!string.IsNullOrEmpty(configuration.ConnectionInitializationSql))
+ SqlHelper.NotifyConnectionInitializing(accessors, connection, configuration.ConnectionInitializationSql);
+ SqlHelper.ExecuteInitializationSql(connection, configuration);
+ SqlHelper.NotifyConnectionOpened(accessors, connection);
+ }
+ catch (Exception ex) {
+ SqlHelper.NotifyConnectionOpeningFailed(accessors, connection, ex);
+ throw;
+ }
+ }
}
}
\ No newline at end of file
diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/DriverFactory.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/DriverFactory.cs
index 68ae309abd..0b857669f8 100644
--- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/DriverFactory.cs
+++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/DriverFactory.cs
@@ -1,4 +1,4 @@
-// Copyright (C) 2009-2020 Xtensive LLC.
+// Copyright (C) 2009-2021 Xtensive LLC.
// This code is distributed under MIT license terms.
// See the License.txt file in the project root for more information.
// Created by: Denis Krjuchkov
@@ -60,45 +60,76 @@ protected override string BuildConnectionString(UrlInfo url)
protected override SqlDriver CreateDriver(string connectionString, SqlDriverConfiguration configuration)
{
using (var connection = new NpgsqlConnection(connectionString)) {
- connection.Open();
- SqlHelper.ExecuteInitializationSql(connection, configuration);
+ if (configuration.DbConnectionAccessors.Count > 0)
+ OpenConnectionWithNotification(connection, configuration);
+ else
+ OpenConnectionFast(connection, configuration);
var version = string.IsNullOrEmpty(configuration.ForcedServerVersion)
? connection.PostgreSqlVersion
: new Version(configuration.ForcedServerVersion);
var builder = new NpgsqlConnectionStringBuilder(connectionString);
var dataSource = string.Format(DataSourceFormat, builder.Host, builder.Port, builder.Database);
var defaultSchema = GetDefaultSchema(connection);
- var coreServerInfo = new CoreServerInfo {
- ServerVersion = version,
- ConnectionString = connectionString,
- MultipleActiveResultSets = false,
- DatabaseName = defaultSchema.Database,
- DefaultSchemaName = defaultSchema.Schema,
- };
-
- if (version.Major < 8 || version.Major==8 && version.Minor < 3) {
- throw new NotSupportedException(Strings.ExPostgreSqlBelow83IsNotSupported);
- }
-
- // We support 8.3, 8.4 and any 9.0+
-
- if (version.Major == 8) {
- return version.Minor == 3
- ? new v8_3.Driver(coreServerInfo)
- : new v8_4.Driver(coreServerInfo);
- }
-
- if (version.Major == 9) {
- return version.Minor == 0
- ? new v9_0.Driver(coreServerInfo)
- : new v9_1.Driver(coreServerInfo);
- }
- return new v10_0.Driver(coreServerInfo);
+ return CreateDriverInstance(connectionString, version, defaultSchema);
+ }
+ }
+
+ private static SqlDriver CreateDriverInstance(
+ string connectionString, Version version, DefaultSchemaInfo defaultSchema)
+ {
+ var coreServerInfo = new CoreServerInfo {
+ ServerVersion = version,
+ ConnectionString = connectionString,
+ MultipleActiveResultSets = false,
+ DatabaseName = defaultSchema.Database,
+ DefaultSchemaName = defaultSchema.Schema,
+ };
+
+ if (version.Major < 8 || (version.Major == 8 && version.Minor < 3)) {
+ throw new NotSupportedException(Strings.ExPostgreSqlBelow83IsNotSupported);
+ }
+
+ // We support 8.3, 8.4 and any 9.0+
+
+ if (version.Major == 8) {
+ return version.Minor == 3
+ ? new v8_3.Driver(coreServerInfo)
+ : new v8_4.Driver(coreServerInfo);
+ }
+
+ if (version.Major == 9) {
+ return version.Minor == 0
+ ? new v9_0.Driver(coreServerInfo)
+ : new v9_1.Driver(coreServerInfo);
}
+ return new v10_0.Driver(coreServerInfo);
}
///
protected override DefaultSchemaInfo ReadDefaultSchema(DbConnection connection, DbTransaction transaction) =>
SqlHelper.ReadDatabaseAndSchema(DatabaseAndSchemaQuery, connection, transaction);
+
+ private void OpenConnectionFast(NpgsqlConnection connection, SqlDriverConfiguration configuration)
+ {
+ connection.Open();
+ SqlHelper.ExecuteInitializationSql(connection, configuration);
+ }
+
+ private void OpenConnectionWithNotification(NpgsqlConnection connection, SqlDriverConfiguration configuration)
+ {
+ var accessors = configuration.DbConnectionAccessors;
+ SqlHelper.NotifyConnectionOpening(accessors, connection);
+ try {
+ connection.Open();
+ if (!string.IsNullOrEmpty(configuration.ConnectionInitializationSql))
+ SqlHelper.NotifyConnectionInitializing(accessors, connection, configuration.ConnectionInitializationSql);
+ SqlHelper.ExecuteInitializationSql(connection, configuration);
+ SqlHelper.NotifyConnectionOpened(accessors, connection);
+ }
+ catch (Exception ex) {
+ SqlHelper.NotifyConnectionOpeningFailed(accessors, connection, ex);
+ throw;
+ }
+ }
}
}
\ No newline at end of file
diff --git a/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/Connection.cs b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/Connection.cs
index 1fb0abf897..b25df18b53 100644
--- a/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/Connection.cs
+++ b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/Connection.cs
@@ -1,4 +1,4 @@
-// Copyright (C) 2009-2020 Xtensive LLC.
+// Copyright (C) 2009-2021 Xtensive LLC.
// This code is distributed under MIT license terms.
// See the License.txt file in the project root for more information.
// Created by: Denis Krjuchkov
@@ -38,20 +38,35 @@ public override DbParameter CreateParameter()
///
public override void Open()
{
- if (!checkConnectionIsAlive)
+ if (!checkConnectionIsAlive) {
base.Open();
- else
- OpenWithCheck(DefaultCheckConnectionQuery);
+ }
+ else {
+ var connectionAccessorEx = Extensions.Get();
+ if (connectionAccessorEx == null) {
+ OpenWithCheckFast(DefaultCheckConnectionQuery);
+ }
+ else {
+ OpenWithCheckAndNotification(DefaultCheckConnectionQuery, connectionAccessorEx);
+ }
+ }
}
///
public override Task OpenAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
- if (!checkConnectionIsAlive)
+ if (!checkConnectionIsAlive) {
return base.OpenAsync(cancellationToken);
- else
- return OpenWithCheckAsync(DefaultCheckConnectionQuery, cancellationToken);
+ }
+
+ var connectionAccessorEx = Extensions.Get();
+ if (connectionAccessorEx == null) {
+ return OpenWithCheckFastAsync(DefaultCheckConnectionQuery, cancellationToken);
+ }
+ else {
+ return OpenWithCheckAndNotificationAsync(DefaultCheckConnectionQuery, connectionAccessorEx, cancellationToken);
+ }
}
///
@@ -65,7 +80,13 @@ public override void OpenAndInitialize(string initializationScript)
var script = string.IsNullOrEmpty(initializationScript.Trim())
? DefaultCheckConnectionQuery
: initializationScript;
- OpenWithCheck(script);
+ var connectionAccessorEx = Extensions.Get();
+ if (connectionAccessorEx == null) {
+ OpenWithCheckFast(script);
+ }
+ else {
+ OpenWithCheckAndNotification(script, connectionAccessorEx);
+ }
}
///
@@ -77,7 +98,10 @@ public override Task OpenAndInitializeAsync(string initializationScript, Cancell
var script = string.IsNullOrEmpty(initializationScript.Trim())
? DefaultCheckConnectionQuery
: initializationScript;
- return OpenWithCheckAsync(script, cancellationToken);
+ var connectionAccessorEx = Extensions.Get();
+ return connectionAccessorEx == null
+ ? OpenWithCheckFastAsync(script, cancellationToken)
+ : OpenWithCheckAndNotificationAsync(script, connectionAccessorEx, cancellationToken);
}
///
@@ -131,16 +155,16 @@ protected override void ClearActiveTransaction()
activeTransaction = null;
}
- private void OpenWithCheck(string checkQueryString)
+ private void OpenWithCheckFast(string checkQueryString)
{
- bool connectionChecked = false;
- bool restoreTriggered = false;
+ var connectionChecked = false;
+ var restoreTriggered = false;
while (!connectionChecked) {
base.Open();
try {
using (var command = underlyingConnection.CreateCommand()) {
command.CommandText = checkQueryString;
- command.ExecuteNonQuery();
+ _ = command.ExecuteNonQuery();
}
connectionChecked = true;
}
@@ -160,16 +184,57 @@ private void OpenWithCheck(string checkQueryString)
restoreTriggered = true;
continue;
}
- else
- throw;
+
+ throw;
}
}
}
- private async Task OpenWithCheckAsync(string checkQueryString, CancellationToken cancellationToken)
+ private void OpenWithCheckAndNotification(string checkQueryString, DbConnectionAccessorExtension connectionAccessorEx)
{
- bool connectionChecked = false;
- bool restoreTriggered = false;
+ var connectionChecked = false;
+ var restoreTriggered = false;
+ var accessors = connectionAccessorEx.Accessors;
+ while (!connectionChecked) {
+ SqlHelper.NotifyConnectionOpening(accessors, UnderlyingConnection, (!connectionChecked && !restoreTriggered));
+ underlyingConnection.Open();
+ try {
+ SqlHelper.NotifyConnectionInitializing(accessors, UnderlyingConnection, checkQueryString, (!connectionChecked && !restoreTriggered));
+ using (var command = underlyingConnection.CreateCommand()) {
+ command.CommandText = checkQueryString;
+ _ = command.ExecuteNonQuery();
+ }
+ connectionChecked = true;
+ SqlHelper.NotifyConnectionOpened(accessors, UnderlyingConnection, (!connectionChecked && !restoreTriggered));
+ }
+ catch (Exception exception) {
+ SqlHelper.NotifyConnectionOpeningFailed(accessors, UnderlyingConnection, exception, (!connectionChecked && !restoreTriggered));
+ if (InternalHelpers.ShouldRetryOn(exception)) {
+ if (restoreTriggered) {
+ throw;
+ }
+
+ var newConnection = new SqlServerConnection(underlyingConnection.ConnectionString);
+ try {
+ underlyingConnection.Close();
+ underlyingConnection.Dispose();
+ }
+ catch { }
+
+ underlyingConnection = newConnection;
+ restoreTriggered = true;
+ continue;
+ }
+
+ throw;
+ }
+ }
+ }
+
+ private async Task OpenWithCheckFastAsync(string checkQueryString, CancellationToken cancellationToken)
+ {
+ var connectionChecked = false;
+ var restoreTriggered = false;
while (!connectionChecked) {
cancellationToken.ThrowIfCancellationRequested();
@@ -177,11 +242,60 @@ private async Task OpenWithCheckAsync(string checkQueryString, CancellationToken
try {
using (var command = underlyingConnection.CreateCommand()) {
command.CommandText = checkQueryString;
- await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
+ _ = await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
+ }
+ connectionChecked = true;
+ }
+ catch (Exception exception) {
+ if (InternalHelpers.ShouldRetryOn(exception)) {
+ if (restoreTriggered) {
+ throw;
+ }
+ var newConnection = new SqlServerConnection(underlyingConnection.ConnectionString);
+ try {
+ underlyingConnection.Close();
+ underlyingConnection.Dispose();
+ }
+ catch { }
+
+ underlyingConnection = newConnection;
+ restoreTriggered = true;
+ continue;
+ }
+
+ throw;
+ }
+ }
+ }
+
+ private async Task OpenWithCheckAndNotificationAsync(string checkQueryString,
+ DbConnectionAccessorExtension connectionAccessorEx, CancellationToken cancellationToken)
+ {
+ var connectionChecked = false;
+ var restoreTriggered = false;
+ var accessors = connectionAccessorEx.Accessors;
+
+ while (!connectionChecked) {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ SqlHelper.NotifyConnectionOpening(accessors, UnderlyingConnection, !connectionChecked && !restoreTriggered);
+
+ await underlyingConnection.OpenAsync(cancellationToken).ConfigureAwait(false);
+ try {
+ SqlHelper.NotifyConnectionInitializing(accessors,
+ UnderlyingConnection, checkQueryString, !connectionChecked && !restoreTriggered);
+
+ using (var command = underlyingConnection.CreateCommand()) {
+ command.CommandText = checkQueryString;
+ _ = await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
}
connectionChecked = true;
+ SqlHelper.NotifyConnectionOpened(accessors, UnderlyingConnection, !connectionChecked && !restoreTriggered);
}
catch (Exception exception) {
+ SqlHelper.NotifyConnectionOpeningFailed(accessors,
+ UnderlyingConnection, exception, (!connectionChecked && !restoreTriggered));
+
if (InternalHelpers.ShouldRetryOn(exception)) {
if (restoreTriggered) {
throw;
@@ -197,8 +311,8 @@ private async Task OpenWithCheckAsync(string checkQueryString, CancellationToken
restoreTriggered = true;
continue;
}
- else
- throw;
+
+ throw;
}
}
}
diff --git a/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/DriverFactory.cs b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/DriverFactory.cs
index 512ec74bff..e9833aa2e6 100644
--- a/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/DriverFactory.cs
+++ b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/DriverFactory.cs
@@ -27,6 +27,7 @@ public class DriverFactory : SqlDriverFactory
private const string DatabaseAndSchemaQuery = "SELECT DB_NAME(), COALESCE(SCHEMA_NAME(), 'dbo')";
+ private const string LangIdQuery = "SELECT @@LANGID";
private const string MessagesQuery = @"Declare @MSGLANGID int;
Select @MSGLANGID = msglangid FROM [master].[sys].[syslanguages] lang
WHERE lang.langid = @@LANGID;
@@ -34,11 +35,15 @@ public class DriverFactory : SqlDriverFactory
FROM [master].[sys].[sysmessages] msg
WHERE msg.msglangid = @MSGLANGID AND msg.error IN ( 2627, 2601, 515, 547 )";
+ private const string VersionQuery = "SELECT @@VERSION";
+
+ private const string ForcedAzureVersion = "12.0.0.0";
+
private static ErrorMessageParser CreateMessageParser(SqlServerConnection connection)
{
bool isEnglish;
using (var command = connection.CreateCommand()) {
- command.CommandText = "SELECT @@LANGID";
+ command.CommandText = LangIdQuery;
isEnglish = command.ExecuteScalar().ToString()=="0";
}
var templates = new Dictionary();
@@ -54,7 +59,7 @@ private static ErrorMessageParser CreateMessageParser(SqlServerConnection connec
private static bool IsAzure(SqlServerConnection connection)
{
using (var command = connection.CreateCommand()) {
- command.CommandText = "SELECT @@VERSION";
+ command.CommandText = VersionQuery;
return ((string) command.ExecuteScalar()).IndexOf("Azure", StringComparison.Ordinal) >= 0;
}
}
@@ -94,116 +99,219 @@ protected override string BuildConnectionString(UrlInfo url)
protected override SqlDriver CreateDriver(string connectionString, SqlDriverConfiguration configuration)
{
var isPooingOn = !IsPoolingOff(connectionString);
- configuration.EnsureConnectionIsAlive = isPooingOn && configuration.EnsureConnectionIsAlive;
+ configuration.EnsureConnectionIsAlive &= isPooingOn;
- using (var connection = CreateAndOpenConnection(connectionString, configuration)) {
- string versionString;
- bool isAzure;
+ using var connection = CreateAndOpenConnection(connectionString, configuration);
+ var isEnsureAlive = configuration.EnsureConnectionIsAlive;
+ var forcedServerVersion = configuration.ForcedServerVersion;
+ var isForcedVersion = !string.IsNullOrEmpty(forcedServerVersion);
+ var isForcedAzure = isForcedVersion && forcedServerVersion.Equals("azure", StringComparison.OrdinalIgnoreCase);
+ var isAzure = isForcedAzure || (!isForcedVersion && IsAzure(connection));
+ var parser = isAzure ? new ErrorMessageParser() : CreateMessageParser(connection);
+
+ var versionString = isForcedVersion
+ ? isForcedAzure ? ForcedAzureVersion : forcedServerVersion
+ : connection.ServerVersion ?? string.Empty;
+ var version = new Version(versionString);
+ var defaultSchema = GetDefaultSchema(connection);
- var forcedServerVersion = configuration.ForcedServerVersion;
- if (string.IsNullOrEmpty(forcedServerVersion)) {
- versionString = connection.ServerVersion;
- isAzure = IsAzure(connection);
- }
- else if (forcedServerVersion.Equals("azure", StringComparison.OrdinalIgnoreCase)) {
- versionString = "12.0.0.0";
- isAzure = true;
- }
- else {
- versionString = forcedServerVersion;
- isAzure = false;
- }
+ return CreateDriverInstance(connectionString, isAzure, version, defaultSchema, parser, isEnsureAlive);
+ }
- var builder = new SqlConnectionStringBuilder(connectionString);
- var version = new Version(versionString);
- var defaultSchema = GetDefaultSchema(connection);
- var coreServerInfo = new CoreServerInfo {
- ServerVersion = version,
- ConnectionString = connectionString,
- MultipleActiveResultSets = builder.MultipleActiveResultSets,
- DatabaseName = defaultSchema.Database,
- DefaultSchemaName = defaultSchema.Schema,
- };
- if (isAzure)
- return new Azure.Driver(coreServerInfo, new ErrorMessageParser(), configuration.EnsureConnectionIsAlive);
- if (version.Major < 9)
- throw new NotSupportedException(Strings.ExSqlServerBelow2005IsNotSupported);
- var parser = CreateMessageParser(connection);
- if (version.Major==9)
- return new v09.Driver(coreServerInfo, parser, configuration.EnsureConnectionIsAlive);
- if (version.Major==10)
- return new v10.Driver(coreServerInfo, parser, configuration.EnsureConnectionIsAlive);
- if (version.Major==11)
- return new v11.Driver(coreServerInfo, parser, configuration.EnsureConnectionIsAlive);
- if (version.Major==12)
- return new v12.Driver(coreServerInfo, parser, configuration.EnsureConnectionIsAlive);
- if (version.Major==13)
- return new v13.Driver(coreServerInfo, parser, configuration.EnsureConnectionIsAlive);
- return new v13.Driver(coreServerInfo, parser, configuration.EnsureConnectionIsAlive);
+ private static SqlDriver CreateDriverInstance(string connectionString, bool isAzure, Version version,
+ DefaultSchemaInfo defaultSchema, ErrorMessageParser parser, bool isEnsureAlive)
+ {
+ var builder = new SqlConnectionStringBuilder(connectionString);
+ var coreServerInfo = new CoreServerInfo {
+ ServerVersion = version,
+ ConnectionString = connectionString,
+ MultipleActiveResultSets = builder.MultipleActiveResultSets,
+ DatabaseName = defaultSchema.Database,
+ DefaultSchemaName = defaultSchema.Schema,
+ };
+ if (isAzure) {
+ return new Azure.Driver(coreServerInfo, parser, isEnsureAlive);
}
+
+ if (version.Major < 9) {
+ throw new NotSupportedException(Strings.ExSqlServerBelow2005IsNotSupported);
+ }
+ return version.Major switch {
+ 9 => new v09.Driver(coreServerInfo, parser, isEnsureAlive),
+ 10 => new v10.Driver(coreServerInfo, parser, isEnsureAlive),
+ 11 => new v11.Driver(coreServerInfo, parser, isEnsureAlive),
+ 12 => new v12.Driver(coreServerInfo, parser, isEnsureAlive),
+ 13 => new v13.Driver(coreServerInfo, parser, isEnsureAlive),
+ _ => new v13.Driver(coreServerInfo, parser, isEnsureAlive)
+ };
}
///
- protected override DefaultSchemaInfo ReadDefaultSchema(DbConnection connection, DbTransaction transaction)
- {
- return SqlHelper.ReadDatabaseAndSchema(DatabaseAndSchemaQuery, connection, transaction);
- }
+ protected override DefaultSchemaInfo ReadDefaultSchema(DbConnection connection, DbTransaction transaction) =>
+ SqlHelper.ReadDatabaseAndSchema(DatabaseAndSchemaQuery, connection, transaction);
private SqlServerConnection CreateAndOpenConnection(string connectionString, SqlDriverConfiguration configuration)
{
var connection = new SqlServerConnection(connectionString);
+ var initScript = configuration.ConnectionInitializationSql;
+
if (!configuration.EnsureConnectionIsAlive) {
- connection.Open();
- SqlHelper.ExecuteInitializationSql(connection, configuration);
+ if (configuration.DbConnectionAccessors.Count == 0)
+ OpenConnectionFast(connection, initScript);
+ else
+ OpenConnectionWithNotification(connection, configuration);
return connection;
}
- var testQuery = (string.IsNullOrEmpty(configuration.ConnectionInitializationSql))
+ var testQuery = string.IsNullOrEmpty(initScript)
? CheckConnectionQuery
- : configuration.ConnectionInitializationSql;
+ : initScript;
+ if (configuration.DbConnectionAccessors.Count == 0)
+ return EnsureConnectionIsAliveFast(connection, testQuery);
+ else
+ return EnsureConnectionIsAliveWithNotification(connection, testQuery, configuration.DbConnectionAccessors);
+ }
+
+ private static void OpenConnectionFast(SqlServerConnection connection, string sqlScript)
+ {
connection.Open();
- EnsureConnectionIsAlive(ref connection, testQuery);
- return connection;
+ SqlHelper.ExecuteInitializationSql(connection, sqlScript);
}
- private void EnsureConnectionIsAlive(ref SqlServerConnection connection, string query)
+ private static void OpenConnectionWithNotification(SqlServerConnection connection,
+ SqlDriverConfiguration configuration)
{
+ var accessors = configuration.DbConnectionAccessors;
+ var initSql = configuration.ConnectionInitializationSql;
+
+ SqlHelper.NotifyConnectionOpening(accessors, connection);
try {
+ connection.Open();
+ if (!string.IsNullOrEmpty(initSql)) {
+ SqlHelper.NotifyConnectionInitializing(accessors, connection, initSql);
+ SqlHelper.ExecuteInitializationSql(connection, initSql);
+ }
+ SqlHelper.NotifyConnectionOpened(accessors, connection);
+ }
+ catch (Exception ex) {
+ SqlHelper.NotifyConnectionOpeningFailed(accessors, connection, ex);
+ throw;
+ }
+ }
+
+ private static SqlServerConnection EnsureConnectionIsAliveFast(SqlServerConnection connection, string query)
+ {
+ try {
+ connection.Open();
+
using (var command = connection.CreateCommand()) {
command.CommandText = query;
- command.ExecuteNonQuery();
+ _ = command.ExecuteNonQuery();
}
+
+ return connection;
}
catch (Exception exception) {
+ try {
+ connection.Close();
+ connection.Dispose();
+ }
+ catch {
+ // ignored
+ }
+
if (InternalHelpers.ShouldRetryOn(exception)) {
- if (!TryReconnect(ref connection, query))
- throw;
+ var (isReconnected, newConnection) = TryReconnectFast(connection.ConnectionString, query);
+ if (isReconnected) {
+ return newConnection;
+ }
}
- else
- throw;
+ throw;
}
}
- private static bool TryReconnect(ref SqlServerConnection connection, string query)
+ private static SqlServerConnection EnsureConnectionIsAliveWithNotification(SqlServerConnection connection,
+ string query, IReadOnlyCollection connectionAccessors)
{
+ SqlHelper.NotifyConnectionOpening(connectionAccessors, connection);
try {
- var newConnection = new SqlServerConnection(connection.ConnectionString);
+ connection.Open();
+
+ SqlHelper.NotifyConnectionInitializing(connectionAccessors, connection, query);
+
+ using (var command = connection.CreateCommand()) {
+ command.CommandText = query;
+ _ = command.ExecuteNonQuery();
+ }
+
+ SqlHelper.NotifyConnectionOpened(connectionAccessors, connection);
+ return connection;
+ }
+ catch (Exception exception) {
+ var retryToConnect = InternalHelpers.ShouldRetryOn(exception);
+ if (!retryToConnect)
+ SqlHelper.NotifyConnectionOpeningFailed(connectionAccessors, connection, exception);
try {
connection.Close();
connection.Dispose();
}
- catch { }
+ catch {
+ // ignored
+ }
+
+ if (retryToConnect) {
+ var (isReconnected, newConnection) = TryReconnectWithNotification(connection.ConnectionString, query, connectionAccessors);
+ if (isReconnected) {
+ return newConnection;
+ }
+ }
+ throw;
+ }
+ }
+
+ private static (bool isReconnected, SqlServerConnection connection) TryReconnectFast(
+ string connectionString, string query)
+ {
+ var connection = new SqlServerConnection(connectionString);
- connection = newConnection;
+ try {
+ connection.Open();
+
+ using (var command = connection.CreateCommand()) {
+ command.CommandText = query;
+ _ = command.ExecuteNonQuery();
+ }
+
+ return (true, connection);
+ }
+ catch {
+ connection.Dispose();
+ return (false, null);
+ }
+ }
+
+ private static (bool isReconnected, SqlServerConnection connection) TryReconnectWithNotification(
+ string connectionString, string query, IReadOnlyCollection connectionAccessors)
+ {
+ var connection = new SqlServerConnection(connectionString);
+
+ SqlHelper.NotifyConnectionOpening(connectionAccessors, connection, true);
+ try {
connection.Open();
+ SqlHelper.NotifyConnectionInitializing(connectionAccessors, connection, query, true);
+
using (var command = connection.CreateCommand()) {
command.CommandText = query;
- command.ExecuteNonQuery();
+ _ = command.ExecuteNonQuery();
}
- return true;
+
+ SqlHelper.NotifyConnectionOpened(connectionAccessors, connection, true);
+ return (true, connection);
}
- catch (Exception) {
- return false;
+ catch (Exception exception) {
+ SqlHelper.NotifyConnectionOpeningFailed(connectionAccessors, connection, exception, true);
+ connection.Dispose();
+ return (false, null);
}
}
diff --git a/Orm/Xtensive.Orm.Sqlite/Sql.Drivers.Sqlite/DriverFactory.cs b/Orm/Xtensive.Orm.Sqlite/Sql.Drivers.Sqlite/DriverFactory.cs
index e617f92645..9393507190 100644
--- a/Orm/Xtensive.Orm.Sqlite/Sql.Drivers.Sqlite/DriverFactory.cs
+++ b/Orm/Xtensive.Orm.Sqlite/Sql.Drivers.Sqlite/DriverFactory.cs
@@ -1,6 +1,6 @@
-// Copyright (C) 2003-2010 Xtensive LLC.
-// All rights reserved.
-// For conditions of distribution and use, see license.
+// Copyright (C) 2011-2021 Xtensive LLC.
+// This code is distributed under MIT license terms.
+// See the License.txt file in the project root for more information.
// Created by: Malisa Ncube
// Created: 2011.04.29
@@ -42,22 +42,32 @@ protected override SqlDriver CreateDriver(string connectionString, SqlDriverConf
private SqlDriver DoCreateDriver(string connectionString, SqlDriverConfiguration configuration)
{
using (var connection = new SQLiteConnection(connectionString)) {
- connection.Open();
- SqlHelper.ExecuteInitializationSql(connection, configuration);
+ if (configuration.DbConnectionAccessors.Count > 0)
+ OpenConnectionWithNotification(connection, configuration);
+ else
+ OpenConnectionFast(connection, configuration);
var version = new Version(connection.ServerVersion);
var defaultSchema = GetDefaultSchema(connection);
- var coreServerInfo = new CoreServerInfo {
- ServerVersion = version,
- ConnectionString = connectionString,
- MultipleActiveResultSets = false,
- DatabaseName = defaultSchema.Database,
- DefaultSchemaName = defaultSchema.Schema,
- };
+ return CreateDriverInstance(connectionString, version, defaultSchema);
+ }
+ }
- if (version.Major < 3)
- throw new NotSupportedException(Strings.ExSqlLiteServerBelow3IsNotSupported);
- return new v3.Driver(coreServerInfo);
+ private static SqlDriver CreateDriverInstance(string connectionString, Version version,
+ DefaultSchemaInfo defaultSchema)
+ {
+ var coreServerInfo = new CoreServerInfo {
+ ServerVersion = version,
+ ConnectionString = connectionString,
+ MultipleActiveResultSets = false,
+ DatabaseName = defaultSchema.Database,
+ DefaultSchemaName = defaultSchema.Schema,
+ };
+
+ if (version.Major < 3) {
+ throw new NotSupportedException(Strings.ExSqlLiteServerBelow3IsNotSupported);
}
+
+ return new v3.Driver(coreServerInfo);
}
///
@@ -77,5 +87,28 @@ protected override DefaultSchemaInfo ReadDefaultSchema(DbConnection connection,
{
return new DefaultSchemaInfo(GetDataSource(connection.ConnectionString), Extractor.DefaultSchemaName);
}
+
+ private void OpenConnectionFast(SQLiteConnection connection, SqlDriverConfiguration configuration)
+ {
+ connection.Open();
+ SqlHelper.ExecuteInitializationSql(connection, configuration);
+ }
+
+ private void OpenConnectionWithNotification(SQLiteConnection connection, SqlDriverConfiguration configuration)
+ {
+ var accessors = configuration.DbConnectionAccessors;
+ SqlHelper.NotifyConnectionOpening(accessors, connection);
+ try {
+ connection.Open();
+ if (!string.IsNullOrEmpty(configuration.ConnectionInitializationSql))
+ SqlHelper.NotifyConnectionInitializing(accessors, connection, configuration.ConnectionInitializationSql);
+ SqlHelper.ExecuteInitializationSql(connection, configuration);
+ SqlHelper.NotifyConnectionOpened(accessors, connection);
+ }
+ catch (Exception ex) {
+ SqlHelper.NotifyConnectionOpeningFailed(accessors, connection, ex);
+ throw;
+ }
+ }
}
}
\ No newline at end of file
diff --git a/Orm/Xtensive.Orm.Tests.Sql/DriverFactoryTest.cs b/Orm/Xtensive.Orm.Tests.Sql/DriverFactoryTest.cs
index 493576c3b2..bb800ce5df 100644
--- a/Orm/Xtensive.Orm.Tests.Sql/DriverFactoryTest.cs
+++ b/Orm/Xtensive.Orm.Tests.Sql/DriverFactoryTest.cs
@@ -1,4 +1,4 @@
-// Copyright (C) 2003-2010 Xtensive LLC.
+// Copyright (C) 2009-2021 Xtensive LLC.
// This code is distributed under MIT license terms.
// See the License.txt file in the project root for more information.
@@ -8,6 +8,44 @@
using Xtensive.Orm;
using Xtensive.Orm.Building.Builders;
using Xtensive.Sql;
+using Xtensive.Orm.Tests.Sql.DriverFactoryTestTypes;
+
+namespace Xtensive.Orm.Tests.Sql.DriverFactoryTestTypes
+{
+ public class TestConnectionAccessor : DbConnectionAccessor
+ {
+ public int OpeningCounter = 0;
+ public int OpenedCounter = 0;
+ public int OpeningInitCounter = 0;
+ public int OpeningFailedCounter = 0;
+
+ public override void ConnectionOpening(ConnectionEventData eventData)
+ {
+ OpeningCounter++;
+ }
+
+ public override void ConnectionOpened(ConnectionEventData eventData)
+ {
+ OpenedCounter++;
+ }
+
+ public override void ConnectionInitialization(ConnectionInitEventData eventData)
+ {
+ OpeningInitCounter++;
+ }
+
+ public override void ConnectionOpeningFailed(ConnectionErrorEventData eventData)
+ {
+ OpeningFailedCounter++;
+ }
+ }
+
+ public static class StaticCounter
+ {
+ public static int OpeningReached;
+ public static int OpenedReached;
+ }
+}
namespace Xtensive.Orm.Tests.Sql
{
@@ -95,6 +133,61 @@ public void SqlServerConnectionCheckTest()
Assert.That(GetCheckConnectionIsAliveFlag(driver), Is.False);
}
+ [Test]
+ public void ConnectionAccessorTest()
+ {
+ var accessorInstance = new TestConnectionAccessor();
+ var accessorsArray = new[] { accessorInstance };
+ var descriptor = ProviderDescriptor.Get(provider);
+ var factory = (SqlDriverFactory) Activator.CreateInstance(descriptor.DriverFactory);
+
+ Assert.That(accessorInstance.OpeningCounter, Is.EqualTo(0));
+ Assert.That(accessorInstance.OpeningInitCounter, Is.EqualTo(0));
+ Assert.That(accessorInstance.OpenedCounter, Is.EqualTo(0));
+ Assert.That(accessorInstance.OpeningFailedCounter, Is.EqualTo(0));
+
+ var configuration = new SqlDriverConfiguration(accessorsArray);
+ _ = factory.GetDriver(new ConnectionInfo(Url), configuration);
+ Assert.That(accessorInstance.OpeningCounter, Is.EqualTo(1));
+ Assert.That(accessorInstance.OpeningInitCounter, Is.EqualTo(0));
+ Assert.That(accessorInstance.OpenedCounter, Is.EqualTo(1));
+ Assert.That(accessorInstance.OpeningFailedCounter, Is.EqualTo(0));
+
+ configuration = new SqlDriverConfiguration(accessorsArray) { EnsureConnectionIsAlive = true };
+ _ = factory.GetDriver(new ConnectionInfo(Url), configuration);
+ Assert.That(accessorInstance.OpeningCounter, Is.EqualTo(2));
+ if (provider == WellKnown.Provider.SqlServer)
+ Assert.That(accessorInstance.OpeningInitCounter, Is.EqualTo(1));
+ else
+ Assert.That(accessorInstance.OpeningInitCounter, Is.EqualTo(0));
+ Assert.That(accessorInstance.OpenedCounter, Is.EqualTo(2));
+ Assert.That(accessorInstance.OpeningFailedCounter, Is.EqualTo(0));
+
+ configuration = new SqlDriverConfiguration(accessorsArray) { ConnectionInitializationSql = InitQueryPerProvider(provider) };
+ _ = factory.GetDriver(new ConnectionInfo(Url), configuration);
+ Assert.That(accessorInstance.OpeningCounter, Is.EqualTo(3));
+ if (provider == WellKnown.Provider.SqlServer)
+ Assert.That(accessorInstance.OpeningInitCounter, Is.EqualTo(2));
+ else
+ Assert.That(accessorInstance.OpeningInitCounter, Is.EqualTo(1));
+ Assert.That(accessorInstance.OpenedCounter, Is.EqualTo(3));
+ Assert.That(accessorInstance.OpeningFailedCounter, Is.EqualTo(0));
+
+ configuration = new SqlDriverConfiguration(accessorsArray) { ConnectionInitializationSql = "dummy string to trigger error" };
+ try {
+ _ = factory.GetDriver(new ConnectionInfo(Url), configuration);
+ }
+ catch {
+ //skip it
+ }
+ Assert.That(accessorInstance.OpeningCounter, Is.EqualTo(4));
+ if (provider == WellKnown.Provider.SqlServer)
+ Assert.That(accessorInstance.OpeningInitCounter, Is.EqualTo(3));
+ else
+ Assert.That(accessorInstance.OpeningInitCounter, Is.EqualTo(2));
+ Assert.That(accessorInstance.OpenedCounter, Is.EqualTo(3));
+ Assert.That(accessorInstance.OpeningFailedCounter, Is.EqualTo(1));
+ }
private static void TestProvider(string providerName, string connectionString, string connectionUrl)
{
@@ -109,5 +202,25 @@ private static bool GetCheckConnectionIsAliveFlag(SqlDriver driver)
return (bool) type.GetField(fieldName, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)
.GetValue(driver);
}
+
+ private static string InitQueryPerProvider(string currentProvider)
+ {
+ switch (currentProvider) {
+ case WellKnown.Provider.Firebird:
+ return "select current_timestamp from RDB$DATABASE;";
+ case WellKnown.Provider.MySql:
+ return "SELECT 0";
+ case WellKnown.Provider.Oracle:
+ return "select current_timestamp from DUAL";
+ case WellKnown.Provider.PostgreSql:
+ return "SELECT 0";
+ case WellKnown.Provider.SqlServer:
+ return "SELECT 0";
+ case WellKnown.Provider.Sqlite:
+ return "SELECT 0";
+ default:
+ throw new ArgumentOutOfRangeException(currentProvider);
+ }
+ }
}
}
\ No newline at end of file
diff --git a/Orm/Xtensive.Orm.Tests/Storage/ConnectionAccessorTest.cs b/Orm/Xtensive.Orm.Tests/Storage/ConnectionAccessorTest.cs
new file mode 100644
index 0000000000..e00d8e947e
--- /dev/null
+++ b/Orm/Xtensive.Orm.Tests/Storage/ConnectionAccessorTest.cs
@@ -0,0 +1,281 @@
+// Copyright (C) 2021 Xtensive LLC.
+// This code is distributed under MIT license terms.
+// See the License.txt file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using NUnit.Framework;
+using Xtensive.Core;
+using Xtensive.Orm.Providers;
+using Xtensive.Sql;
+using Xtensive.Orm.Tests.Storage.ConnectionAccessorsModel;
+using System.Threading.Tasks;
+
+namespace Xtensive.Orm.Tests.Storage.ConnectionAccessorsModel
+{
+ public class MyConnectionAccessor : DbConnectionAccessor
+ {
+ private Guid instanceMarker;
+
+ public readonly Guid UniqueInstanceIdentifier;
+
+ public int ConnectionOpeningCounter;
+ public int ConnectionInitializationCounter;
+ public int ConnectionOpenedCounter;
+ public int ConnectionOpeningFailedCounter;
+
+ public override void ConnectionOpening(ConnectionEventData eventData)
+ {
+ instanceMarker = UniqueInstanceIdentifier;
+ ConnectionOpeningCounter++;
+ }
+
+ public override void ConnectionInitialization(ConnectionInitEventData eventData)
+ {
+ ConnectionInitializationCounter++;
+ if (instanceMarker != UniqueInstanceIdentifier) {
+ throw new Exception("Not the same instance");
+ }
+ }
+
+ public override void ConnectionOpened(ConnectionEventData eventData)
+ {
+ ConnectionOpenedCounter++;
+ if (instanceMarker != UniqueInstanceIdentifier) {
+ throw new Exception("Not the same instance");
+ }
+ }
+
+ public override void ConnectionOpeningFailed(ConnectionErrorEventData eventData)
+ {
+ ConnectionOpeningFailedCounter++;
+ if (instanceMarker != UniqueInstanceIdentifier) {
+ throw new Exception("Not the same instance");
+ }
+ }
+
+ public MyConnectionAccessor()
+ {
+ UniqueInstanceIdentifier = Guid.NewGuid();
+ }
+ }
+
+ public class NoDefaultConstructorAccessor : DbConnectionAccessor
+ {
+#pragma warning disable IDE0060 // Remove unused parameter
+ public NoDefaultConstructorAccessor(int dummyParameter)
+#pragma warning restore IDE0060 // Remove unused parameter
+ {
+ }
+ }
+
+ public class NonPublicDefaultConstructorAccessor : DbConnectionAccessor
+ {
+ private NonPublicDefaultConstructorAccessor()
+ {
+ }
+ }
+
+ #region Performance Test accessors
+
+ public class PerfCheckAccessor1 : DbConnectionAccessor { }
+ public class PerfCheckAccessor2 : DbConnectionAccessor { }
+ public class PerfCheckAccessor3 : DbConnectionAccessor { }
+ public class PerfCheckAccessor4 : DbConnectionAccessor { }
+ public class PerfCheckAccessor5 : DbConnectionAccessor { }
+ public class PerfCheckAccessor6 : DbConnectionAccessor { }
+ public class PerfCheckAccessor7 : DbConnectionAccessor { }
+ public class PerfCheckAccessor8 : DbConnectionAccessor { }
+ public class PerfCheckAccessor9 : DbConnectionAccessor { }
+ public class PerfCheckAccessor10 : DbConnectionAccessor { }
+ public class PerfCheckAccessor11 : DbConnectionAccessor { }
+ public class PerfCheckAccessor12 : DbConnectionAccessor { }
+ public class PerfCheckAccessor13 : DbConnectionAccessor { }
+ public class PerfCheckAccessor14 : DbConnectionAccessor { }
+ public class PerfCheckAccessor15 : DbConnectionAccessor { }
+ public class PerfCheckAccessor16 : DbConnectionAccessor { }
+ public class PerfCheckAccessor17 : DbConnectionAccessor { }
+ public class PerfCheckAccessor18 : DbConnectionAccessor { }
+ public class PerfCheckAccessor19 : DbConnectionAccessor { }
+ public class PerfCheckAccessor20 : DbConnectionAccessor { }
+ public class PerfCheckAccessor21 : DbConnectionAccessor { }
+ public class PerfCheckAccessor22 : DbConnectionAccessor { }
+ public class PerfCheckAccessor23 : DbConnectionAccessor { }
+ public class PerfCheckAccessor24 : DbConnectionAccessor { }
+ public class PerfCheckAccessor25 : DbConnectionAccessor { }
+
+ #endregion
+
+ public static class StaticCounter
+ {
+ public static int OpeningReached;
+ public static int OpenedReached;
+ }
+
+ public class DummyEntity : Entity
+ {
+ [Field, Key]
+ public int Id { get; private set; }
+
+ [Field]
+ public int Value { get; set; }
+
+ public DummyEntity(Session session)
+ : base(session)
+ {
+ }
+ }
+}
+
+namespace Xtensive.Orm.Tests.Storage
+{
+ [TestFixture]
+ public sealed class ConnectionAccessorTest
+ {
+ [Test]
+ public void DomainRegistryTest()
+ {
+ var domainConfig = DomainConfigurationFactory.Create();
+ domainConfig.Types.Register(typeof(DummyEntity));
+ domainConfig.Types.Register(typeof(MyConnectionAccessor));
+
+ Assert.That(domainConfig.Types.DbConnectionAccessors.Count(), Is.EqualTo(1));
+ }
+
+ [Test]
+ public void NoDefaultConstructorTest()
+ {
+ var domainConfig = DomainConfigurationFactory.Create();
+ domainConfig.UpgradeMode = DomainUpgradeMode.Recreate;
+ domainConfig.Types.Register(typeof(DummyEntity));
+ domainConfig.Types.Register(typeof(NoDefaultConstructorAccessor));
+
+ Domain domain = null;
+ _ = Assert.Throws(() => domain = Domain.Build(domainConfig));
+ domain.DisposeSafely();
+ }
+
+ [Test]
+ public void NonPublicDefaultConstructorTest()
+ {
+ var domainConfig = DomainConfigurationFactory.Create();
+ domainConfig.UpgradeMode = DomainUpgradeMode.Recreate;
+ domainConfig.Types.Register(typeof(DummyEntity));
+ domainConfig.Types.Register(typeof(NonPublicDefaultConstructorAccessor));
+
+ using var domain = Domain.Build(domainConfig);
+ }
+
+ [Test]
+ public void SessionConnectionAccessorsTest()
+ {
+ var domainConfig = DomainConfigurationFactory.Create();
+ domainConfig.UpgradeMode = DomainUpgradeMode.Recreate;
+ domainConfig.Types.Register(typeof(DummyEntity));
+ domainConfig.Types.Register(typeof(MyConnectionAccessor));
+
+ Guid? first = null;
+ using (var domain = Domain.Build(domainConfig))
+ using (var session = domain.OpenSession()) {
+ var nativeHandler = (SqlSessionHandler) session.Handler;
+ var extension = nativeHandler.Connection.Extensions.Get();
+ var accessorInstance = (MyConnectionAccessor) extension.Accessors.First();
+ Assert.That(accessorInstance.ConnectionOpeningCounter, Is.Not.EqualTo(0));
+ Assert.That(accessorInstance.ConnectionOpenedCounter, Is.Not.EqualTo(0));
+ first = accessorInstance.UniqueInstanceIdentifier;
+ }
+
+ Guid? second = null;
+ using (var domain = Domain.Build(domainConfig))
+ using (var session = domain.OpenSession()) {
+ var nativeHandler = (SqlSessionHandler) session.Handler;
+ var extension = nativeHandler.Connection.Extensions.Get();
+ var accessorInstance = (MyConnectionAccessor) extension.Accessors.First();
+ Assert.That(accessorInstance.ConnectionOpeningCounter, Is.Not.EqualTo(0));
+ Assert.That(accessorInstance.ConnectionOpenedCounter, Is.Not.EqualTo(0));
+ second = accessorInstance.UniqueInstanceIdentifier;
+ }
+
+ Assert.That(first != null && second != null && first != second, Is.True);
+ }
+
+ [Test]
+ [TestCase(0)]
+ [TestCase(1)]
+ [TestCase(2)]
+ public void ConnectionExtensionExistanceTest(int amountOfAccessors)
+ {
+ var domainConfig = DomainConfigurationFactory.Create();
+ domainConfig.UpgradeMode = DomainUpgradeMode.Recreate;
+
+ foreach (var accessor in GetAccessors(amountOfAccessors)) {
+ domainConfig.Types.Register(accessor);
+ }
+
+ using (var domain = Domain.Build(domainConfig))
+ using (var session = domain.OpenSession()) {
+ var nativeHandler = (SqlSessionHandler) session.Handler;
+ var extensions = nativeHandler.Connection.Extensions;
+ if (amountOfAccessors > 0) {
+ Assert.That(extensions.Count, Is.EqualTo(1));
+ var extension = extensions.Get();
+ Assert.That(extension, Is.Not.Null);
+ Assert.That(extension.Accessors.Count, Is.EqualTo(amountOfAccessors));
+ }
+ else {
+ Assert.That(extensions.Count, Is.EqualTo(0));
+ }
+ }
+ }
+
+ [Explicit]
+ [TestCase(0)]
+ [TestCase(5)]
+ [TestCase(10)]
+ [TestCase(15)]
+ [TestCase(20)]
+ [TestCase(25)]
+ public void SessionOpeningPerformanceTest(int amountOfAccessors)
+ {
+ var domainConfig = DomainConfigurationFactory.Create();
+ domainConfig.UpgradeMode = DomainUpgradeMode.Recreate;
+
+ foreach (var accessor in GetAccessors(amountOfAccessors)) {
+ domainConfig.Types.Register(accessor);
+ }
+
+ var watch = new Stopwatch();
+ using (var domain = Domain.Build(domainConfig)) {
+ watch.Start();
+ for (var i = 0; i < 1000000; i++) {
+ domain.OpenSession().Dispose();
+ }
+ watch.Stop();
+ }
+ Console.WriteLine(watch.ElapsedTicks / 1000000);
+ }
+
+ private IEnumerable GetAccessors(int neededCount)
+ {
+ if (neededCount > 25) {
+ throw new Exception();
+ }
+
+ var all = new Type[] {
+ typeof(PerfCheckAccessor1), typeof(PerfCheckAccessor2), typeof(PerfCheckAccessor3), typeof(PerfCheckAccessor4),
+ typeof(PerfCheckAccessor5), typeof(PerfCheckAccessor6), typeof(PerfCheckAccessor7), typeof(PerfCheckAccessor8),
+ typeof(PerfCheckAccessor9), typeof(PerfCheckAccessor10), typeof(PerfCheckAccessor11), typeof(PerfCheckAccessor12),
+ typeof(PerfCheckAccessor13), typeof(PerfCheckAccessor14), typeof(PerfCheckAccessor15), typeof(PerfCheckAccessor16),
+ typeof(PerfCheckAccessor17), typeof(PerfCheckAccessor18), typeof(PerfCheckAccessor19), typeof(PerfCheckAccessor20),
+ typeof(PerfCheckAccessor21), typeof(PerfCheckAccessor22), typeof(PerfCheckAccessor23), typeof(PerfCheckAccessor24),
+ typeof(PerfCheckAccessor25)
+ };
+ for (var i = 0; i < neededCount; i++) {
+ yield return all[i];
+ }
+ }
+ }
+}
diff --git a/Orm/Xtensive.Orm/Orm/Configuration/DomainTypeRegistry.cs b/Orm/Xtensive.Orm/Orm/Configuration/DomainTypeRegistry.cs
index 5594bb7548..8a4fe8934c 100644
--- a/Orm/Xtensive.Orm/Orm/Configuration/DomainTypeRegistry.cs
+++ b/Orm/Xtensive.Orm/Orm/Configuration/DomainTypeRegistry.cs
@@ -26,7 +26,10 @@ public class DomainTypeRegistry : TypeRegistry
private readonly static Type iModuleType = typeof (IModule);
private readonly static Type iUpgradeHandlerType = typeof (IUpgradeHandler);
private readonly static Type keyGeneratorType = typeof (KeyGenerator);
- private static readonly Type ifulltextCatalogNameBuilder = typeof (IFullTextCatalogNameBuilder);
+ private readonly static Type ifulltextCatalogNameBuilder = typeof(IFullTextCatalogNameBuilder);
+ private readonly static Type iDbConnectionAccessorType = typeof(IDbConnectionAccessor);
+
+ private Type[] connectionAccessors;
///
/// Gets all the registered persistent types.
@@ -100,6 +103,27 @@ public IEnumerable FullTextCatalogResolvers
get { return this.Where(IsFullTextCatalogNameBuilder); }
}
+ ///
+ /// Gets all the registered implementations.
+ ///
+ public IEnumerable DbConnectionAccessors
+ {
+ get {
+ // a lot of access to this property. better to have items cached;
+ if (IsLocked) {
+ if (connectionAccessors == null) {
+ var container = new List(10);// not so many accessors expected
+ foreach (var type in this.Where(IsDbConnectionAccessor))
+ container.Add(type);
+ connectionAccessors = container.Count == 0 ? Array.Empty() : container.ToArray();
+ }
+ return connectionAccessors;
+ }
+ // if instance is not locked then there is a chance of new accessors appeared
+ return this.Where(IsDbConnectionAccessor);
+ }
+ }
+
#region IsXxx method group
///
@@ -119,7 +143,8 @@ public static bool IsInterestingType(Type type)
IsUpgradeHandler(type) ||
IsKeyGenerator(type) ||
IsCompilerContainer(type) ||
- IsFullTextCatalogNameBuilder(type);
+ IsFullTextCatalogNameBuilder(type) ||
+ IsDbConnectionAccessor(type);
}
///
@@ -238,6 +263,21 @@ public static bool IsFullTextCatalogNameBuilder(Type type)
return false;
}
+ ///
+ /// Determines whether the is
+ /// a database connection accessor.
+ ///
+ /// The type to check.
+ /// Check result.
+ public static bool IsDbConnectionAccessor(Type type)
+ {
+ if (type.IsAbstract) {
+ return false;
+ }
+
+ return iDbConnectionAccessorType.IsAssignableFrom(type) && iDbConnectionAccessorType != type;
+ }
+
#endregion
#region ICloneable members
diff --git a/Orm/Xtensive.Orm/Orm/ConnectionErrorEventData.cs b/Orm/Xtensive.Orm/Orm/ConnectionErrorEventData.cs
new file mode 100644
index 0000000000..d5670d7ed1
--- /dev/null
+++ b/Orm/Xtensive.Orm/Orm/ConnectionErrorEventData.cs
@@ -0,0 +1,28 @@
+// Copyright (C) 2021 Xtensive LLC.
+// This code is distributed under MIT license terms.
+// See the License.txt file in the project root for more information.
+
+using System;
+using System.Data.Common;
+using Xtensive.Core;
+
+namespace Xtensive.Orm
+{
+ ///
+ /// Extended with error happend during connection opening, restoration or initialization.
+ ///
+ public class ConnectionErrorEventData : ConnectionEventData
+ {
+ ///
+ /// The exception appeared.
+ ///
+ public Exception Exception { get; }
+
+ public ConnectionErrorEventData(Exception exception, DbConnection connection, bool reconnect = false)
+ : base(connection, reconnect)
+ {
+ ArgumentValidator.EnsureArgumentNotNull(exception, nameof(exception));
+ Exception = exception;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Orm/Xtensive.Orm/Orm/ConnectionEventData.cs b/Orm/Xtensive.Orm/Orm/ConnectionEventData.cs
new file mode 100644
index 0000000000..6ab3d75ff5
--- /dev/null
+++ b/Orm/Xtensive.Orm/Orm/ConnectionEventData.cs
@@ -0,0 +1,32 @@
+// Copyright (C) 2021 Xtensive LLC.
+// This code is distributed under MIT license terms.
+// See the License.txt file in the project root for more information.
+
+using System.Data.Common;
+using Xtensive.Core;
+
+namespace Xtensive.Orm
+{
+ ///
+ /// Contains general data for methods.
+ ///
+ public class ConnectionEventData
+ {
+ ///
+ /// The connection for which event triggered.
+ ///
+ public DbConnection Connection { get; }
+
+ ///
+ /// Indicates whether event happened during an attempt to restore connection.
+ ///
+ public bool Reconnect { get; }
+
+ public ConnectionEventData(DbConnection connection, bool reconnect = false)
+ {
+ ArgumentValidator.EnsureArgumentNotNull(connection, nameof(connection));
+ Connection = connection;
+ Reconnect = reconnect;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Orm/Xtensive.Orm/Orm/ConnectionInitEventData.cs b/Orm/Xtensive.Orm/Orm/ConnectionInitEventData.cs
new file mode 100644
index 0000000000..d14f825c11
--- /dev/null
+++ b/Orm/Xtensive.Orm/Orm/ConnectionInitEventData.cs
@@ -0,0 +1,27 @@
+// Copyright (C) 2021 Xtensive LLC.
+// This code is distributed under MIT license terms.
+// See the License.txt file in the project root for more information.
+
+using System.Data.Common;
+using Xtensive.Core;
+
+namespace Xtensive.Orm
+{
+ ///
+ /// Extended with connection initialization script
+ ///
+ public class ConnectionInitEventData : ConnectionEventData
+ {
+ ///
+ /// Gets the script which will be used for connection initializatin
+ ///
+ public string InitializationScript { get; }
+
+ public ConnectionInitEventData(string initializationScript, DbConnection connection, bool reconnect = false)
+ : base(connection, reconnect)
+ {
+ ArgumentValidator.EnsureArgumentNotNullOrEmpty(initializationScript, nameof(initializationScript));
+ InitializationScript = initializationScript;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Orm/Xtensive.Orm/Orm/Interfaces/DbConnectionAccessor.cs b/Orm/Xtensive.Orm/Orm/Interfaces/DbConnectionAccessor.cs
new file mode 100644
index 0000000000..c5806ca096
--- /dev/null
+++ b/Orm/Xtensive.Orm/Orm/Interfaces/DbConnectionAccessor.cs
@@ -0,0 +1,35 @@
+// Copyright (C) 2021 Xtensive LLC.
+// This code is distributed under MIT license terms.
+// See the License.txt file in the project root for more information.
+
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Xtensive.Orm
+{
+ ///
+ /// Base type for native database connection accessors to be inherited from.
+ ///
+ public abstract class DbConnectionAccessor : IDbConnectionAccessor
+ {
+ ///
+ public virtual void ConnectionOpening(ConnectionEventData eventData)
+ {
+ }
+
+ ///
+ public virtual void ConnectionInitialization(ConnectionInitEventData eventData)
+ {
+ }
+
+ ///
+ public virtual void ConnectionOpened(ConnectionEventData eventData)
+ {
+ }
+
+ ///
+ public virtual void ConnectionOpeningFailed(ConnectionErrorEventData eventData)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/Orm/Xtensive.Orm/Orm/Interfaces/IDbConnectionAccessor.cs b/Orm/Xtensive.Orm/Orm/Interfaces/IDbConnectionAccessor.cs
new file mode 100644
index 0000000000..fe82974d60
--- /dev/null
+++ b/Orm/Xtensive.Orm/Orm/Interfaces/IDbConnectionAccessor.cs
@@ -0,0 +1,40 @@
+ // Copyright (C) 2021 Xtensive LLC.
+ // This code is distributed under MIT license terms.
+ // See the License.txt file in the project root for more information.
+
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Xtensive.Orm
+{
+ ///
+ /// Offers event-like methods to access native database connection on different stages.
+ ///
+ public interface IDbConnectionAccessor
+ {
+ ///
+ /// Executes before connection opening.
+ ///
+ /// Information connected with this event.
+ void ConnectionOpening(ConnectionEventData eventData);
+
+ ///
+ /// Executes when connection is already opened but initialization script
+ /// hasn't been executed yet.
+ ///
+ /// Information connected with this event.
+ void ConnectionInitialization(ConnectionInitEventData eventData);
+
+ ///
+ /// Executes when connection is successfully opened and initialized.
+ ///
+ /// Information connected with this event.
+ void ConnectionOpened(ConnectionEventData eventData);
+
+ ///
+ /// Executes if an error appeared on either connection opening or connection initialization.
+ ///
+ /// Information connected with this event.
+ void ConnectionOpeningFailed(ConnectionErrorEventData eventData);
+ }
+}
\ No newline at end of file
diff --git a/Orm/Xtensive.Orm/Orm/Providers/StorageDriver.Operations.cs b/Orm/Xtensive.Orm/Orm/Providers/StorageDriver.Operations.cs
index b63607ee50..cbef6e3bd8 100644
--- a/Orm/Xtensive.Orm/Orm/Providers/StorageDriver.Operations.cs
+++ b/Orm/Xtensive.Orm/Orm/Providers/StorageDriver.Operations.cs
@@ -1,4 +1,4 @@
-// Copyright (C) 2009-2020 Xtensive LLC.
+// Copyright (C) 2009-2021 Xtensive LLC.
// This code is distributed under MIT license terms.
// See the License.txt file in the project root for more information.
// Created by: Denis Krjuchkov
@@ -14,7 +14,7 @@
namespace Xtensive.Orm.Providers
{
- partial class StorageDriver
+ public partial class StorageDriver
{
private sealed class InitializationSqlExtension
{
@@ -50,6 +50,11 @@ public SqlConnection CreateConnection(Session session)
throw ExceptionBuilder.BuildException(exception);
}
+ if (connectionAccessorFactories != null) {
+ connection.AssignConnectionAccessors(
+ CreateConnectionAccessorsFast(configuration.Types.DbConnectionAccessors));
+ }
+
var sessionConfiguration = GetConfiguration(session);
connection.CommandTimeout = sessionConfiguration.DefaultCommandTimeout;
var connectionInfo = GetConnectionInfo(session) ?? sessionConfiguration.ConnectionInfo;
@@ -69,14 +74,14 @@ public void OpenConnection(Session session, SqlConnection connection)
if (isLoggingEnabled)
SqlLog.Info(Strings.LogSessionXOpeningConnectionY, session.ToStringSafely(), connection.ConnectionInfo);
- var extension = connection.Extensions.Get();
+ var script = connection.Extensions.Get()?.Script;
try {
- if (extension == null || string.IsNullOrEmpty(extension.Script)) {
- connection.Open();
+ if (!string.IsNullOrEmpty(script)) {
+ connection.OpenAndInitialize(script);
}
else {
- connection.OpenAndInitialize(extension.Script);
+ connection.Open();
}
}
catch (Exception exception) {
@@ -94,13 +99,15 @@ public async Task OpenConnectionAsync(Session session, SqlConnection connection,
if (isLoggingEnabled)
SqlLog.Info(Strings.LogSessionXOpeningConnectionY, session.ToStringSafely(), connection.ConnectionInfo);
- var extension = connection.Extensions.Get();
+ var script = connection.Extensions.Get()?.Script;
try {
- if (!string.IsNullOrEmpty(extension?.Script))
- await connection.OpenAndInitializeAsync(extension.Script, cancellationToken).ConfigureAwait(false);
- else
+ if (!string.IsNullOrEmpty(script)) {
+ await connection.OpenAndInitializeAsync(script, cancellationToken).ConfigureAwait(false);
+ }
+ else {
await connection.OpenAsync(cancellationToken).ConfigureAwait(false);
+ }
}
catch (OperationCanceledException) {
throw;
diff --git a/Orm/Xtensive.Orm/Orm/Providers/StorageDriver.cs b/Orm/Xtensive.Orm/Orm/Providers/StorageDriver.cs
index 3fd4b3188c..9ecd310414 100644
--- a/Orm/Xtensive.Orm/Orm/Providers/StorageDriver.cs
+++ b/Orm/Xtensive.Orm/Orm/Providers/StorageDriver.cs
@@ -1,4 +1,4 @@
-// Copyright (C) 2003-2010 Xtensive LLC.
+// Copyright (C) 2009-2021 Xtensive LLC.
// All rights reserved.
// For conditions of distribution and use, see license.
// Created by: Denis Krjuchkov
@@ -7,7 +7,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
using Xtensive.Core;
+using Xtensive.Linq;
using Xtensive.Orm.Logging;
using Xtensive.Orm.Configuration;
using Xtensive.Orm.Model;
@@ -24,6 +27,9 @@ namespace Xtensive.Orm.Providers
///
public sealed partial class StorageDriver
{
+ private static readonly MethodInfo FactoryCreatorMethod = typeof(StorageDriver)
+ .GetMethod(nameof(CreateNewAccessor), BindingFlags.Static | BindingFlags.NonPublic);
+
private readonly DomainConfiguration configuration;
private readonly SqlDriver underlyingDriver;
private readonly SqlTranslator translator;
@@ -31,6 +37,8 @@ public sealed partial class StorageDriver
private readonly bool isLoggingEnabled;
private readonly bool hasSavepoints;
+ private readonly IReadOnlyDictionary> connectionAccessorFactories;
+
public ProviderInfo ProviderInfo { get; private set; }
public StorageExceptionBuilder ExceptionBuilder { get; private set; }
@@ -86,7 +94,7 @@ public DbDataReaderAccessor GetDataReaderAccessor(TupleDescriptor descriptor)
public StorageDriver CreateNew(Domain domain)
{
ArgumentValidator.EnsureArgumentNotNull(domain, "domain");
- return new StorageDriver(underlyingDriver, ProviderInfo, domain.Configuration, GetModelProvider(domain));
+ return new StorageDriver(underlyingDriver, ProviderInfo, domain.Configuration, GetModelProvider(domain), connectionAccessorFactories);
}
private static DomainModel GetNullModel()
@@ -140,6 +148,61 @@ private void FixExtractionResultSqlServerFamily(SqlExtractionResult result)
}
}
+ private IReadOnlyCollection CreateConnectionAccessorsFast(IEnumerable connectionAccessorTypes)
+ {
+ if (connectionAccessorFactories == null)
+ return Array.Empty();
+ var instances = new List(connectionAccessorFactories.Count);
+ foreach (var type in connectionAccessorTypes) {
+ if (connectionAccessorFactories.TryGetValue(type, out var factory)) {
+ instances.Add(factory());
+ }
+ }
+ return instances.ToArray();
+ }
+
+ private static IReadOnlyCollection CreateConnectionAccessors(IEnumerable connectionAccessorTypes,
+ out IReadOnlyDictionary> factories)
+ {
+ factories = null;
+
+ List instances;
+ Dictionary> factoriesLocal;
+
+ if (connectionAccessorTypes is IReadOnlyCollection asCollection) {
+ if (asCollection.Count == 0)
+ return Array.Empty();
+ instances = new List(asCollection.Count);
+ factoriesLocal = new Dictionary>(asCollection.Count);
+ }
+ else {
+ if (connectionAccessorTypes.Any())
+ return Array.Empty();
+ instances = new List();
+ factoriesLocal = new Dictionary>();
+ }
+
+ foreach (var type in connectionAccessorTypes) {
+ var ctor = type.GetConstructor(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, Type.EmptyTypes, null);
+ if (ctor == null) {
+ throw new NotSupportedException(string.Format(Strings.ExConnectionAccessorXHasNoParameterlessConstructor, type));
+ }
+
+ var accessorFactory = (Func) FactoryCreatorMethod.MakeGenericMethod(type).Invoke(null, null);
+ instances.Add(accessorFactory());
+ factoriesLocal[type] = accessorFactory;
+ }
+ factories = factoriesLocal;
+ return instances.ToArray();
+ }
+
+ private static Func CreateNewAccessor() where T : IDbConnectionAccessor
+ {
+ return FastExpression.Lambda>(
+ Expression.Convert(Expression.New(typeof(T)), typeof(IDbConnectionAccessor)))
+ .Compile();
+ }
+
// Constructors
public static StorageDriver Create(SqlDriverFactory driverFactory, DomainConfiguration configuration)
@@ -147,7 +210,8 @@ public static StorageDriver Create(SqlDriverFactory driverFactory, DomainConfigu
ArgumentValidator.EnsureArgumentNotNull(driverFactory, "driverFactory");
ArgumentValidator.EnsureArgumentNotNull(configuration, "configuration");
- var driverConfiguration = new SqlDriverConfiguration {
+ var accessors = CreateConnectionAccessors(configuration.Types.DbConnectionAccessors, out var factories);
+ var driverConfiguration = new SqlDriverConfiguration(accessors) {
ForcedServerVersion = configuration.ForcedServerVersion,
ConnectionInitializationSql = configuration.ConnectionInitializationSql,
EnsureConnectionIsAlive = configuration.EnsureConnectionIsAlive,
@@ -156,11 +220,14 @@ public static StorageDriver Create(SqlDriverFactory driverFactory, DomainConfigu
var driver = driverFactory.GetDriver(configuration.ConnectionInfo, driverConfiguration);
var providerInfo = ProviderInfoBuilder.Build(configuration.ConnectionInfo.Provider, driver);
- return new StorageDriver(driver, providerInfo, configuration, GetNullModel);
+ return new StorageDriver(driver, providerInfo, configuration, GetNullModel, factories);
}
- private StorageDriver(
- SqlDriver driver, ProviderInfo providerInfo, DomainConfiguration configuration, Func modelProvider)
+ private StorageDriver(SqlDriver driver,
+ ProviderInfo providerInfo,
+ DomainConfiguration configuration,
+ Func modelProvider,
+ IReadOnlyDictionary> factoryCache)
{
underlyingDriver = driver;
ProviderInfo = providerInfo;
@@ -171,6 +238,7 @@ private StorageDriver(
hasSavepoints = underlyingDriver.ServerInfo.ServerFeatures.Supports(ServerFeatures.Savepoints);
isLoggingEnabled = SqlLog.IsLogged(LogLevel.Info); // Just to cache this value
ServerInfo = underlyingDriver.ServerInfo;
+ connectionAccessorFactories = factoryCache;
}
}
}
\ No newline at end of file
diff --git a/Orm/Xtensive.Orm/Sql/DbConnectionAccessorExtension.cs b/Orm/Xtensive.Orm/Sql/DbConnectionAccessorExtension.cs
new file mode 100644
index 0000000000..bf881f246d
--- /dev/null
+++ b/Orm/Xtensive.Orm/Sql/DbConnectionAccessorExtension.cs
@@ -0,0 +1,25 @@
+// Copyright (C) 2021 Xtensive LLC.
+// This code is distributed under MIT license terms.
+// See the License.txt file in the project root for more information.
+
+using System.Collections.Generic;
+using Xtensive.Orm;
+
+namespace Xtensive.Sql
+{
+ ///
+ /// Wrapper to pass s to connection.
+ ///
+ public sealed class DbConnectionAccessorExtension
+ {
+ ///
+ /// Collection of instances.
+ ///
+ public IReadOnlyCollection Accessors { get; }
+
+ internal DbConnectionAccessorExtension(IReadOnlyCollection connectionAccessors)
+ {
+ Accessors = connectionAccessors;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Orm/Xtensive.Orm/Sql/SqlConnection.cs b/Orm/Xtensive.Orm/Sql/SqlConnection.cs
index 074e12e4b4..286bb83f7c 100644
--- a/Orm/Xtensive.Orm/Sql/SqlConnection.cs
+++ b/Orm/Xtensive.Orm/Sql/SqlConnection.cs
@@ -1,4 +1,4 @@
-// Copyright (C) 2003-2010 Xtensive LLC.
+// Copyright (C) 2009-2021 Xtensive LLC.
// This code is distributed under MIT license terms.
// See the License.txt file in the project root for more information.
@@ -166,7 +166,22 @@ public virtual IBinaryLargeObject CreateBinaryLargeObject() =>
public virtual void Open()
{
EnsureIsNotDisposed();
- UnderlyingConnection.Open();
+ var connectionAccessorEx = Extensions.Get();
+ if (connectionAccessorEx == null) {
+ UnderlyingConnection.Open();
+ }
+ else {
+ var accessors = connectionAccessorEx.Accessors;
+ SqlHelper.NotifyConnectionOpening(accessors, UnderlyingConnection);
+ try {
+ UnderlyingConnection.Open();
+ SqlHelper.NotifyConnectionOpened(accessors, UnderlyingConnection);
+ }
+ catch (Exception ex) {
+ SqlHelper.NotifyConnectionOpeningFailed(accessors, UnderlyingConnection, ex);
+ throw;
+ }
+ }
}
///
@@ -175,15 +190,37 @@ public virtual void Open()
/// Initialization script.
public virtual void OpenAndInitialize(string initializationScript)
{
- UnderlyingConnection.Open();
- if (string.IsNullOrEmpty(initializationScript)) {
- return;
- }
+ var connectionAccessorEx = Extensions.Get();
+ if (connectionAccessorEx == null) {
+ UnderlyingConnection.Open();
+ if (string.IsNullOrEmpty(initializationScript)) {
+ return;
+ }
- using (var command = UnderlyingConnection.CreateCommand()) {
+ using var command = UnderlyingConnection.CreateCommand();
command.CommandText = initializationScript;
_ = command.ExecuteNonQuery();
}
+ else {
+ var accessors = connectionAccessorEx.Accessors;
+ SqlHelper.NotifyConnectionOpening(accessors, UnderlyingConnection);
+ try {
+ UnderlyingConnection.Open();
+ if (string.IsNullOrEmpty(initializationScript)) {
+ SqlHelper.NotifyConnectionOpened(accessors, UnderlyingConnection);
+ return;
+ }
+
+ SqlHelper.NotifyConnectionInitializing(accessors, UnderlyingConnection, initializationScript);
+ using var command = UnderlyingConnection.CreateCommand();
+ command.CommandText = initializationScript;
+ _ = command.ExecuteNonQuery();
+ }
+ catch (Exception ex) {
+ SqlHelper.NotifyConnectionOpeningFailed(accessors, UnderlyingConnection, ex);
+ throw;
+ }
+ }
}
///
diff --git a/Orm/Xtensive.Orm/Sql/SqlDriverConfiguration.cs b/Orm/Xtensive.Orm/Sql/SqlDriverConfiguration.cs
index 9dcebec5af..7a7f0b5a9b 100644
--- a/Orm/Xtensive.Orm/Sql/SqlDriverConfiguration.cs
+++ b/Orm/Xtensive.Orm/Sql/SqlDriverConfiguration.cs
@@ -1,9 +1,14 @@
-// Copyright (C) 2003-2012 Xtensive LLC.
-// All rights reserved.
-// For conditions of distribution and use, see license.
+// Copyright (C) 2012-2021 Xtensive LLC.
+// This code is distributed under MIT license terms.
+// See the License.txt file in the project root for more information.
// Created by: Denis Krjuchkov
// Created: 2012.12.27
+using System;
+using System.Collections.Generic;
+using Xtensive.Core;
+using Xtensive.Orm;
+
namespace Xtensive.Sql
{
///
@@ -26,13 +31,23 @@ public sealed class SqlDriverConfiguration
///
public bool EnsureConnectionIsAlive { get; set; }
+ ///
+ /// Gets connection accessors that should be notified about connection events.
+ ///
+ public IReadOnlyCollection DbConnectionAccessors { get; private set; }
+
///
/// Clones this instance.
///
/// Clone of this instance.
public SqlDriverConfiguration Clone()
{
- return new SqlDriverConfiguration {
+ // no deep cloning
+ var accessors = (DbConnectionAccessors.Count == 0)
+ ? Array.Empty()
+ : DbConnectionAccessors.ToArray(DbConnectionAccessors.Count);
+
+ return new SqlDriverConfiguration(accessors) {
ForcedServerVersion = ForcedServerVersion,
ConnectionInitializationSql = ConnectionInitializationSql,
EnsureConnectionIsAlive = EnsureConnectionIsAlive
@@ -44,6 +59,15 @@ public SqlDriverConfiguration Clone()
///
public SqlDriverConfiguration()
{
+ DbConnectionAccessors = Array.Empty();
+ }
+
+ ///
+ /// Creates new instance of this type.
+ ///
+ public SqlDriverConfiguration(IReadOnlyCollection connectionAccessors)
+ {
+ DbConnectionAccessors = connectionAccessors;
}
}
}
\ No newline at end of file
diff --git a/Orm/Xtensive.Orm/Sql/SqlExtensions.cs b/Orm/Xtensive.Orm/Sql/SqlExtensions.cs
index 5d656e584f..42092ff731 100644
--- a/Orm/Xtensive.Orm/Sql/SqlExtensions.cs
+++ b/Orm/Xtensive.Orm/Sql/SqlExtensions.cs
@@ -1,10 +1,11 @@
-// Copyright (C) 2003-2010 Xtensive LLC.
-// All rights reserved.
-// For conditions of distribution and use, see license.
+// Copyright (C) 2009-2021 Xtensive LLC.
+// This code is distributed under MIT license terms.
+// See the License.txt file in the project root for more information.
// Created by: Denis Krjuchkov
// Created: 2009.07.30
using System;
+using System.Collections.Generic;
using Xtensive.Core;
using Xtensive.Orm;
using Xtensive.Sql.Dml;
@@ -49,5 +50,17 @@ public static string GetSchema(this UrlInfo url, string defaultValue)
var result = resource.Substring(position + 1).TryCutSuffix(SchemaSeparatorString);
return string.IsNullOrEmpty(result) ? defaultValue : result;
}
+
+ ///
+ /// Assigns connection accessors to so they will have access.
+ /// to database connection on certain operations.
+ ///
+ /// The connection to assign accessors.
+ /// The accessors.
+ public static void AssignConnectionAccessors(this SqlConnection connection,
+ IReadOnlyCollection connectionAccessors)
+ {
+ connection.Extensions.Set(new DbConnectionAccessorExtension(connectionAccessors));
+ }
}
}
\ No newline at end of file
diff --git a/Orm/Xtensive.Orm/Sql/SqlHelper.cs b/Orm/Xtensive.Orm/Sql/SqlHelper.cs
index dcc10e826b..ae5bd74056 100644
--- a/Orm/Xtensive.Orm/Sql/SqlHelper.cs
+++ b/Orm/Xtensive.Orm/Sql/SqlHelper.cs
@@ -1,4 +1,4 @@
-// Copyright (C) 2003-2010 Xtensive LLC.
+// Copyright (C) 2009-2021 Xtensive LLC.
// All rights reserved.
// For conditions of distribution and use, see license.
// Created by: Denis Krjuchkov
@@ -371,6 +371,21 @@ public static void ExecuteInitializationSql(DbConnection connection, SqlDriverCo
}
}
+ ///
+ /// Executes (if any).
+ ///
+ /// Connection to initialize.
+ /// Sql expression.
+ public static void ExecuteInitializationSql(DbConnection connection, string initializationSql)
+ {
+ if (string.IsNullOrEmpty(initializationSql)) {
+ return;
+ }
+ using var command = connection.CreateCommand();
+ command.CommandText = initializationSql;
+ _ = command.ExecuteNonQuery();
+ }
+
///
/// Reduces the isolation level to the most commonly supported ones.
///
@@ -433,5 +448,75 @@ public static NotSupportedException NotSupported(ServerFeatures feature)
{
return NotSupported(feature.ToString());
}
+
+ #region Notifications
+
+ ///
+ /// Notifies all the that
+ /// is about to be opened.
+ ///
+ /// The accessors that should be notified.
+ /// The connection that is opening.
+ /// if event happened on attemp to restore connection, otherwise .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void NotifyConnectionOpening(
+ IEnumerable connectionAccessors, DbConnection connection, bool reconnect = false)
+ {
+ foreach (var accessor in connectionAccessors) {
+ accessor.ConnectionOpening(new ConnectionEventData(connection, reconnect));
+ }
+ }
+
+ ///
+ /// Notifies all the that
+ /// opened connection is about to be initialized with .
+ ///
+ /// The accessors that should be notified.
+ /// Opened but not initialized connection
+ /// The script that will run to initialize connection
+ /// if event happened on attemp to restore connection, otherwise .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void NotifyConnectionInitializing(
+ IEnumerable connectionAccessors, DbConnection connection, string initializationScript, bool reconnect = false)
+ {
+ foreach (var accessor in connectionAccessors) {
+ accessor.ConnectionInitialization(new ConnectionInitEventData(initializationScript, connection, reconnect));
+ }
+ }
+
+ ///
+ /// Notifies all the about
+ /// successful connection opening.
+ ///
+ /// The accessors that should be notified.
+ /// The connection that is completely opened and initialized.
+ /// if event happened on attemp to restore connection, otherwise .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void NotifyConnectionOpened(
+ IEnumerable connectionAccessor, DbConnection connection, bool reconnect = false)
+ {
+ foreach (var accessor in connectionAccessor) {
+ accessor.ConnectionOpened(new ConnectionEventData(connection, reconnect));
+ }
+ }
+
+ ///
+ /// Notifies all the about
+ /// connection opening failure.
+ ///
+ /// The accessors that should be notified.
+ /// Connection that failed to be opened or properly initialized.
+ /// The exception which appeared.
+ /// if event happened on attemp to restore connection, otherwise .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void NotifyConnectionOpeningFailed(
+ IEnumerable connnectionAccessors, DbConnection connection, Exception exception, bool reconnect = false)
+ {
+ foreach (var accessor in connnectionAccessors) {
+ accessor.ConnectionOpeningFailed(new ConnectionErrorEventData(exception, connection, reconnect));
+ }
+ }
+
+ #endregion
}
}
diff --git a/Orm/Xtensive.Orm/Strings.Designer.cs b/Orm/Xtensive.Orm/Strings.Designer.cs
index 6a87df93bf..1904947164 100644
--- a/Orm/Xtensive.Orm/Strings.Designer.cs
+++ b/Orm/Xtensive.Orm/Strings.Designer.cs
@@ -1524,6 +1524,15 @@ internal static string ExConfigurationWithXNameAlreadyRegistered {
}
}
+ ///
+ /// Looks up a localized string similar to Connection accessor '{0}' has no parameterless constructor..
+ ///
+ internal static string ExConnectionAccessorXHasNoParameterlessConstructor {
+ get {
+ return ResourceManager.GetString("ExConnectionAccessorXHasNoParameterlessConstructor", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to ConnectionInfo is missing. If you are using configuration file you should specify either 'connectionUrl' element or 'connectionString' and 'provider' elements.
///
diff --git a/Orm/Xtensive.Orm/Strings.resx b/Orm/Xtensive.Orm/Strings.resx
index 226c2e0c07..bf6ede1988 100644
--- a/Orm/Xtensive.Orm/Strings.resx
+++ b/Orm/Xtensive.Orm/Strings.resx
@@ -3467,4 +3467,7 @@ Error: {1}
Can't modify Active or Disposed scope.
+
+ Connection accessor '{0}' has no parameterless constructor.
+
\ No newline at end of file