2017-07-20 11:21:28 +02:00
using System ;
2016-12-14 14:06:30 +01:00
using System.Collections.Generic ;
using System.Configuration ;
using System.Data.Common ;
using System.Linq ;
using System.Threading ;
using NPoco ;
using NPoco.FluentMappings ;
using Umbraco.Core.Exceptions ;
using Umbraco.Core.Logging ;
2017-12-18 18:26:32 +01:00
using Umbraco.Core.Migrations.Install ;
2016-12-14 14:06:30 +01:00
using Umbraco.Core.Persistence.FaultHandling ;
using Umbraco.Core.Persistence.Mappers ;
using Umbraco.Core.Persistence.SqlSyntax ;
namespace Umbraco.Core.Persistence
{
/// <summary>
2016-12-16 14:18:37 +01:00
/// Default implementation of <see cref="IUmbracoDatabaseFactory"/>.
2016-12-14 14:06:30 +01:00
/// </summary>
/// <remarks>
/// <para>This factory implementation creates and manages an "ambient" database connection. When running
/// within an Http context, "ambient" means "associated with that context". Otherwise, it means "static to
/// the current thread". In this latter case, note that the database connection object is not thread safe.</para>
2016-12-16 14:18:37 +01:00
/// <para>It wraps an NPoco UmbracoDatabaseFactory which is initializes with a proper IPocoDataFactory to ensure
2016-12-14 14:06:30 +01:00
/// that NPoco's plumbing is cached appropriately for the whole application.</para>
/// </remarks>
2016-12-16 14:18:37 +01:00
internal class UmbracoDatabaseFactory : DisposableObject , IUmbracoDatabaseFactory
2016-12-14 14:06:30 +01:00
{
private readonly ISqlSyntaxProvider [ ] _sqlSyntaxProviders ;
private readonly IMapperCollection _mappers ;
private readonly ILogger _logger ;
2017-09-27 10:25:47 +02:00
private readonly SqlContext _sqlContext = new SqlContext ( ) ;
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim ( ) ;
2016-12-14 14:06:30 +01:00
private DatabaseFactory _npocoDatabaseFactory ;
private IPocoDataFactory _pocoDataFactory ;
private string _connectionString ;
private string _providerName ;
private DbProviderFactory _dbProviderFactory ;
private DatabaseType _databaseType ;
private ISqlSyntaxProvider _sqlSyntax ;
private RetryPolicy _connectionRetryPolicy ;
private RetryPolicy _commandRetryPolicy ;
2018-03-30 10:16:21 +02:00
private NPoco . MapperCollection _pocoMappers ;
private bool _upgrading ;
2016-12-14 14:06:30 +01:00
2018-03-30 10:16:21 +02:00
#region Constructors
2016-12-14 14:06:30 +01:00
/// <summary>
/// Initializes a new instance of the <see cref="UmbracoDatabaseFactory"/>.
/// </summary>
/// <remarks>Used by LightInject.</remarks>
2017-05-12 14:49:44 +02:00
public UmbracoDatabaseFactory ( IEnumerable < ISqlSyntaxProvider > sqlSyntaxProviders , ILogger logger , IMapperCollection mappers )
: this ( Constants . System . UmbracoConnectionName , sqlSyntaxProviders , logger , mappers )
2016-12-14 14:06:30 +01:00
{
if ( Configured = = false )
DatabaseBuilder . GiveLegacyAChance ( this , logger ) ;
}
/// <summary>
/// Initializes a new instance of the <see cref="UmbracoDatabaseFactory"/>.
/// </summary>
/// <remarks>Used by the other ctor and in tests.</remarks>
2017-05-12 14:49:44 +02:00
public UmbracoDatabaseFactory ( string connectionStringName , IEnumerable < ISqlSyntaxProvider > sqlSyntaxProviders , ILogger logger , IMapperCollection mappers )
2016-12-14 14:06:30 +01:00
{
if ( string . IsNullOrWhiteSpace ( connectionStringName ) ) throw new ArgumentNullOrEmptyException ( nameof ( connectionStringName ) ) ;
2017-05-12 14:49:44 +02:00
_mappers = mappers ? ? throw new ArgumentNullException ( nameof ( mappers ) ) ;
_sqlSyntaxProviders = sqlSyntaxProviders ? . ToArray ( ) ? ? throw new ArgumentNullException ( nameof ( sqlSyntaxProviders ) ) ;
_logger = logger ? ? throw new ArgumentNullException ( nameof ( logger ) ) ;
2016-12-14 14:06:30 +01:00
var settings = ConfigurationManager . ConnectionStrings [ connectionStringName ] ;
if ( settings = = null )
return ; // not configured
// could as well be <add name="umbracoDbDSN" connectionString="" providerName="" />
// so need to test the values too
var connectionString = settings . ConnectionString ;
var providerName = settings . ProviderName ;
if ( string . IsNullOrWhiteSpace ( connectionString ) | | string . IsNullOrWhiteSpace ( providerName ) )
{
logger . Debug < UmbracoDatabaseFactory > ( "Missing connection string or provider name, defer configuration." ) ;
return ; // not configured
}
Configure ( settings . ConnectionString , settings . ProviderName ) ;
}
/// <summary>
/// Initializes a new instance of the <see cref="UmbracoDatabaseFactory"/>.
/// </summary>
/// <remarks>Used in tests.</remarks>
2017-05-12 14:49:44 +02:00
public UmbracoDatabaseFactory ( string connectionString , string providerName , IEnumerable < ISqlSyntaxProvider > sqlSyntaxProviders , ILogger logger , IMapperCollection mappers )
2016-12-14 14:06:30 +01:00
{
2017-05-12 14:49:44 +02:00
_mappers = mappers ? ? throw new ArgumentNullException ( nameof ( mappers ) ) ;
_sqlSyntaxProviders = sqlSyntaxProviders ? . ToArray ( ) ? ? throw new ArgumentNullException ( nameof ( sqlSyntaxProviders ) ) ;
_logger = logger ? ? throw new ArgumentNullException ( nameof ( logger ) ) ;
2016-12-14 14:06:30 +01:00
if ( string . IsNullOrWhiteSpace ( connectionString ) | | string . IsNullOrWhiteSpace ( providerName ) )
{
logger . Debug < UmbracoDatabaseFactory > ( "Missing connection string or provider name, defer configuration." ) ;
return ; // not configured
}
Configure ( connectionString , providerName ) ;
}
#endregion
2016-12-16 14:18:37 +01:00
/// <inheritdoc />
2016-12-14 14:06:30 +01:00
public bool Configured { get ; private set ; }
2016-12-16 14:18:37 +01:00
/// <inheritdoc />
2016-12-14 14:06:30 +01:00
public bool CanConnect = > Configured & & DbConnectionExtensions . IsConnectionAvailable ( _connectionString , _providerName ) ;
2016-12-16 14:18:37 +01:00
/// <inheritdoc />
2017-09-27 10:25:47 +02:00
public ISqlContext SqlContext = > _sqlContext ;
2016-12-14 14:06:30 +01:00
2018-03-30 10:16:21 +02:00
/// <inheritdoc />
public void ConfigureForUpgrade ( )
{
_upgrading = true ;
}
2016-12-16 14:18:37 +01:00
/// <inheritdoc />
2016-12-14 14:06:30 +01:00
public void Configure ( string connectionString , string providerName )
{
using ( new WriteLock ( _lock ) )
{
_logger . Debug < UmbracoDatabaseFactory > ( "Configuring." ) ;
if ( Configured ) throw new InvalidOperationException ( "Already configured." ) ;
if ( connectionString . IsNullOrWhiteSpace ( ) ) throw new ArgumentNullException ( nameof ( connectionString ) ) ;
if ( providerName . IsNullOrWhiteSpace ( ) ) throw new ArgumentNullException ( nameof ( providerName ) ) ;
_connectionString = connectionString ;
_providerName = providerName ;
_connectionRetryPolicy = RetryPolicyFactory . GetDefaultSqlConnectionRetryPolicyByConnectionString ( _connectionString ) ;
_commandRetryPolicy = RetryPolicyFactory . GetDefaultSqlCommandRetryPolicyByConnectionString ( _connectionString ) ;
_dbProviderFactory = DbProviderFactories . GetFactory ( _providerName ) ;
if ( _dbProviderFactory = = null )
throw new Exception ( $"Can't find a provider factory for provider name \" { _providerName } \ "." ) ;
_databaseType = DatabaseType . Resolve ( _dbProviderFactory . GetType ( ) . Name , _providerName ) ;
if ( _databaseType = = null )
throw new Exception ( $"Can't find an NPoco database type for provider name \" { _providerName } \ "." ) ;
_sqlSyntax = GetSqlSyntaxProvider ( _providerName ) ;
if ( _sqlSyntax = = null )
throw new Exception ( $"Can't find a sql syntax provider for provider name \" { _providerName } \ "." ) ;
// ensure we have only 1 set of mappers, and 1 PocoDataFactory, for all database
// so that everything NPoco is properly cached for the lifetime of the application
2018-03-30 10:16:21 +02:00
_pocoMappers = new NPoco . MapperCollection { new PocoMapper ( ) } ;
var factory = new FluentPocoDataFactory ( GetPocoDataFactoryResolver ) ;
2016-12-14 14:06:30 +01:00
_pocoDataFactory = factory ;
var config = new FluentConfig ( xmappers = > factory ) ;
// create the database factory
2018-03-30 10:16:21 +02:00
_npocoDatabaseFactory = DatabaseFactory . Config ( x = > x
2016-12-14 14:06:30 +01:00
. UsingDatabase ( CreateDatabaseInstance ) // creating UmbracoDatabase instances
. WithFluentConfig ( config ) ) ; // with proper configuration
2016-12-16 14:18:37 +01:00
if ( _npocoDatabaseFactory = = null ) throw new NullReferenceException ( "The call to UmbracoDatabaseFactory.Config yielded a null UmbracoDatabaseFactory instance." ) ;
2016-12-14 14:06:30 +01:00
2017-09-27 10:25:47 +02:00
// can initialize now because it is the UmbracoDatabaseFactory that determines
// the sql syntax, poco data factory, and database type
_sqlContext . Initialize ( _sqlSyntax , _databaseType , _pocoDataFactory , _mappers ) ;
2016-12-14 14:06:30 +01:00
_logger . Debug < UmbracoDatabaseFactory > ( "Configured." ) ;
Configured = true ;
}
}
2016-12-16 14:18:37 +01:00
/// <inheritdoc />
public IUmbracoDatabase CreateDatabase ( )
{
return ( IUmbracoDatabase ) _npocoDatabaseFactory . GetDatabase ( ) ;
}
2018-03-30 10:16:21 +02:00
// gets initialized poco data builders
private InitializedPocoDataBuilder GetPocoDataFactoryResolver ( Type type , IPocoDataFactory factory )
= > new UmbracoPocoDataBuilder ( type , _pocoMappers , _upgrading ) . Init ( ) ;
2016-12-14 14:06:30 +01:00
// gets the sql syntax provider that corresponds, from attribute
private ISqlSyntaxProvider GetSqlSyntaxProvider ( string providerName )
{
var name = providerName . ToLowerInvariant ( ) ;
var provider = _sqlSyntaxProviders . FirstOrDefault ( x = >
x . GetType ( )
. FirstAttribute < SqlSyntaxProviderAttribute > ( )
. ProviderName . ToLowerInvariant ( )
. Equals ( name ) ) ;
if ( provider ! = null ) return provider ;
throw new InvalidOperationException ( $"Unknown provider name \" { providerName } \ "" ) ;
// previously we'd try to return SqlServerSyntaxProvider by default but this is bad
//provider = _syntaxProviders.FirstOrDefault(x => x.GetType() == typeof(SqlServerSyntaxProvider));
}
2016-12-16 14:18:37 +01:00
// ensures that the database is configured, else throws
2016-12-14 14:06:30 +01:00
private void EnsureConfigured ( )
{
2017-09-27 10:25:47 +02:00
_lock . EnterReadLock ( ) ;
try
2016-12-14 14:06:30 +01:00
{
if ( Configured = = false )
throw new InvalidOperationException ( "Not configured." ) ;
}
2017-09-27 10:25:47 +02:00
finally
{
if ( _lock . IsReadLockHeld )
_lock . ExitReadLock ( ) ;
}
2016-12-14 14:06:30 +01:00
}
2016-12-16 14:18:37 +01:00
// method used by NPoco's UmbracoDatabaseFactory to actually create the database instance
2016-12-14 14:06:30 +01:00
private UmbracoDatabase CreateDatabaseInstance ( )
{
return new UmbracoDatabase ( _connectionString , _sqlContext , _dbProviderFactory , _logger , _connectionRetryPolicy , _commandRetryPolicy ) ;
}
protected override void DisposeResources ( )
{
// this is weird, because hybrid accessors store different databases per
// thread, so we don't really know what we are disposing here...
// besides, we don't really want to dispose the factory, which is a singleton...
// fixme - does not make any sense!
//var db = _umbracoDatabaseAccessor.UmbracoDatabase;
//_umbracoDatabaseAccessor.UmbracoDatabase = null;
//db?.Dispose();
Configured = false ;
}
// during tests, the thread static var can leak between tests
// this method provides a way to force-reset the variable
2017-07-20 11:21:28 +02:00
internal void ResetForTests ( )
{
2016-12-14 14:06:30 +01:00
// fixme - does not make any sense!
//var db = _umbracoDatabaseAccessor.UmbracoDatabase;
//_umbracoDatabaseAccessor.UmbracoDatabase = null;
//db?.Dispose();
2017-07-20 11:21:28 +02:00
//_databaseScopeAccessor.Scope = null;
}
2016-12-14 14:06:30 +01:00
}
2017-07-20 11:21:28 +02:00
}