Skip to content

Commit

Permalink
Heisenberg existence... (Allow model store item collection to be used…
Browse files Browse the repository at this point in the history
… in provider DbDatabaseExists method in cases where it was previously available.)

We changed code in 6.1.1 to avoid the need to build the model before calling Exists. The reason for this is that building the model can require a connection to the database in order to get the provider manifest token. This means that, the model passed to Exists was empty because the real model has not yet been built. Note that this could happen in certain code paths before (e.g. always with Migrations and when using static methods on Database), but it now happens in most or all code paths.

The problem with this is that some providers use the model information in order to return something useable from Exists when it is not possible to know if the "database" has been created or not.

The fix included here adds a new method to DbProviderServices that takes a Lazy<StoreItemCollection>. The SQL Server provider overrides this method but never evaluates the Lazy and so the model is still not built. Providers that do not override the new method will get the base implementation which simply evaluates the Lazy and calls the original DbDatabaseExists method. This should therefore revert to the 6.1 behavior for providers other than SQL Server.
  • Loading branch information
ajcvickers committed Jun 10, 2014
1 parent 9dd0827 commit 787e6ab
Show file tree
Hide file tree
Showing 12 changed files with 168 additions and 29 deletions.
26 changes: 25 additions & 1 deletion src/EntityFramework.SqlServer/SqlProviderServices.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1107,7 +1107,31 @@ internal static SqlVersion CreateDatabaseFromScript(int? commandTimeout, DbConne
/// <param name="commandTimeout">Execution timeout for any commands needed to determine the existence of the database.</param>
/// <param name="storeItemCollection">The collection of all store items from the model. This parameter is no longer used for determining database existence.</param>
/// <returns>True if the provider can deduce the database only based on the connection.</returns>
protected override bool DbDatabaseExists(DbConnection connection, int? commandTimeout, StoreItemCollection storeItemCollection)
protected override bool DbDatabaseExists(
DbConnection connection, int? commandTimeout, StoreItemCollection storeItemCollection)
{
return DbDatabaseExists(connection, commandTimeout, new Lazy<StoreItemCollection>(() => storeItemCollection));
}

/// <summary>
/// Determines whether the database for the given connection exists.
/// There are three cases:
/// 1. Initial Catalog = X, AttachDBFilename = null: (SELECT Count(*) FROM sys.databases WHERE [name]= X) > 0
/// 2. Initial Catalog = X, AttachDBFilename = F: if (SELECT Count(*) FROM sys.databases WHERE [name]= X) > true,
/// if not, try to open the connection and then return (SELECT Count(*) FROM sys.databases WHERE [name]= X) > 0
/// 3. Initial Catalog = null, AttachDBFilename = F: Try to open the connection. If that succeeds the result is true, otherwise
/// if the there are no databases corresponding to the given file return false, otherwise throw.
/// Note: We open the connection to cover the scenario when the mdf exists, but is not attached.
/// Given that opening the connection would auto-attach it, it would not be appropriate to return false in this case.
/// Also note that checking for the existence of the file does not work for a remote server. (Dev11 #290487)
/// For further details on the behavior when AttachDBFilename is specified see Dev10# 188936
/// </summary>
/// <param name="connection">Connection to a database whose existence is checked by this method.</param>
/// <param name="commandTimeout">Execution timeout for any commands needed to determine the existence of the database.</param>
/// <param name="storeItemCollection">The collection of all store items from the model. This parameter is no longer used for determining database existence.</param>
/// <returns>True if the provider can deduce the database only based on the connection.</returns>
protected override bool DbDatabaseExists(
DbConnection connection, int? commandTimeout, Lazy<StoreItemCollection> storeItemCollection)
{
Check.NotNull(connection, "connection");
Check.NotNull(storeItemCollection, "storeItemCollection");
Expand Down
14 changes: 14 additions & 0 deletions src/EntityFramework.SqlServerCompact/SqlCeProviderServices.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,20 @@ protected override string DbCreateDatabaseScript(string providerManifestToken, S
/// <param name="storeItemCollection"> Item Collection. </param>
/// <returns> Bool indicating whether database exists or not. </returns>
protected override bool DbDatabaseExists(DbConnection connection, int? timeOut, StoreItemCollection storeItemCollection)
{
return DbDatabaseExists(connection, timeOut, new Lazy<StoreItemCollection>(() => storeItemCollection));
}

/// <summary>
/// API for checkin whether database exists or not.
/// This will internally only check whether the file that the connection points to exists or not.
/// Note: In case of SQLCE, timeout and storeItemCollection parameters are ignored.
/// </summary>
/// <param name="connection"> Connection </param>
/// <param name="timeOut"> Timeout for internal commands. </param>
/// <param name="storeItemCollection"> Item Collection. </param>
/// <returns> Bool indicating whether database exists or not. </returns>
protected override bool DbDatabaseExists(DbConnection connection, int? timeOut, Lazy<StoreItemCollection> storeItemCollection)
{
Check.NotNull(connection, "connection");
Check.NotNull(storeItemCollection, "storeItemCollection");
Expand Down
39 changes: 38 additions & 1 deletion src/EntityFramework/Core/Common/DbProviderServices.cs
Original file line number Diff line number Diff line change
Expand Up @@ -639,13 +639,33 @@ public bool DatabaseExists(DbConnection connection, int? commandTimeout, StoreIt
}
}

/// <summary>Returns a value indicating whether a given database exists on the server.</summary>
/// <returns>True if the provider can deduce the database only based on the connection.</returns>
/// <param name="connection">Connection to a database whose existence is checked by this method.</param>
/// <param name="commandTimeout">Execution timeout for any commands needed to determine the existence of the database.</param>
/// <param name="storeItemCollection">The collection of all store items from the model. This parameter is no longer used for determining database existence.</param>
public bool DatabaseExists(
DbConnection connection,
int? commandTimeout,
Lazy<StoreItemCollection> storeItemCollection)
{
Check.NotNull(connection, "connection");
Check.NotNull(storeItemCollection, "storeItemCollection");

using (new TransactionScope(TransactionScopeOption.Suppress))
{
return DbDatabaseExists(connection, commandTimeout, storeItemCollection);
}
}

/// <summary>Returns a value indicating whether a given database exists on the server.</summary>
/// <returns>True if the provider can deduce the database only based on the connection.</returns>
/// <param name="connection">Connection to a database whose existence is checked by this method.</param>
/// <param name="commandTimeout">Execution timeout for any commands needed to determine the existence of the database.</param>
/// <param name="storeItemCollection">The collection of all store items from the model. This parameter is no longer used for determining database existence.</param>
protected virtual bool DbDatabaseExists(
DbConnection connection, int? commandTimeout,
DbConnection connection,
int? commandTimeout,
StoreItemCollection storeItemCollection)
{
Check.NotNull(connection, "connection");
Expand All @@ -654,6 +674,23 @@ protected virtual bool DbDatabaseExists(
throw new ProviderIncompatibleException(Strings.ProviderDoesNotSupportDatabaseExists);
}

/// <summary>Returns a value indicating whether a given database exists on the server.</summary>
/// <returns>True if the provider can deduce the database only based on the connection.</returns>
/// <param name="connection">Connection to a database whose existence is checked by this method.</param>
/// <param name="commandTimeout">Execution timeout for any commands needed to determine the existence of the database.</param>
/// <param name="storeItemCollection">The collection of all store items from the model. This parameter is no longer used for determining database existence.</param>
/// <remarks>Override this method to avoid creating the store item collection if it is not needed. The default implementation evaluates the Lazy and calls the other overload of this method.</remarks>
protected virtual bool DbDatabaseExists(
DbConnection connection,
int? commandTimeout,
Lazy<StoreItemCollection> storeItemCollection)
{
Check.NotNull(connection, "connection");
Check.NotNull(storeItemCollection, "storeItemCollection");

return DbDatabaseExists(connection, commandTimeout, storeItemCollection.Value);
}

/// <summary>Deletes the specified database.</summary>
/// <param name="connection">Connection to an existing database that needs to be deleted.</param>
/// <param name="commandTimeout">Execution timeout for any commands needed to delete the database.</param>
Expand Down
40 changes: 34 additions & 6 deletions src/EntityFramework/Database.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace System.Data.Entity
using System.ComponentModel;
using System.Data.Common;
using System.Data.Entity.Core.EntityClient;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Core.Objects;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.Infrastructure.DependencyResolution;
Expand Down Expand Up @@ -202,7 +203,10 @@ internal void Create(DatabaseExistenceState existenceState)
{
if (existenceState == DatabaseExistenceState.Unknown)
{
if (_internalContext.DatabaseOperations.Exists(_internalContext.Connection, _internalContext.CommandTimeout))
if (_internalContext.DatabaseOperations.Exists(
_internalContext.Connection,
_internalContext.CommandTimeout,
new Lazy<StoreItemCollection>(CreateStoreItemCollection)))
{
var interceptionContext = new DbInterceptionContext();
interceptionContext = interceptionContext.WithDbContext(_internalContext.Owner);
Expand All @@ -226,7 +230,10 @@ internal void Create(DatabaseExistenceState existenceState)
/// <returns> True if the database did not exist and was created; false otherwise. </returns>
public bool CreateIfNotExists()
{
if (_internalContext.DatabaseOperations.Exists(_internalContext.Connection, _internalContext.CommandTimeout))
if (_internalContext.DatabaseOperations.Exists(
_internalContext.Connection,
_internalContext.CommandTimeout,
new Lazy<StoreItemCollection>(CreateStoreItemCollection)))
{
return false;
}
Expand All @@ -244,7 +251,10 @@ public bool CreateIfNotExists()
/// <returns> True if the database exists; false otherwise. </returns>
public bool Exists()
{
return _internalContext.DatabaseOperations.Exists(_internalContext.Connection, _internalContext.CommandTimeout);
return _internalContext.DatabaseOperations.Exists(
_internalContext.Connection,
_internalContext.CommandTimeout,
new Lazy<StoreItemCollection>(CreateStoreItemCollection));
}

/// <summary>
Expand All @@ -257,7 +267,10 @@ public bool Exists()
/// <returns> True if the database did exist and was deleted; false otherwise. </returns>
public bool Delete()
{
if (!_internalContext.DatabaseOperations.Exists(_internalContext.Connection, _internalContext.CommandTimeout))
if (!_internalContext.DatabaseOperations.Exists(
_internalContext.Connection,
_internalContext.CommandTimeout,
new Lazy<StoreItemCollection>(CreateStoreItemCollection)))
{
return false;
}
Expand Down Expand Up @@ -289,7 +302,10 @@ public static bool Exists(string nameOrConnectionString)

using (var connection = new LazyInternalConnection(nameOrConnectionString))
{
return new DatabaseOperations().Exists(connection.Connection, null);
return new DatabaseOperations().Exists(
connection.Connection,
null,
new Lazy<StoreItemCollection>(() => new StoreItemCollection()));
}
}

Expand Down Expand Up @@ -329,7 +345,10 @@ public static bool Exists(DbConnection existingConnection)
{
Check.NotNull(existingConnection, "existingConnection");

return new DatabaseOperations().Exists(existingConnection, null);
return new DatabaseOperations().Exists(
existingConnection,
null,
new Lazy<StoreItemCollection>(() => new StoreItemCollection()));
}

/// <summary>
Expand Down Expand Up @@ -692,6 +711,15 @@ public override int GetHashCode()

#endregion

private StoreItemCollection CreateStoreItemCollection()
{
using (var clonedObjectContext = _internalContext.CreateObjectContextForDdlOps())
{
var entityConnection = ((EntityConnection)clonedObjectContext.ObjectContext.Connection);
return (StoreItemCollection)entityConnection.GetMetadataWorkspace().GetItemCollection(DataSpace.SSpace);
}
}

/// <summary>
/// Gets or sets the timeout value, in seconds, for all context operations.
/// The default value is null, where null indicates that the default value of the underlying
Expand Down
4 changes: 2 additions & 2 deletions src/EntityFramework/Internal/DatabaseOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public virtual bool Create(ObjectContext objectContext)
// having an ObjectContext.
// </summary>
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
public virtual bool Exists(DbConnection connection, int? commandTimeout)
public virtual bool Exists(DbConnection connection, int? commandTimeout, Lazy<StoreItemCollection> storeItemCollection)
{
DebugCheck.NotNull(connection);

Expand All @@ -51,7 +51,7 @@ public virtual bool Exists(DbConnection connection, int? commandTimeout)
try
{
return DbProviderServices.GetProviderServices(connection)
.DatabaseExists(connection, commandTimeout, new StoreItemCollection());
.DatabaseExists(connection, commandTimeout, storeItemCollection);
}
catch
{
Expand Down
14 changes: 13 additions & 1 deletion src/EntityFramework/Internal/DatabaseTableChecker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ internal class DatabaseTableChecker
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
public DatabaseExistenceState AnyModelTableExists(InternalContext internalContext)
{
var exists = internalContext.DatabaseOperations.Exists(internalContext.Connection, internalContext.CommandTimeout);
var exists = internalContext.DatabaseOperations.Exists(
internalContext.Connection,
internalContext.CommandTimeout,
new Lazy<StoreItemCollection>(() => CreateStoreItemCollection(internalContext)));

if (!exists)
{
Expand Down Expand Up @@ -87,6 +90,15 @@ public DatabaseExistenceState AnyModelTableExists(InternalContext internalContex
}
}

private static StoreItemCollection CreateStoreItemCollection(InternalContext internalContext)
{
using (var clonedObjectContext = internalContext.CreateObjectContextForDdlOps())
{
var entityConnection = ((EntityConnection)clonedObjectContext.ObjectContext.Connection);
return (StoreItemCollection)entityConnection.GetMetadataWorkspace().GetItemCollection(DataSpace.SSpace);
}
}

public virtual bool QueryForTableExistence(
IPseudoProvider provider, ClonedObjectContext clonedObjectContext, List<EntitySet> modelTables)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ namespace System.Data.Entity.WrappingProvider
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Migrations.Sql;
using System.Data.Entity.Functionals.Utilities;
using System.Reflection;

public class WrappingEfProvider<TAdoNetBase, TEfBase> : DbProviderServices
where TAdoNetBase : DbProviderFactory
Expand Down Expand Up @@ -46,10 +45,10 @@ protected override void DbCreateDatabase(DbConnection connection, int? commandTi
_baseServices.CreateDatabase(((WrappingConnection<TAdoNetBase>)connection).BaseConnection, commandTimeout, storeItemCollection);
}

protected override bool DbDatabaseExists(DbConnection connection, int? commandTimeout, StoreItemCollection storeItemCollection)
protected override bool DbDatabaseExists(DbConnection connection, int? commandTimeout, Lazy<StoreItemCollection> storeItemCollection)
{
WrappingAdoNetProvider<TAdoNetBase>.Instance.Log.Add(
new LogItem("DbDatabaseExists", connection, new object[] { commandTimeout, storeItemCollection }));
new LogItem("DbDatabaseExists", connection, new object[] { commandTimeout, storeItemCollection.Value }));

return _baseServices.DatabaseExists(
((WrappingConnection<TAdoNetBase>)connection).BaseConnection, commandTimeout, storeItemCollection);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace System.Data.Entity.WrappingProvider
using System.Collections.Generic;
using System.Data.Common;
using System.Data.Entity.Core.Common;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Functionals.Utilities;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.Infrastructure.DependencyResolution;
Expand Down Expand Up @@ -191,6 +192,28 @@ public void Simple_query_and_update_works_with_wrapping_provider_setup_at_EF_lev
Assert.Contains("Generate", methods);
}

[Fact] // CodePlex 2320
[UseDefaultExecutionStrategy]
public void Model_is_available_in_DatabaseExists()
{
WrappingAdoNetProvider<SqlClientFactory>.WrapProviders();

var log = WrappingAdoNetProvider<SqlClientFactory>.Instance.Log;
log.Clear();

using (var context = new EfLevelBlogContext())
{
context.Database.Exists();
}

var rawDetails = (object[])log.Where(i => i.Method == "DbDatabaseExists").Select(i => i.RawDetails).Single();
var itemCollection = (StoreItemCollection)rawDetails[1];

Assert.Equal(
new[] { "Blog", "Post" },
itemCollection.OfType<EntityType>().Select(e => e.Name).OrderBy(n => n).ToArray());
}

public class Blog
{
public int Id { get; set; }
Expand Down
Loading

0 comments on commit 787e6ab

Please sign in to comment.