Skip to content

Commit

Permalink
Store options in ServiceProviderCache
Browse files Browse the repository at this point in the history
Fixes #19152
  • Loading branch information
AndriySvyryd committed Jul 22, 2021
1 parent 6f50372 commit 7c1eec0
Show file tree
Hide file tree
Showing 17 changed files with 275 additions and 268 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -519,7 +519,7 @@ public virtual void Validate(IDbContextOptions options)
private sealed class ExtensionInfo : DbContextOptionsExtensionInfo
{
private string? _logFragment;
private long? _serviceProviderHash;
private int? _serviceProviderHash;

public ExtensionInfo(IDbContextOptionsExtension extension)
: base(extension)
Expand All @@ -532,37 +532,57 @@ public ExtensionInfo(IDbContextOptionsExtension extension)
public override bool IsDatabaseProvider
=> true;

public override long GetServiceProviderHashCode()
public override int GetServiceProviderHashCode()
{
if (_serviceProviderHash == null)
{
long hashCode;
var hashCode = new HashCode();

if (!string.IsNullOrEmpty(Extension._connectionString))
{
hashCode = Extension._connectionString.GetHashCode();
hashCode.Add(Extension._connectionString);
}
else
{
hashCode = (Extension._accountEndpoint?.GetHashCode() ?? 0);
hashCode = (hashCode * 397) ^ (Extension._accountKey?.GetHashCode() ?? 0);
hashCode.Add(Extension._accountEndpoint);
hashCode.Add(Extension._accountKey);
}

hashCode = (hashCode * 397) ^ (Extension._region?.GetHashCode() ?? 0);
hashCode = (hashCode * 3) ^ (Extension._connectionMode?.GetHashCode() ?? 0);
hashCode = (hashCode * 3) ^ (Extension._limitToEndpoint?.GetHashCode() ?? 0);
hashCode = (hashCode * 397) ^ (Extension._webProxy?.GetHashCode() ?? 0);
hashCode = (hashCode * 397) ^ (Extension._requestTimeout?.GetHashCode() ?? 0);
hashCode = (hashCode * 397) ^ (Extension._openTcpConnectionTimeout?.GetHashCode() ?? 0);
hashCode = (hashCode * 397) ^ (Extension._idleTcpConnectionTimeout?.GetHashCode() ?? 0);
hashCode = (hashCode * 131) ^ (Extension._gatewayModeMaxConnectionLimit?.GetHashCode() ?? 0);
hashCode = (hashCode * 397) ^ (Extension._maxTcpConnectionsPerEndpoint?.GetHashCode() ?? 0);
hashCode = (hashCode * 131) ^ (Extension._maxRequestsPerTcpConnection?.GetHashCode() ?? 0);
_serviceProviderHash = hashCode;
hashCode.Add(Extension._region);
hashCode.Add(Extension._connectionMode);
hashCode.Add(Extension._limitToEndpoint);
hashCode.Add(Extension._enableContentResponseOnWrite);
hashCode.Add(Extension._webProxy);
hashCode.Add(Extension._requestTimeout);
hashCode.Add(Extension._openTcpConnectionTimeout);
hashCode.Add(Extension._idleTcpConnectionTimeout);
hashCode.Add(Extension._gatewayModeMaxConnectionLimit);
hashCode.Add(Extension._maxTcpConnectionsPerEndpoint);
hashCode.Add(Extension._maxRequestsPerTcpConnection);

_serviceProviderHash = hashCode.ToHashCode();
}

return _serviceProviderHash.Value;
}

public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other)
=> other is ExtensionInfo otherInfo
&& Extension._connectionString == otherInfo.Extension._connectionString
&& Extension._accountEndpoint == otherInfo.Extension._accountEndpoint
&& Extension._accountKey == otherInfo.Extension._accountKey
&& Extension._region == otherInfo.Extension._region
&& Extension._connectionMode == otherInfo.Extension._connectionMode
&& Extension._limitToEndpoint == otherInfo.Extension._limitToEndpoint
&& Extension._enableContentResponseOnWrite == otherInfo.Extension._enableContentResponseOnWrite
&& Extension._webProxy == otherInfo.Extension._webProxy
&& Extension._requestTimeout == otherInfo.Extension._requestTimeout
&& Extension._openTcpConnectionTimeout == otherInfo.Extension._openTcpConnectionTimeout
&& Extension._idleTcpConnectionTimeout == otherInfo.Extension._idleTcpConnectionTimeout
&& Extension._gatewayModeMaxConnectionLimit == otherInfo.Extension._gatewayModeMaxConnectionLimit
&& Extension._maxTcpConnectionsPerEndpoint == otherInfo.Extension._maxTcpConnectionsPerEndpoint
&& Extension._maxRequestsPerTcpConnection == otherInfo.Extension._maxRequestsPerTcpConnection;

public override void PopulateDebugInfo(IDictionary<string, string> debugInfo)
{
Check.NotNull(debugInfo, nameof(debugInfo));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ public virtual void Initialize(IDbContextOptions options)
ConnectionString = cosmosOptions.ConnectionString;
Region = cosmosOptions.Region;
LimitToEndpoint = cosmosOptions.LimitToEndpoint;
EnableContentResponseOnWrite = cosmosOptions.EnableContentResponseOnWrite;
ConnectionMode = cosmosOptions.ConnectionMode;
WebProxy = cosmosOptions.WebProxy;
RequestTimeout = cosmosOptions.RequestTimeout;
Expand All @@ -161,7 +162,6 @@ public virtual void Initialize(IDbContextOptions options)
GatewayModeMaxConnectionLimit = cosmosOptions.GatewayModeMaxConnectionLimit;
MaxTcpConnectionsPerEndpoint = cosmosOptions.MaxTcpConnectionsPerEndpoint;
MaxRequestsPerTcpConnection = cosmosOptions.MaxRequestsPerTcpConnection;
EnableContentResponseOnWrite = cosmosOptions.EnableContentResponseOnWrite;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,12 +186,16 @@ public override string LogFragment
}
}

public override long GetServiceProviderHashCode()
=> Extension._databaseRoot?.GetHashCode() ?? 0L;
public override int GetServiceProviderHashCode()
=> Extension._databaseRoot?.GetHashCode() ?? 0;

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

public override void PopulateDebugInfo(IDictionary<string, string> debugInfo)
=> debugInfo["InMemoryDatabase:DatabaseRoot"]
= (Extension._databaseRoot?.GetHashCode() ?? 0L).ToString(CultureInfo.InvariantCulture);
= (Extension._databaseRoot?.GetHashCode() ?? 0).ToString(CultureInfo.InvariantCulture);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,12 @@ public override string LogFragment
? "using change tracking proxies "
: "";

public override long GetServiceProviderHashCode()
=> Extension.UseProxies ? 541 : 0;
public override int GetServiceProviderHashCode()
=> Extension.UseProxies.GetHashCode();

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

public override void PopulateDebugInfo(IDictionary<string, string> debugInfo)
{
Expand Down
14 changes: 12 additions & 2 deletions src/EFCore.Relational/Infrastructure/RelationalOptionsExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -428,12 +428,22 @@ public override bool IsDatabaseProvider

/// <summary>
/// Returns a hash code created from any options that would cause a new <see cref="IServiceProvider" />
/// to be needed. Most extensions do not have any such options and should return zero.
/// to be needed. For example, if the options affect a singleton service. However most extensions do not
/// have any such options and should return zero.
/// </summary>
/// <returns> A hash over options that require a new service provider when changed. </returns>
public override long GetServiceProviderHashCode()
public override int GetServiceProviderHashCode()
=> 0;

/// <summary>
/// Returns a value indicating whether all of the options used in <see cref="GetServiceProviderHashCode"/>
/// are the same as in the given extension.
/// </summary>
/// <param name="other"> The other extension. </param>
/// <returns> A value indicating whether all of the options that require a new service provider are the same. </returns>
public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other)
=> true;

/// <summary>
/// A message fragment for logging typically containing information about
/// any useful non-default options that have been configured.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,12 @@ public ExtensionInfo(IDbContextOptionsExtension extension)
public override bool IsDatabaseProvider
=> false;

public override long GetServiceProviderHashCode()
public override int GetServiceProviderHashCode()
=> 0;

public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other)
=> true;

public override void PopulateDebugInfo(IDictionary<string, string> debugInfo)
=> debugInfo["SqlServer:" + nameof(SqlServerNetTopologySuiteDbContextOptionsBuilderExtensions.UseNetTopologySuite)] = "1";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,12 @@ public override bool IsDatabaseProvider
public override string LogFragment
=> "using NetTopologySuite ";

public override long GetServiceProviderHashCode()
public override int GetServiceProviderHashCode()
=> 0;

public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other)
=> true;

public override void PopulateDebugInfo(IDictionary<string, string> debugInfo)
=> debugInfo["NetTopologySuite"] = "1";
}
Expand Down
54 changes: 48 additions & 6 deletions src/EFCore/DbContextOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Utilities;

namespace Microsoft.EntityFrameworkCore
Expand All @@ -27,23 +30,25 @@ protected DbContextOptions(
{
Check.NotNull(extensions, nameof(extensions));

_extensions = extensions;
_extensionsMap = extensions as ImmutableSortedDictionary<Type, IDbContextOptionsExtension>
?? ImmutableSortedDictionary.Create<Type, IDbContextOptionsExtension>(TypeFullNameComparer.Instance)
.AddRange(extensions);
}

/// <summary>
/// Gets the extensions that store the configured options.
/// </summary>
public virtual IEnumerable<IDbContextOptionsExtension> Extensions
=> _extensions.Values;
=> ExtensionsMap.Values;

/// <summary>
/// Gets the extension of the specified type. Returns null if no extension of the specified type is configured.
/// Gets the extension of the specified type. Returns <see langword="null" /> if no extension of the specified type is configured.
/// </summary>
/// <typeparam name="TExtension"> The type of the extension to get. </typeparam>
/// <returns> The extension, or null if none was found. </returns>
/// <returns> The extension, or <see langword="null" /> if none was found. </returns>
public virtual TExtension? FindExtension<TExtension>()
where TExtension : class, IDbContextOptionsExtension
=> _extensions.TryGetValue(typeof(TExtension), out var extension) ? (TExtension)extension : null;
=> ExtensionsMap.TryGetValue(typeof(TExtension), out var extension) ? (TExtension)extension : null;

/// <summary>
/// Gets the extension of the specified type. Throws if no extension of the specified type is configured.
Expand Down Expand Up @@ -72,7 +77,13 @@ public virtual TExtension GetExtension<TExtension>()
public abstract DbContextOptions WithExtension<TExtension>(TExtension extension)
where TExtension : class, IDbContextOptionsExtension;

private readonly IReadOnlyDictionary<Type, IDbContextOptionsExtension> _extensions;
private readonly ImmutableSortedDictionary<Type, IDbContextOptionsExtension> _extensionsMap;

/// <summary>
/// Gets the extensions that store the configured options.
/// </summary>
protected virtual IImmutableDictionary<Type, IDbContextOptionsExtension> ExtensionsMap
=> _extensionsMap;

/// <summary>
/// The type of context that these options are for. Will return <see cref="DbContext" /> if the
Expand All @@ -91,5 +102,36 @@ public virtual void Freeze()
/// configured with <see cref="DbContext.OnConfiguring(DbContextOptionsBuilder)" />.
/// </summary>
public virtual bool IsFrozen { get; private set; }

/// <inheritdoc />
public override bool Equals(object? obj)
=> ReferenceEquals(this, obj)
|| (obj is DbContextOptions otherOptions && Equals(otherOptions));

/// <summary>
/// Determines whether the specified object is equal to the current object.
/// </summary>
/// <param name="other"> The object to compare with the current object. </param>
/// <returns>
/// <see langword="true" /> if the specified object is equal to the current object; otherwise, <see langword="false" />.
/// </returns>
protected virtual bool Equals(DbContextOptions other)
=> _extensionsMap.Count == other._extensionsMap.Count
&& _extensionsMap.Zip(other._extensionsMap)
.All(p => p.First.Value.Info.ShouldUseSameServiceProvider(p.Second.Value.Info));

/// <inheritdoc />
public override int GetHashCode()
{
var hashCode = new HashCode();

foreach (var dbContextOptionsExtension in _extensionsMap)
{
hashCode.Add(dbContextOptionsExtension.Key);
hashCode.Add(dbContextOptionsExtension.Value.Info.GetServiceProviderHashCode());
}

return hashCode.ToHashCode();
}
}
}
18 changes: 5 additions & 13 deletions src/EFCore/DbContextOptions`.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@

using System;
using System.Collections.Generic;
using System.Linq;
using System.Collections.Immutable;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Utilities;

namespace Microsoft.EntityFrameworkCore
Expand All @@ -24,7 +25,7 @@ public class DbContextOptions<TContext> : DbContextOptions
/// to create instances of this class and it is not designed to be directly constructed in your application code.
/// </summary>
public DbContextOptions()
: base(new Dictionary<Type, IDbContextOptionsExtension>())
: this(ImmutableSortedDictionary.Create<Type, IDbContextOptionsExtension>(TypeFullNameComparer.Instance))
{
}

Expand All @@ -40,21 +41,12 @@ public DbContextOptions(
{
}

/// <summary>
/// Adds the given extension to the underlying options and creates a new
/// <see cref="DbContextOptions" /> with the extension added.
/// </summary>
/// <typeparam name="TExtension"> The type of extension to be added. </typeparam>
/// <param name="extension"> The extension to be added. </param>
/// <returns> The new options instance with the given extension added. </returns>
/// <inheritdoc />
public override DbContextOptions WithExtension<TExtension>(TExtension extension)
{
Check.NotNull(extension, nameof(extension));

var extensions = Extensions.ToDictionary(p => p.GetType(), p => p);
extensions[typeof(TExtension)] = extension;

return new DbContextOptions<TContext>(extensions);
return new DbContextOptions<TContext>(ExtensionsMap.SetItem(extension.GetType(), extension));
}

/// <summary>
Expand Down
Loading

0 comments on commit 7c1eec0

Please sign in to comment.