Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Associate the DatabaseRoot with the scoped options instance and not the singleton options. #34477

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ public static IServiceCollection AddEntityFrameworkInMemoryDatabase(this IServic
.TryAddProviderSpecificServices(
b => b
.TryAddSingleton<IInMemorySingletonOptions, InMemorySingletonOptions>()
.TryAddSingleton<IInMemoryStoreCache, InMemoryStoreCache>()
.TryAddScoped<IInMemoryStoreProvider, InMemoryStoreProvider>()
.TryAddSingleton<IInMemoryDatabaseRootCache, InMemoryDatabaseRootCache>()
.TryAddSingleton<IInMemoryTableFactory, InMemoryTableFactory>()
.TryAddScoped<IInMemoryDatabase, InMemoryDatabase>());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,6 @@ namespace Microsoft.EntityFrameworkCore.InMemory.Infrastructure.Internal;
/// </remarks>
public interface IInMemorySingletonOptions : ISingletonOptions
{
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
InMemoryDatabaseRoot? DatabaseRoot { get; }

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public InMemoryOptionsExtension()
protected InMemoryOptionsExtension(InMemoryOptionsExtension copyFrom)
{
_storeName = copyFrom._storeName;
_nullabilityCheckEnabled = copyFrom._nullabilityCheckEnabled;
_databaseRoot = copyFrom._databaseRoot;
}

Expand Down Expand Up @@ -170,6 +171,11 @@ public override string LogFragment

builder.Append("StoreName=").Append(Extension._storeName).Append(' ');

if (Extension._databaseRoot is { } root)
{
builder.Append("DatabaseRoot=").Append(root.GetHashCode()).Append(' ');
}

if (!Extension._nullabilityCheckEnabled)
{
builder.Append("NullabilityChecksEnabled ");
Expand All @@ -185,22 +191,16 @@ public override string LogFragment
public override int GetServiceProviderHashCode()
{
var hashCode = new HashCode();
hashCode.Add(Extension._databaseRoot);
hashCode.Add(Extension._nullabilityCheckEnabled);
return hashCode.ToHashCode();
}

public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other)
=> other is ExtensionInfo otherInfo
&& Extension._databaseRoot == otherInfo.Extension._databaseRoot
&& Extension._nullabilityCheckEnabled == otherInfo.Extension._nullabilityCheckEnabled;

public override void PopulateDebugInfo(IDictionary<string, string> debugInfo)
{
debugInfo["InMemoryDatabase:DatabaseRoot"]
= (Extension._databaseRoot?.GetHashCode() ?? 0).ToString(CultureInfo.InvariantCulture);
debugInfo["InMemoryDatabase:NullabilityChecksEnabled"]
=> debugInfo["InMemoryDatabase:NullabilityChecksEnabled"]
= (!Extension._nullabilityCheckEnabled).GetHashCode().ToString(CultureInfo.InvariantCulture);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ public virtual void Initialize(IDbContextOptions options)

if (inMemoryOptions != null)
{
DatabaseRoot = inMemoryOptions.DatabaseRoot;
IsNullabilityCheckEnabled = inMemoryOptions.IsNullabilityCheckEnabled;
}
}
Expand All @@ -38,15 +37,6 @@ public virtual void Validate(IDbContextOptions options)
{
var inMemoryOptions = options.FindExtension<InMemoryOptionsExtension>();

if (inMemoryOptions != null
&& DatabaseRoot != inMemoryOptions.DatabaseRoot)
{
throw new InvalidOperationException(
CoreStrings.SingletonOptionChanged(
nameof(InMemoryDbContextOptionsExtensions.UseInMemoryDatabase),
nameof(DbContextOptionsBuilder.UseInternalServiceProvider)));
}

if (inMemoryOptions != null
&& IsNullabilityCheckEnabled != inMemoryOptions.IsNullabilityCheckEnabled)
{
Expand All @@ -57,14 +47,6 @@ public virtual void Validate(IDbContextOptions options)
}
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual InMemoryDatabaseRoot? DatabaseRoot { get; private set; }

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace Microsoft.EntityFrameworkCore.InMemory.Query.Internal;
/// </summary>
public class InMemoryQueryContextFactory : IQueryContextFactory
{
private readonly IInMemoryStore _store;
private readonly IInMemoryStoreProvider _storeProvider;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand All @@ -23,10 +23,9 @@ public class InMemoryQueryContextFactory : IQueryContextFactory
/// </summary>
public InMemoryQueryContextFactory(
QueryContextDependencies dependencies,
IInMemoryStoreCache storeCache,
IDbContextOptions contextOptions)
IInMemoryStoreProvider storeProvider)
{
_store = storeCache.GetStore(contextOptions);
_storeProvider = storeProvider;
Dependencies = dependencies;
}

Expand All @@ -42,5 +41,5 @@ public InMemoryQueryContextFactory(
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual QueryContext Create()
=> new InMemoryQueryContext(Dependencies, _store);
=> new InMemoryQueryContext(Dependencies, _storeProvider.Store);
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.EntityFrameworkCore.InMemory.Infrastructure.Internal;

namespace Microsoft.EntityFrameworkCore.InMemory.Storage.Internal;

/// <summary>
Expand All @@ -11,14 +9,13 @@ namespace Microsoft.EntityFrameworkCore.InMemory.Storage.Internal;
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public static class InMemoryStoreCacheExtensions
public interface IInMemoryDatabaseRootCache
{
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public static IInMemoryStore GetStore(this IInMemoryStoreCache storeCache, IDbContextOptions options)
=> storeCache.GetStore(options.Extensions.OfType<InMemoryOptionsExtension>().First().StoreName);
InMemoryDatabaseRoot SharedRoot { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ namespace Microsoft.EntityFrameworkCore.InMemory.Storage.Internal;
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public interface IInMemoryStoreCache
public interface IInMemoryStoreProvider
{
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
IInMemoryStore GetStore(string name);
IInMemoryStore Store { get; }
}
15 changes: 7 additions & 8 deletions src/EFCore.InMemory/Storage/Internal/InMemoryDatabase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace Microsoft.EntityFrameworkCore.InMemory.Storage.Internal;
/// </summary>
public class InMemoryDatabase : Database, IInMemoryDatabase
{
private readonly IInMemoryStore _store;
private readonly IInMemoryStoreProvider _storeProvider;
private readonly IUpdateAdapterFactory _updateAdapterFactory;
private readonly IDiagnosticsLogger<DbLoggerCategory.Update> _updateLogger;
private readonly IDesignTimeModel _designTimeModel;
Expand All @@ -24,14 +24,13 @@ public class InMemoryDatabase : Database, IInMemoryDatabase
/// </summary>
public InMemoryDatabase(
DatabaseDependencies dependencies,
IInMemoryStoreCache storeCache,
IDbContextOptions options,
IInMemoryStoreProvider storeProvider,
IDesignTimeModel designTimeModel,
IUpdateAdapterFactory updateAdapterFactory,
IDiagnosticsLogger<DbLoggerCategory.Update> updateLogger)
: base(dependencies)
{
_store = storeCache.GetStore(options);
_storeProvider = storeProvider;
_designTimeModel = designTimeModel;
_updateAdapterFactory = updateAdapterFactory;
_updateLogger = updateLogger;
Expand All @@ -44,7 +43,7 @@ public InMemoryDatabase(
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual IInMemoryStore Store
=> _store;
=> _storeProvider.Store;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand All @@ -53,7 +52,7 @@ public virtual IInMemoryStore Store
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public override int SaveChanges(IList<IUpdateEntry> entries)
=> _store.ExecuteTransaction(entries, _updateLogger);
=> Store.ExecuteTransaction(entries, _updateLogger);

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand All @@ -66,7 +65,7 @@ public override Task<int> SaveChangesAsync(
CancellationToken cancellationToken = default)
=> cancellationToken.IsCancellationRequested
? Task.FromCanceled<int>(cancellationToken)
: Task.FromResult(_store.ExecuteTransaction(entries, _updateLogger));
: Task.FromResult(Store.ExecuteTransaction(entries, _updateLogger));

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand All @@ -75,5 +74,5 @@ public override Task<int> SaveChangesAsync(
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual bool EnsureDatabaseCreated()
=> _store.EnsureCreated(_updateAdapterFactory, _designTimeModel.Model, _updateLogger);
=> Store.EnsureCreated(_updateAdapterFactory, _designTimeModel.Model, _updateLogger);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.EntityFrameworkCore.InMemory.Storage.Internal;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public class InMemoryDatabaseRootCache : IInMemoryDatabaseRootCache
{
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public InMemoryDatabaseRoot SharedRoot { get; } = new();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Concurrent;

namespace Microsoft.EntityFrameworkCore.InMemory.Storage.Internal;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public static class InMemoryDatabaseRootExtensions
{
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public static IInMemoryStore GetStore(this InMemoryDatabaseRoot root, string name, IInMemoryTableFactory tableFactory)
{
var instance = (ConcurrentDictionary<string, IInMemoryStore>)
LazyInitializer.EnsureInitialized(
ref root.Instance,
() => new ConcurrentDictionary<string, IInMemoryStore>());

return instance.GetOrAdd(name, static (_, f) => new InMemoryStore(f), tableFactory);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Concurrent;
using Microsoft.EntityFrameworkCore.InMemory.Infrastructure.Internal;

namespace Microsoft.EntityFrameworkCore.InMemory.Storage.Internal;
Expand All @@ -12,35 +11,24 @@ namespace Microsoft.EntityFrameworkCore.InMemory.Storage.Internal;
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public class InMemoryStoreCache : IInMemoryStoreCache
public class InMemoryStoreProvider : IInMemoryStoreProvider
{
private readonly IDbContextOptions _options;
private readonly IInMemoryDatabaseRootCache _databaseRootCache;
private readonly IInMemoryTableFactory _tableFactory;
private readonly ConcurrentDictionary<string, IInMemoryStore> _namedStores;
private IInMemoryStore? _store;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public InMemoryStoreCache(
IInMemoryTableFactory tableFactory,
IInMemorySingletonOptions? options)
public InMemoryStoreProvider(IDbContextOptions options, IInMemoryDatabaseRootCache databaseRootCache, IInMemoryTableFactory tableFactory)
{
_options = options;
_databaseRootCache = databaseRootCache;
_tableFactory = tableFactory;

if (options?.DatabaseRoot != null)
{
LazyInitializer.EnsureInitialized(
ref options.DatabaseRoot.Instance,
() => new ConcurrentDictionary<string, IInMemoryStore>());

_namedStores = (ConcurrentDictionary<string, IInMemoryStore>)options.DatabaseRoot.Instance;
}
else
{
_namedStores = new ConcurrentDictionary<string, IInMemoryStore>();
}
}

/// <summary>
Expand All @@ -49,6 +37,19 @@ public InMemoryStoreCache(
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual IInMemoryStore GetStore(string name)
=> _namedStores.GetOrAdd(name, _ => new InMemoryStore(_tableFactory));
public IInMemoryStore Store
=> LazyInitializer.EnsureInitialized(ref _store, InitializeStore);

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
protected virtual IInMemoryStore InitializeStore()
{
var extension = _options.Extensions.OfType<InMemoryOptionsExtension>().First();
var root = extension.DatabaseRoot ?? _databaseRootCache.SharedRoot;
return root.GetStore(extension.StoreName, _tableFactory);
}
}
Loading
Loading