Browse Source

Remove compiled queries from the EF Core 2.x/3.x stores

pull/822/head
Kévin Chalet 7 years ago
parent
commit
47c04a238c
  1. 22
      src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreHelpers.cs
  2. 107
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs
  3. 207
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs
  4. 87
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictScopeStore.cs
  5. 262
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs

22
src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreHelpers.cs

@ -10,7 +10,6 @@ using System.Linq;
using System.Threading; using System.Threading;
using JetBrains.Annotations; using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Query;
using OpenIddict.EntityFrameworkCore; using OpenIddict.EntityFrameworkCore;
using OpenIddict.EntityFrameworkCore.Models; using OpenIddict.EntityFrameworkCore.Models;
@ -118,27 +117,6 @@ namespace Microsoft.EntityFrameworkCore
} }
#if !SUPPORTS_BCL_ASYNC_ENUMERABLE #if !SUPPORTS_BCL_ASYNC_ENUMERABLE
/// <summary>
/// Converts an EF Core/IX-based async enumeration to a BCL enumeration.
/// </summary>
/// <typeparam name="T">The type of the returned entities.</typeparam>
/// <param name="source">The EF Core/IX async enumeration.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The non-streamed async enumeration containing the results.</returns>
internal static IAsyncEnumerable<T> AsAsyncEnumerable<T>(
[NotNull] this AsyncEnumerable<T> source, CancellationToken cancellationToken = default)
{
return ExecuteAsync(cancellationToken);
async IAsyncEnumerable<T> ExecuteAsync(CancellationToken cancellationToken)
{
foreach (var element in await source.ToListAsync(cancellationToken))
{
yield return element;
}
}
}
/// <summary> /// <summary>
/// Executes the query and returns the results as a non-streamed async enumeration. /// Executes the query and returns the results as a non-streamed async enumeration.
/// </summary> /// </summary>

107
src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs

@ -16,7 +16,6 @@ using System.Threading.Tasks;
using JetBrains.Annotations; using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@ -268,15 +267,6 @@ namespace OpenIddict.EntityFrameworkCore
} }
} }
/// <summary>
/// Exposes a compiled query allowing to retrieve an application using its client identifier.
/// </summary>
private static readonly Func<TContext, string, Task<TApplication>> FindByClientId =
EF.CompileAsyncQuery((TContext context, string identifier) =>
(from application in context.Set<TApplication>().AsTracking()
where application.ClientId == identifier
select application).FirstOrDefault());
/// <summary> /// <summary>
/// Retrieves an application using its client identifier. /// Retrieves an application using its client identifier.
/// </summary> /// </summary>
@ -286,25 +276,18 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation,
/// whose result returns the client application corresponding to the identifier. /// whose result returns the client application corresponding to the identifier.
/// </returns> /// </returns>
public virtual ValueTask<TApplication> FindByClientIdAsync([NotNull] string identifier, CancellationToken cancellationToken) public virtual async ValueTask<TApplication> FindByClientIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
{ {
if (string.IsNullOrEmpty(identifier)) if (string.IsNullOrEmpty(identifier))
{ {
throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier)); throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
} }
return new ValueTask<TApplication>(FindByClientId(Context, identifier)); return await (from application in Applications.AsTracking()
where application.ClientId == identifier
select application).FirstOrDefaultAsync(cancellationToken);
} }
/// <summary>
/// Exposes a compiled query allowing to retrieve an application using its unique identifier.
/// </summary>
private static readonly Func<TContext, TKey, Task<TApplication>> FindById =
EF.CompileAsyncQuery((TContext context, TKey identifier) =>
(from application in context.Set<TApplication>().AsTracking()
where application.Id.Equals(identifier)
select application).FirstOrDefault());
/// <summary> /// <summary>
/// Retrieves an application using its unique identifier. /// Retrieves an application using its unique identifier.
/// </summary> /// </summary>
@ -314,37 +297,19 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation,
/// whose result returns the client application corresponding to the identifier. /// whose result returns the client application corresponding to the identifier.
/// </returns> /// </returns>
public virtual ValueTask<TApplication> FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken) public virtual async ValueTask<TApplication> FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
{ {
if (string.IsNullOrEmpty(identifier)) if (string.IsNullOrEmpty(identifier))
{ {
throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier)); throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
} }
return new ValueTask<TApplication>(FindById(Context, ConvertIdentifierFromString(identifier))); var key = ConvertIdentifierFromString(identifier);
}
/// <summary> return await (from application in Applications.AsTracking()
/// Exposes a compiled query allowing to retrieve all the applications where application.Id.Equals(key)
/// associated with the specified post_logout_redirect_uri. select application).FirstOrDefaultAsync(cancellationToken);
/// </summary> }
private static readonly
#if SUPPORTS_BCL_ASYNC_ENUMERABLE
Func<TContext, string, IAsyncEnumerable<TApplication>>
#else
Func<TContext, string, AsyncEnumerable<TApplication>>
#endif
FindByPostLogoutRedirectUri =
// To optimize the efficiency of the query a bit, only applications whose stringified
// PostLogoutRedirectUris contains the specified URL are returned. Once the applications
// are retrieved, a second pass is made to ensure only valid elements are returned.
// Implementers that use this query in a hot path may want to override this method
// to use SQL Server 2016 functions like JSON_VALUE to make the query more efficient.
EF.CompileAsyncQuery((TContext context, string address) =>
from application in context.Set<TApplication>().AsTracking()
where application.PostLogoutRedirectUris.Contains(address)
select application);
/// <summary> /// <summary>
/// Retrieves all the applications associated with the specified post_logout_redirect_uri. /// Retrieves all the applications associated with the specified post_logout_redirect_uri.
@ -360,35 +325,18 @@ namespace OpenIddict.EntityFrameworkCore
throw new ArgumentException("The address cannot be null or empty.", nameof(address)); throw new ArgumentException("The address cannot be null or empty.", nameof(address));
} }
return FindByPostLogoutRedirectUri(Context, address)
#if !SUPPORTS_BCL_ASYNC_ENUMERABLE
.AsAsyncEnumerable(cancellationToken)
#endif
.WhereAwait(async application => (await GetPostLogoutRedirectUrisAsync(application, cancellationToken))
.Contains(address, StringComparer.Ordinal));
}
/// <summary>
/// Exposes a compiled query allowing to retrieve all the
/// applications associated with the specified redirect_uri.
/// </summary>
private static readonly
#if SUPPORTS_BCL_ASYNC_ENUMERABLE
Func<TContext, string, IAsyncEnumerable<TApplication>>
#else
Func<TContext, string, AsyncEnumerable<TApplication>>
#endif
FindByRedirectUri =
// To optimize the efficiency of the query a bit, only applications whose stringified // To optimize the efficiency of the query a bit, only applications whose stringified
// RedirectUris property contains the specified URL are returned. Once the applications // PostLogoutRedirectUris contains the specified URL are returned. Once the applications
// are retrieved, a second pass is made to ensure only valid elements are returned. // are retrieved, a second pass is made to ensure only valid elements are returned.
// Implementers that use this query in a hot path may want to override this method // Implementers that use this method in a hot path may want to override this method
// to use SQL Server 2016 functions like JSON_VALUE to make the query more efficient. // to use SQL Server 2016 functions like JSON_VALUE to make the query more efficient.
EF.CompileAsyncQuery((TContext context, string address) => var applications = (from application in Applications.AsTracking()
from application in context.Set<TApplication>().AsTracking() where application.PostLogoutRedirectUris.Contains(address)
where application.RedirectUris.Contains(address) select application).AsAsyncEnumerable();
select application);
return applications.WhereAwait(async application =>
(await GetPostLogoutRedirectUrisAsync(application, cancellationToken)).Contains(address, StringComparer.Ordinal));
}
/// <summary> /// <summary>
/// Retrieves all the applications associated with the specified redirect_uri. /// Retrieves all the applications associated with the specified redirect_uri.
@ -404,12 +352,17 @@ namespace OpenIddict.EntityFrameworkCore
throw new ArgumentException("The address cannot be null or empty.", nameof(address)); throw new ArgumentException("The address cannot be null or empty.", nameof(address));
} }
return FindByRedirectUri(Context, address) // To optimize the efficiency of the query a bit, only applications whose stringified
#if !SUPPORTS_BCL_ASYNC_ENUMERABLE // RedirectUris property contains the specified URL are returned. Once the applications
.AsAsyncEnumerable(cancellationToken) // are retrieved, a second pass is made to ensure only valid elements are returned.
#endif // Implementers that use this method in a hot path may want to override this method
.WhereAwait(async application => (await GetRedirectUrisAsync(application, cancellationToken)) // to use SQL Server 2016 functions like JSON_VALUE to make the query more efficient.
.Contains(address, StringComparer.Ordinal)); var applications = (from application in Applications.AsTracking()
where application.RedirectUris.Contains(address)
select application).AsAsyncEnumerable();
return applications.WhereAwait(async application =>
(await GetRedirectUrisAsync(application, cancellationToken)).Contains(address, StringComparer.Ordinal));
} }
/// <summary> /// <summary>

207
src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs

@ -16,7 +16,6 @@ using System.Threading.Tasks;
using JetBrains.Annotations; using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@ -244,31 +243,6 @@ namespace OpenIddict.EntityFrameworkCore
} }
} }
/// <summary>
/// Exposes a compiled query allowing to retrieve the authorizations corresponding
/// to the specified subject and associated with the application identifier.
/// </summary>
private static readonly
#if SUPPORTS_BCL_ASYNC_ENUMERABLE
Func<TContext, TKey, string, IAsyncEnumerable<TAuthorization>>
#else
Func<TContext, TKey, string, AsyncEnumerable<TAuthorization>>
#endif
FindBySubjectAndClient =
// Note: due to a bug in Entity Framework Core's query visitor, the authorizations can't be
// filtered using authorization.Application.Id.Equals(key). To work around this issue,
// this compiled query uses an explicit join before applying the equality check.
// See https://github.com/openiddict/openiddict-core/issues/499 for more information.
EF.CompileAsyncQuery((TContext context, TKey identifier, string subject) =>
from authorization in context.Set<TAuthorization>()
.Include(authorization => authorization.Application)
.AsTracking()
where authorization.Subject == subject
join application in context.Set<TApplication>().AsTracking() on authorization.Application.Id equals application.Id
where application.Id.Equals(identifier)
select authorization);
/// <summary> /// <summary>
/// Retrieves the authorizations corresponding to the specified /// Retrieves the authorizations corresponding to the specified
/// subject and associated with the application identifier. /// subject and associated with the application identifier.
@ -290,36 +264,19 @@ namespace OpenIddict.EntityFrameworkCore
throw new ArgumentException("The client cannot be null or empty.", nameof(client)); throw new ArgumentException("The client cannot be null or empty.", nameof(client));
} }
return FindBySubjectAndClient(Context, ConvertIdentifierFromString(client), subject)
#if !SUPPORTS_BCL_ASYNC_ENUMERABLE
.AsAsyncEnumerable(cancellationToken)
#endif
;
}
/// <summary>
/// Exposes a compiled query allowing to retrieve the authorizations matching the specified parameters.
/// </summary>
private static readonly
#if SUPPORTS_BCL_ASYNC_ENUMERABLE
Func<TContext, TKey, string, string, IAsyncEnumerable<TAuthorization>>
#else
Func<TContext, TKey, string, string, AsyncEnumerable<TAuthorization>>
#endif
FindBySubjectClientAndStatus =
// Note: due to a bug in Entity Framework Core's query visitor, the authorizations can't be // Note: due to a bug in Entity Framework Core's query visitor, the authorizations can't be
// filtered using authorization.Application.Id.Equals(key). To work around this issue, // filtered using authorization.Application.Id.Equals(key). To work around this issue,
// this compiled query uses an explicit join before applying the equality check. // this method is overriden to use an explicit join before applying the equality check.
// See https://github.com/openiddict/openiddict-core/issues/499 for more information. // See https://github.com/openiddict/openiddict-core/issues/499 for more information.
EF.CompileAsyncQuery((TContext context, TKey identifier, string subject, string status) =>
from authorization in context.Set<TAuthorization>() var key = ConvertIdentifierFromString(client);
.Include(authorization => authorization.Application)
.AsTracking() return (from authorization in Authorizations.Include(authorization => authorization.Application).AsTracking()
where authorization.Subject == subject && authorization.Status == status where authorization.Subject == subject
join application in context.Set<TApplication>().AsTracking() on authorization.Application.Id equals application.Id join application in Applications.AsTracking() on authorization.Application.Id equals application.Id
where application.Id.Equals(identifier) where application.Id.Equals(key)
select authorization); select authorization).AsAsyncEnumerable();
}
/// <summary> /// <summary>
/// Retrieves the authorizations matching the specified parameters. /// Retrieves the authorizations matching the specified parameters.
@ -348,38 +305,19 @@ namespace OpenIddict.EntityFrameworkCore
throw new ArgumentException("The status cannot be null or empty.", nameof(status)); throw new ArgumentException("The status cannot be null or empty.", nameof(status));
} }
return FindBySubjectClientAndStatus(Context, ConvertIdentifierFromString(client), subject, status)
#if !SUPPORTS_BCL_ASYNC_ENUMERABLE
.AsAsyncEnumerable(cancellationToken)
#endif
;
}
/// <summary>
/// Exposes a compiled query allowing to retrieve the authorizations matching the specified parameters.
/// </summary>
private static readonly
#if SUPPORTS_BCL_ASYNC_ENUMERABLE
Func<TContext, TKey, string, string, string, IAsyncEnumerable<TAuthorization>>
#else
Func<TContext, TKey, string, string, string, AsyncEnumerable<TAuthorization>>
#endif
FindBySubjectClientStatusAndType =
// Note: due to a bug in Entity Framework Core's query visitor, the authorizations can't be // Note: due to a bug in Entity Framework Core's query visitor, the authorizations can't be
// filtered using authorization.Application.Id.Equals(key). To work around this issue, // filtered using authorization.Application.Id.Equals(key). To work around this issue,
// this compiled query uses an explicit join before applying the equality check. // this method is overriden to use an explicit join before applying the equality check.
// See https://github.com/openiddict/openiddict-core/issues/499 for more information. // See https://github.com/openiddict/openiddict-core/issues/499 for more information.
EF.CompileAsyncQuery((TContext context, TKey identifier, string subject, string status, string type) =>
from authorization in context.Set<TAuthorization>() var key = ConvertIdentifierFromString(client);
.Include(authorization => authorization.Application)
.AsTracking() return (from authorization in Authorizations.Include(authorization => authorization.Application).AsTracking()
where authorization.Subject == subject && where authorization.Subject == subject && authorization.Status == status
authorization.Status == status && join application in Applications.AsTracking() on authorization.Application.Id equals application.Id
authorization.Type == type where application.Id.Equals(key)
join application in context.Set<TApplication>().AsTracking() on authorization.Application.Id equals application.Id select authorization).AsAsyncEnumerable();
where application.Id.Equals(identifier) }
select authorization);
/// <summary> /// <summary>
/// Retrieves the authorizations matching the specified parameters. /// Retrieves the authorizations matching the specified parameters.
@ -414,11 +352,20 @@ namespace OpenIddict.EntityFrameworkCore
throw new ArgumentException("The type cannot be null or empty.", nameof(type)); throw new ArgumentException("The type cannot be null or empty.", nameof(type));
} }
return FindBySubjectClientStatusAndType(Context, ConvertIdentifierFromString(client), subject, status, type) // Note: due to a bug in Entity Framework Core's query visitor, the authorizations can't be
#if !SUPPORTS_BCL_ASYNC_ENUMERABLE // filtered using authorization.Application.Id.Equals(key). To work around this issue,
.AsAsyncEnumerable(cancellationToken) // this method is overriden to use an explicit join before applying the equality check.
#endif // See https://github.com/openiddict/openiddict-core/issues/499 for more information.
;
var key = ConvertIdentifierFromString(client);
return (from authorization in Authorizations.Include(authorization => authorization.Application).AsTracking()
where authorization.Subject == subject &&
authorization.Status == status &&
authorization.Type == type
join application in Applications.AsTracking() on authorization.Application.Id equals application.Id
where application.Id.Equals(key)
select authorization).AsAsyncEnumerable();
} }
/// <summary> /// <summary>
@ -439,30 +386,6 @@ namespace OpenIddict.EntityFrameworkCore
.WhereAwait(async authorization => new HashSet<string>( .WhereAwait(async authorization => new HashSet<string>(
await GetScopesAsync(authorization, cancellationToken), StringComparer.Ordinal).IsSupersetOf(scopes)); await GetScopesAsync(authorization, cancellationToken), StringComparer.Ordinal).IsSupersetOf(scopes));
/// <summary>
/// Exposes a compiled query allowing to retrieve the list of
/// authorizations corresponding to the specified application identifier.
/// </summary>
private static readonly
#if SUPPORTS_BCL_ASYNC_ENUMERABLE
Func<TContext, TKey, IAsyncEnumerable<TAuthorization>>
#else
Func<TContext, TKey, AsyncEnumerable<TAuthorization>>
#endif
FindByApplicationId =
// Note: due to a bug in Entity Framework Core's query visitor, the authorizations can't be
// filtered using authorization.Application.Id.Equals(key). To work around this issue,
// this compiled query uses an explicit join before applying the equality check.
// See https://github.com/openiddict/openiddict-core/issues/499 for more information.
EF.CompileAsyncQuery((TContext context, TKey identifier) =>
from authorization in context.Set<TAuthorization>()
.Include(authorization => authorization.Application)
.AsTracking()
join application in context.Set<TApplication>().AsTracking() on authorization.Application.Id equals application.Id
where application.Id.Equals(identifier)
select authorization);
/// <summary> /// <summary>
/// Retrieves the list of authorizations corresponding to the specified application identifier. /// Retrieves the list of authorizations corresponding to the specified application identifier.
/// </summary> /// </summary>
@ -477,23 +400,18 @@ namespace OpenIddict.EntityFrameworkCore
throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier)); throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
} }
return FindByApplicationId(Context, ConvertIdentifierFromString(identifier)) // Note: due to a bug in Entity Framework Core's query visitor, the authorizations can't be
#if !SUPPORTS_BCL_ASYNC_ENUMERABLE // filtered using authorization.Application.Id.Equals(key). To work around this issue,
.AsAsyncEnumerable(cancellationToken) // this method is overriden to use an explicit join before applying the equality check.
#endif // See https://github.com/openiddict/openiddict-core/issues/499 for more information.
;
}
/// <summary> var key = ConvertIdentifierFromString(identifier);
/// Exposes a compiled query allowing to retrieve an authorization using its unique identifier.
/// </summary> return (from authorization in Authorizations.Include(authorization => authorization.Application).AsTracking()
private static readonly Func<TContext, TKey, Task<TAuthorization>> FindById = join application in Applications.AsTracking() on authorization.Application.Id equals application.Id
EF.CompileAsyncQuery((TContext context, TKey identifier) => where application.Id.Equals(identifier)
(from authorization in context.Set<TAuthorization>() select authorization).AsAsyncEnumerable();
.Include(authorization => authorization.Application) }
.AsTracking()
where authorization.Id.Equals(identifier)
select authorization).FirstOrDefault());
/// <summary> /// <summary>
/// Retrieves an authorization using its unique identifier. /// Retrieves an authorization using its unique identifier.
@ -501,34 +419,19 @@ namespace OpenIddict.EntityFrameworkCore
/// <param name="identifier">The unique identifier associated with the authorization.</param> /// <param name="identifier">The unique identifier associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The authorization corresponding to the identifier.</returns> /// <returns>The authorization corresponding to the identifier.</returns>
public virtual ValueTask<TAuthorization> FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken) public virtual async ValueTask<TAuthorization> FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
{ {
if (string.IsNullOrEmpty(identifier)) if (string.IsNullOrEmpty(identifier))
{ {
throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier)); throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
} }
return new ValueTask<TAuthorization>(FindById(Context, ConvertIdentifierFromString(identifier))); var key = ConvertIdentifierFromString(identifier);
}
/// <summary> return await (from authorization in Authorizations.Include(authorization => authorization.Application).AsTracking()
/// Exposes a compiled query allowing to retrieve all the where authorization.Id.Equals(key)
/// authorizations corresponding to the specified subject. select authorization).FirstOrDefaultAsync(cancellationToken);
/// </summary> }
private static readonly
#if SUPPORTS_BCL_ASYNC_ENUMERABLE
Func<TContext, string, IAsyncEnumerable<TAuthorization>>
#else
Func<TContext, string, AsyncEnumerable<TAuthorization>>
#endif
FindBySubject =
EF.CompileAsyncQuery((TContext context, string subject) =>
from authorization in context.Set<TAuthorization>()
.Include(authorization => authorization.Application)
.AsTracking()
where authorization.Subject == subject
select authorization);
/// <summary> /// <summary>
/// Retrieves the subject associated with an authorization. /// Retrieves the subject associated with an authorization.
@ -544,11 +447,9 @@ namespace OpenIddict.EntityFrameworkCore
throw new ArgumentException("The subject cannot be null or empty.", nameof(subject)); throw new ArgumentException("The subject cannot be null or empty.", nameof(subject));
} }
return FindBySubject(Context, subject) return (from authorization in Authorizations.Include(authorization => authorization.Application).AsTracking()
#if !SUPPORTS_BCL_ASYNC_ENUMERABLE where authorization.Subject == subject
.AsAsyncEnumerable(cancellationToken) select authorization).AsAsyncEnumerable();
#endif
;
} }
/// <summary> /// <summary>

87
src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictScopeStore.cs

@ -14,7 +14,6 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using JetBrains.Annotations; using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -177,15 +176,6 @@ namespace OpenIddict.EntityFrameworkCore
} }
} }
/// <summary>
/// Exposes a compiled query allowing to retrieve a scope using its unique identifier.
/// </summary>
private static readonly Func<TContext, TKey, Task<TScope>> FindById =
EF.CompileAsyncQuery((TContext context, TKey identifier) =>
(from scope in context.Set<TScope>().AsTracking()
where scope.Id.Equals(identifier)
select scope).FirstOrDefault());
/// <summary> /// <summary>
/// Retrieves a scope using its unique identifier. /// Retrieves a scope using its unique identifier.
/// </summary> /// </summary>
@ -195,24 +185,19 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation,
/// whose result returns the scope corresponding to the identifier. /// whose result returns the scope corresponding to the identifier.
/// </returns> /// </returns>
public virtual ValueTask<TScope> FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken) public virtual async ValueTask<TScope> FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
{ {
if (string.IsNullOrEmpty(identifier)) if (string.IsNullOrEmpty(identifier))
{ {
throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier)); throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
} }
return new ValueTask<TScope>(FindById(Context, ConvertIdentifierFromString(identifier))); var key = ConvertIdentifierFromString(identifier);
}
/// <summary> return await (from scope in Scopes.AsTracking()
/// Exposes a compiled query allowing to retrieve a scope using its name. where scope.Id.Equals(key)
/// </summary> select scope).FirstOrDefaultAsync(cancellationToken);
private static readonly Func<TContext, string, Task<TScope>> FindByName = }
EF.CompileAsyncQuery((TContext context, string name) =>
(from scope in context.Set<TScope>().AsTracking()
where scope.Name == name
select scope).FirstOrDefault());
/// <summary> /// <summary>
/// Retrieves a scope using its name. /// Retrieves a scope using its name.
@ -223,30 +208,18 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation,
/// whose result returns the scope corresponding to the specified name. /// whose result returns the scope corresponding to the specified name.
/// </returns> /// </returns>
public virtual ValueTask<TScope> FindByNameAsync([NotNull] string name, CancellationToken cancellationToken) public virtual async ValueTask<TScope> FindByNameAsync([NotNull] string name, CancellationToken cancellationToken)
{ {
if (string.IsNullOrEmpty(name)) if (string.IsNullOrEmpty(name))
{ {
throw new ArgumentException("The scope name cannot be null or empty.", nameof(name)); throw new ArgumentException("The scope name cannot be null or empty.", nameof(name));
} }
return new ValueTask<TScope>(FindByName(Context, name)); return await (from scope in Scopes.AsTracking()
where scope.Name == name
select scope).FirstOrDefaultAsync(cancellationToken);
} }
/// <summary>
/// Exposes a compiled query allowing to retrieve a list of scopes using their name.
/// </summary>
private static readonly
#if SUPPORTS_BCL_ASYNC_ENUMERABLE
Func<TContext, ImmutableArray<string>, IAsyncEnumerable<TScope>>
#else
Func<TContext, ImmutableArray<string>, AsyncEnumerable<TScope>>
#endif
FindByNames = EF.CompileAsyncQuery((TContext context, ImmutableArray<string> names) =>
from scope in context.Set<TScope>().AsTracking()
where names.Contains(scope.Name)
select scope);
/// <summary> /// <summary>
/// Retrieves a list of scopes using their name. /// Retrieves a list of scopes using their name.
/// </summary> /// </summary>
@ -261,34 +234,11 @@ namespace OpenIddict.EntityFrameworkCore
throw new ArgumentException("Scope names cannot be null or empty.", nameof(names)); throw new ArgumentException("Scope names cannot be null or empty.", nameof(names));
} }
return FindByNames(Context, names) return (from scope in Scopes.AsTracking()
#if !SUPPORTS_BCL_ASYNC_ENUMERABLE where names.Contains(scope.Name)
.AsAsyncEnumerable(cancellationToken) select scope).AsAsyncEnumerable();
#endif
;
} }
/// <summary>
/// Exposes a compiled query allowing to retrieve all the scopes that contain the specified resource.
/// </summary>
private static readonly
#if SUPPORTS_BCL_ASYNC_ENUMERABLE
Func<TContext, string, IAsyncEnumerable<TScope>>
#else
Func<TContext, string, AsyncEnumerable<TScope>>
#endif
FindByResource =
// To optimize the efficiency of the query a bit, only scopes whose stringified
// Resources column contains the specified resource are returned. Once the scopes
// are retrieved, a second pass is made to ensure only valid elements are returned.
// Implementers that use this query in a hot path may want to override this method
// to use SQL Server 2016 functions like JSON_VALUE to make the query more efficient.
EF.CompileAsyncQuery((TContext context, string resource) =>
from scope in context.Set<TScope>().AsTracking()
where scope.Resources.Contains(resource)
select scope);
/// <summary> /// <summary>
/// Retrieves all the scopes that contain the specified resource. /// Retrieves all the scopes that contain the specified resource.
/// </summary> /// </summary>
@ -303,11 +253,12 @@ namespace OpenIddict.EntityFrameworkCore
throw new ArgumentException("The resource cannot be null or empty.", nameof(resource)); throw new ArgumentException("The resource cannot be null or empty.", nameof(resource));
} }
return FindByResource(Context, resource) var scopes = (from scope in Scopes.AsTracking()
#if !SUPPORTS_BCL_ASYNC_ENUMERABLE where scope.Resources.Contains(resource)
.AsAsyncEnumerable(cancellationToken) select scope).AsAsyncEnumerable();
#endif
.WhereAwait(async scope => (await GetResourcesAsync(scope, cancellationToken)).Contains(resource, StringComparer.Ordinal)); return scopes.WhereAwait(async scope =>
(await GetResourcesAsync(scope, cancellationToken)).Contains(resource, StringComparer.Ordinal));
} }
/// <summary> /// <summary>

262
src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs

@ -15,7 +15,6 @@ using System.Threading.Tasks;
using JetBrains.Annotations; using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@ -197,32 +196,6 @@ namespace OpenIddict.EntityFrameworkCore
} }
} }
/// <summary>
/// Exposes a compiled query allowing to retrieve the tokens corresponding
/// to the specified subject and associated with the application identifier.
/// </summary>
private static readonly
#if SUPPORTS_BCL_ASYNC_ENUMERABLE
Func<TContext, TKey, string, IAsyncEnumerable<TToken>>
#else
Func<TContext, TKey, string, AsyncEnumerable<TToken>>
#endif
FindBySubjectAndClient =
// Note: due to a bug in Entity Framework Core's query visitor, the authorizations can't be
// filtered using token.Application.Id.Equals(key). To work around this issue,
// this compiled query uses an explicit join before applying the equality check.
// See https://github.com/openiddict/openiddict-core/issues/499 for more information.
EF.CompileAsyncQuery((TContext context, TKey identifier, string subject) =>
from token in context.Set<TToken>()
.Include(token => token.Application)
.Include(token => token.Authorization)
.AsTracking()
where token.Subject == subject
join application in context.Set<TApplication>().AsTracking() on token.Application.Id equals application.Id
where application.Id.Equals(identifier)
select token);
/// <summary> /// <summary>
/// Retrieves the tokens corresponding to the specified /// Retrieves the tokens corresponding to the specified
/// subject and associated with the application identifier. /// subject and associated with the application identifier.
@ -244,37 +217,19 @@ namespace OpenIddict.EntityFrameworkCore
throw new ArgumentException("The client cannot be null or empty.", nameof(client)); throw new ArgumentException("The client cannot be null or empty.", nameof(client));
} }
return FindBySubjectAndClient(Context, ConvertIdentifierFromString(client), subject)
#if !SUPPORTS_BCL_ASYNC_ENUMERABLE
.AsAsyncEnumerable(cancellationToken)
#endif
;
}
/// <summary>
/// Exposes a compiled query allowing to retrieve the tokens matching the specified parameters.
/// </summary>
private static readonly
#if SUPPORTS_BCL_ASYNC_ENUMERABLE
Func<TContext, TKey, string, string, IAsyncEnumerable<TToken>>
#else
Func<TContext, TKey, string, string, AsyncEnumerable<TToken>>
#endif
FindBySubjectClientAndStatus =
// Note: due to a bug in Entity Framework Core's query visitor, the authorizations can't be // Note: due to a bug in Entity Framework Core's query visitor, the authorizations can't be
// filtered using token.Application.Id.Equals(key). To work around this issue, // filtered using token.Application.Id.Equals(key). To work around this issue,
// this compiled query uses an explicit join before applying the equality check. // this compiled query uses an explicit join before applying the equality check.
// See https://github.com/openiddict/openiddict-core/issues/499 for more information. // See https://github.com/openiddict/openiddict-core/issues/499 for more information.
EF.CompileAsyncQuery((TContext context, TKey identifier, string subject, string status) =>
from token in context.Set<TToken>() var key = ConvertIdentifierFromString(client);
.Include(token => token.Application)
.Include(token => token.Authorization) return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking()
.AsTracking() where token.Subject == subject
where token.Subject == subject && token.Status == status join application in Applications.AsTracking() on token.Application.Id equals application.Id
join application in context.Set<TApplication>().AsTracking() on token.Application.Id equals application.Id where application.Id.Equals(key)
where application.Id.Equals(identifier) select token).AsAsyncEnumerable();
select token); }
/// <summary> /// <summary>
/// Retrieves the tokens matching the specified parameters. /// Retrieves the tokens matching the specified parameters.
@ -303,39 +258,20 @@ namespace OpenIddict.EntityFrameworkCore
throw new ArgumentException("The status cannot be null or empty.", nameof(status)); throw new ArgumentException("The status cannot be null or empty.", nameof(status));
} }
return FindBySubjectClientAndStatus(Context, ConvertIdentifierFromString(client), subject, status)
#if !SUPPORTS_BCL_ASYNC_ENUMERABLE
.AsAsyncEnumerable(cancellationToken)
#endif
;
}
/// <summary>
/// Exposes a compiled query allowing to retrieve the tokens matching the specified parameters.
/// </summary>
private static readonly
#if SUPPORTS_BCL_ASYNC_ENUMERABLE
Func<TContext, TKey, string, string, string, IAsyncEnumerable<TToken>>
#else
Func<TContext, TKey, string, string, string, AsyncEnumerable<TToken>>
#endif
FindBySubjectClientStatusAndType =
// Note: due to a bug in Entity Framework Core's query visitor, the authorizations can't be // Note: due to a bug in Entity Framework Core's query visitor, the authorizations can't be
// filtered using token.Application.Id.Equals(key). To work around this issue, // filtered using token.Application.Id.Equals(key). To work around this issue,
// this compiled query uses an explicit join before applying the equality check. // this compiled query uses an explicit join before applying the equality check.
// See https://github.com/openiddict/openiddict-core/issues/499 for more information. // See https://github.com/openiddict/openiddict-core/issues/499 for more information.
EF.CompileAsyncQuery((TContext context, TKey identifier, string subject, string status, string type) =>
from token in context.Set<TToken>() var key = ConvertIdentifierFromString(client);
.Include(token => token.Application)
.Include(token => token.Authorization) return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking()
.AsTracking() where token.Subject == subject &&
where token.Subject == subject && token.Status == status
token.Status == status && join application in Applications.AsTracking() on token.Application.Id equals application.Id
token.Type == type where application.Id.Equals(key)
join application in context.Set<TApplication>().AsTracking() on token.Application.Id equals application.Id select token).AsAsyncEnumerable();
where application.Id.Equals(identifier) }
select token);
/// <summary> /// <summary>
/// Retrieves the tokens matching the specified parameters. /// Retrieves the tokens matching the specified parameters.
@ -370,37 +306,21 @@ namespace OpenIddict.EntityFrameworkCore
throw new ArgumentException("The type cannot be null or empty.", nameof(type)); throw new ArgumentException("The type cannot be null or empty.", nameof(type));
} }
return FindBySubjectClientStatusAndType(Context, ConvertIdentifierFromString(client), subject, status, type) // Note: due to a bug in Entity Framework Core's query visitor, the authorizations can't be
#if !SUPPORTS_BCL_ASYNC_ENUMERABLE
.AsAsyncEnumerable(cancellationToken)
#endif
;
}
/// <summary>
/// Exposes a compiled query allowing to retrieve the list of
/// tokens corresponding to the specified application identifier.
/// </summary>
private static readonly
#if SUPPORTS_BCL_ASYNC_ENUMERABLE
Func<TContext, TKey, IAsyncEnumerable<TToken>>
#else
Func<TContext, TKey, AsyncEnumerable<TToken>>
#endif
FindByApplicationId =
// Note: due to a bug in Entity Framework Core's query visitor, the tokens can't be
// filtered using token.Application.Id.Equals(key). To work around this issue, // filtered using token.Application.Id.Equals(key). To work around this issue,
// this compiled query uses an explicit join before applying the equality check. // this compiled query uses an explicit join before applying the equality check.
// See https://github.com/openiddict/openiddict-core/issues/499 for more information. // See https://github.com/openiddict/openiddict-core/issues/499 for more information.
EF.CompileAsyncQuery((TContext context, TKey identifier) =>
from token in context.Set<TToken>() var key = ConvertIdentifierFromString(client);
.Include(token => token.Application)
.Include(token => token.Authorization) return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking()
.AsTracking() where token.Subject == subject &&
join application in context.Set<TApplication>().AsTracking() on token.Application.Id equals application.Id token.Status == status &&
where application.Id.Equals(identifier) token.Type == type
select token); join application in Applications.AsTracking() on token.Application.Id equals application.Id
where application.Id.Equals(key)
select token).AsAsyncEnumerable();
}
/// <summary> /// <summary>
/// Retrieves the list of tokens corresponding to the specified application identifier. /// Retrieves the list of tokens corresponding to the specified application identifier.
@ -415,37 +335,18 @@ namespace OpenIddict.EntityFrameworkCore
throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier)); throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
} }
return FindByApplicationId(Context, ConvertIdentifierFromString(identifier))
#if !SUPPORTS_BCL_ASYNC_ENUMERABLE
.AsAsyncEnumerable(cancellationToken)
#endif
;
}
/// <summary>
/// Exposes a compiled query allowing to retrieve the list of
/// tokens corresponding to the specified authorization identifier.
/// </summary>
private static readonly
#if SUPPORTS_BCL_ASYNC_ENUMERABLE
Func<TContext, TKey, IAsyncEnumerable<TToken>>
#else
Func<TContext, TKey, AsyncEnumerable<TToken>>
#endif
FindByAuthorizationId =
// Note: due to a bug in Entity Framework Core's query visitor, the tokens can't be // Note: due to a bug in Entity Framework Core's query visitor, the tokens can't be
// filtered using token.Authorization.Id.Equals(key). To work around this issue, // filtered using token.Application.Id.Equals(key). To work around this issue,
// this compiled query uses an explicit join before applying the equality check. // this method is overriden to use an explicit join before applying the equality check.
// See https://github.com/openiddict/openiddict-core/issues/499 for more information. // See https://github.com/openiddict/openiddict-core/issues/499 for more information.
EF.CompileAsyncQuery((TContext context, TKey identifier) =>
from token in context.Set<TToken>() var key = ConvertIdentifierFromString(identifier);
.Include(token => token.Application)
.Include(token => token.Authorization) return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking()
.AsTracking() join application in Applications.AsTracking() on token.Application.Id equals application.Id
join authorization in context.Set<TAuthorization>().AsTracking() on token.Authorization.Id equals authorization.Id where application.Id.Equals(key)
where authorization.Id.Equals(identifier) select token).AsAsyncEnumerable();
select token); }
/// <summary> /// <summary>
/// Retrieves the list of tokens corresponding to the specified authorization identifier. /// Retrieves the list of tokens corresponding to the specified authorization identifier.
@ -460,24 +361,18 @@ namespace OpenIddict.EntityFrameworkCore
throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier)); throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
} }
return FindByAuthorizationId(Context, ConvertIdentifierFromString(identifier)) // Note: due to a bug in Entity Framework Core's query visitor, the tokens can't be
#if !SUPPORTS_BCL_ASYNC_ENUMERABLE // filtered using token.Authorization.Id.Equals(key). To work around this issue,
.AsAsyncEnumerable(cancellationToken) // this method is overriden to use an explicit join before applying the equality check.
#endif // See https://github.com/openiddict/openiddict-core/issues/499 for more information.
;
}
/// <summary> var key = ConvertIdentifierFromString(identifier);
/// Exposes a compiled query allowing to retrieve a token using its unique identifier.
/// </summary> return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking()
private static readonly Func<TContext, TKey, Task<TToken>> FindById = join authorization in Authorizations.AsTracking() on token.Authorization.Id equals authorization.Id
EF.CompileAsyncQuery((TContext context, TKey identifier) => where authorization.Id.Equals(key)
(from token in context.Set<TToken>() select token).AsAsyncEnumerable();
.Include(token => token.Application) }
.Include(token => token.Authorization)
.AsTracking()
where token.Id.Equals(identifier)
select token).FirstOrDefault());
/// <summary> /// <summary>
/// Retrieves a token using its unique identifier. /// Retrieves a token using its unique identifier.
@ -488,28 +383,19 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation,
/// whose result returns the token corresponding to the unique identifier. /// whose result returns the token corresponding to the unique identifier.
/// </returns> /// </returns>
public virtual ValueTask<TToken> FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken) public virtual async ValueTask<TToken> FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
{ {
if (string.IsNullOrEmpty(identifier)) if (string.IsNullOrEmpty(identifier))
{ {
throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier)); throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
} }
return new ValueTask<TToken>(FindById(Context, ConvertIdentifierFromString(identifier))); var key = ConvertIdentifierFromString(identifier);
}
/// <summary> return await (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking()
/// Exposes a compiled query allowing to retrieve the list of where token.Id.Equals(key)
/// tokens corresponding to the specified reference identifier. select token).FirstOrDefaultAsync(cancellationToken);
/// </summary> }
private static readonly Func<TContext, string, Task<TToken>> FindByReferenceId =
EF.CompileAsyncQuery((TContext context, string identifier) =>
(from token in context.Set<TToken>()
.Include(token => token.Application)
.Include(token => token.Authorization)
.AsTracking()
where token.ReferenceId == identifier
select token).FirstOrDefault());
/// <summary> /// <summary>
/// Retrieves the list of tokens corresponding to the specified reference identifier. /// Retrieves the list of tokens corresponding to the specified reference identifier.
@ -521,34 +407,18 @@ namespace OpenIddict.EntityFrameworkCore
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation, /// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation,
/// whose result returns the tokens corresponding to the specified reference identifier. /// whose result returns the tokens corresponding to the specified reference identifier.
/// </returns> /// </returns>
public virtual ValueTask<TToken> FindByReferenceIdAsync([NotNull] string identifier, CancellationToken cancellationToken) public virtual async ValueTask<TToken> FindByReferenceIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
{ {
if (string.IsNullOrEmpty(identifier)) if (string.IsNullOrEmpty(identifier))
{ {
throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier)); throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
} }
return new ValueTask<TToken>(FindByReferenceId(Context, identifier)); return await (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking()
where token.ReferenceId == identifier
select token).FirstOrDefaultAsync(cancellationToken);
} }
/// <summary>
/// Exposes a compiled query allowing to retrieve the
/// list of tokens corresponding to the specified subject.
/// </summary>
private static readonly
#if SUPPORTS_BCL_ASYNC_ENUMERABLE
Func<TContext, string, IAsyncEnumerable<TToken>>
#else
Func<TContext, string, AsyncEnumerable<TToken>>
#endif
FindBySubject = EF.CompileAsyncQuery((TContext context, string subject) =>
from token in context.Set<TToken>()
.Include(token => token.Application)
.Include(token => token.Authorization)
.AsTracking()
where token.Subject == subject
select token);
/// <summary> /// <summary>
/// Retrieves the list of tokens corresponding to the specified subject. /// Retrieves the list of tokens corresponding to the specified subject.
/// </summary> /// </summary>
@ -562,11 +432,9 @@ namespace OpenIddict.EntityFrameworkCore
throw new ArgumentException("The subject cannot be null or empty.", nameof(subject)); throw new ArgumentException("The subject cannot be null or empty.", nameof(subject));
} }
return FindBySubject(Context, subject) return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking()
#if !SUPPORTS_BCL_ASYNC_ENUMERABLE where token.Subject == subject
.AsAsyncEnumerable(cancellationToken) select token).AsAsyncEnumerable();
#endif
;
} }
/// <summary> /// <summary>

Loading…
Cancel
Save