From 47c04a238cc658b973df050be0051038f2469778 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Sat, 5 Oct 2019 18:59:28 +0200 Subject: [PATCH] Remove compiled queries from the EF Core 2.x/3.x stores --- .../OpenIddictEntityFrameworkCoreHelpers.cs | 22 -- .../Stores/OpenIddictApplicationStore.cs | 107 ++----- .../Stores/OpenIddictAuthorizationStore.cs | 207 ++++---------- .../Stores/OpenIddictScopeStore.cs | 87 ++---- .../Stores/OpenIddictTokenStore.cs | 262 +++++------------- 5 files changed, 168 insertions(+), 517 deletions(-) diff --git a/src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreHelpers.cs b/src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreHelpers.cs index ba00bb4d..bc1bc93e 100644 --- a/src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreHelpers.cs +++ b/src/OpenIddict.EntityFrameworkCore/OpenIddictEntityFrameworkCoreHelpers.cs @@ -10,7 +10,6 @@ using System.Linq; using System.Threading; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Query; using OpenIddict.EntityFrameworkCore; using OpenIddict.EntityFrameworkCore.Models; @@ -118,27 +117,6 @@ namespace Microsoft.EntityFrameworkCore } #if !SUPPORTS_BCL_ASYNC_ENUMERABLE - /// - /// Converts an EF Core/IX-based async enumeration to a BCL enumeration. - /// - /// The type of the returned entities. - /// The EF Core/IX async enumeration. - /// The that can be used to abort the operation. - /// The non-streamed async enumeration containing the results. - internal static IAsyncEnumerable AsAsyncEnumerable( - [NotNull] this AsyncEnumerable source, CancellationToken cancellationToken = default) - { - return ExecuteAsync(cancellationToken); - - async IAsyncEnumerable ExecuteAsync(CancellationToken cancellationToken) - { - foreach (var element in await source.ToListAsync(cancellationToken)) - { - yield return element; - } - } - } - /// /// Executes the query and returns the results as a non-streamed async enumeration. /// diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs index c0ba3477..fcc8e467 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs @@ -16,7 +16,6 @@ using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Options; @@ -268,15 +267,6 @@ namespace OpenIddict.EntityFrameworkCore } } - /// - /// Exposes a compiled query allowing to retrieve an application using its client identifier. - /// - private static readonly Func> FindByClientId = - EF.CompileAsyncQuery((TContext context, string identifier) => - (from application in context.Set().AsTracking() - where application.ClientId == identifier - select application).FirstOrDefault()); - /// /// Retrieves an application using its client identifier. /// @@ -286,25 +276,18 @@ namespace OpenIddict.EntityFrameworkCore /// A that can be used to monitor the asynchronous operation, /// whose result returns the client application corresponding to the identifier. /// - public virtual ValueTask FindByClientIdAsync([NotNull] string identifier, CancellationToken cancellationToken) + public virtual async ValueTask FindByClientIdAsync([NotNull] string identifier, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(identifier)) { throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier)); } - return new ValueTask(FindByClientId(Context, identifier)); + return await (from application in Applications.AsTracking() + where application.ClientId == identifier + select application).FirstOrDefaultAsync(cancellationToken); } - /// - /// Exposes a compiled query allowing to retrieve an application using its unique identifier. - /// - private static readonly Func> FindById = - EF.CompileAsyncQuery((TContext context, TKey identifier) => - (from application in context.Set().AsTracking() - where application.Id.Equals(identifier) - select application).FirstOrDefault()); - /// /// Retrieves an application using its unique identifier. /// @@ -314,37 +297,19 @@ namespace OpenIddict.EntityFrameworkCore /// A that can be used to monitor the asynchronous operation, /// whose result returns the client application corresponding to the identifier. /// - public virtual ValueTask FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken) + public virtual async ValueTask FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(identifier)) { throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier)); } - return new ValueTask(FindById(Context, ConvertIdentifierFromString(identifier))); - } + var key = ConvertIdentifierFromString(identifier); - /// - /// Exposes a compiled query allowing to retrieve all the applications - /// associated with the specified post_logout_redirect_uri. - /// - private static readonly -#if SUPPORTS_BCL_ASYNC_ENUMERABLE - Func> -#else - Func> -#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().AsTracking() - where application.PostLogoutRedirectUris.Contains(address) - select application); + return await (from application in Applications.AsTracking() + where application.Id.Equals(key) + select application).FirstOrDefaultAsync(cancellationToken); + } /// /// 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)); } - return FindByPostLogoutRedirectUri(Context, address) -#if !SUPPORTS_BCL_ASYNC_ENUMERABLE - .AsAsyncEnumerable(cancellationToken) -#endif - .WhereAwait(async application => (await GetPostLogoutRedirectUrisAsync(application, cancellationToken)) - .Contains(address, StringComparer.Ordinal)); - } - - /// - /// Exposes a compiled query allowing to retrieve all the - /// applications associated with the specified redirect_uri. - /// - private static readonly -#if SUPPORTS_BCL_ASYNC_ENUMERABLE - Func> -#else - Func> -#endif - FindByRedirectUri = - // 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. - // 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. - EF.CompileAsyncQuery((TContext context, string address) => - from application in context.Set().AsTracking() - where application.RedirectUris.Contains(address) - select application); + var applications = (from application in Applications.AsTracking() + where application.PostLogoutRedirectUris.Contains(address) + select application).AsAsyncEnumerable(); + + return applications.WhereAwait(async application => + (await GetPostLogoutRedirectUrisAsync(application, cancellationToken)).Contains(address, StringComparer.Ordinal)); + } /// /// 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)); } - return FindByRedirectUri(Context, address) -#if !SUPPORTS_BCL_ASYNC_ENUMERABLE - .AsAsyncEnumerable(cancellationToken) -#endif - .WhereAwait(async application => (await GetRedirectUrisAsync(application, cancellationToken)) - .Contains(address, StringComparer.Ordinal)); + // To optimize the efficiency of the query a bit, only applications whose stringified + // RedirectUris property 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 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. + 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)); } /// diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs index 2d609471..2262df29 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs @@ -16,7 +16,6 @@ using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Options; @@ -244,31 +243,6 @@ namespace OpenIddict.EntityFrameworkCore } } - /// - /// Exposes a compiled query allowing to retrieve the authorizations corresponding - /// to the specified subject and associated with the application identifier. - /// - private static readonly -#if SUPPORTS_BCL_ASYNC_ENUMERABLE - Func> -#else - Func> -#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() - .Include(authorization => authorization.Application) - .AsTracking() - where authorization.Subject == subject - join application in context.Set().AsTracking() on authorization.Application.Id equals application.Id - where application.Id.Equals(identifier) - select authorization); - /// /// Retrieves the authorizations corresponding to the specified /// 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)); } - return FindBySubjectAndClient(Context, ConvertIdentifierFromString(client), subject) -#if !SUPPORTS_BCL_ASYNC_ENUMERABLE - .AsAsyncEnumerable(cancellationToken) -#endif - ; - } - - /// - /// Exposes a compiled query allowing to retrieve the authorizations matching the specified parameters. - /// - private static readonly -#if SUPPORTS_BCL_ASYNC_ENUMERABLE - Func> -#else - Func> -#endif - FindBySubjectClientAndStatus = - // 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. + // 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. - EF.CompileAsyncQuery((TContext context, TKey identifier, string subject, string status) => - from authorization in context.Set() - .Include(authorization => authorization.Application) - .AsTracking() - where authorization.Subject == subject && authorization.Status == status - join application in context.Set().AsTracking() on authorization.Application.Id equals application.Id - where application.Id.Equals(identifier) - select authorization); + + var key = ConvertIdentifierFromString(client); + + return (from authorization in Authorizations.Include(authorization => authorization.Application).AsTracking() + where authorization.Subject == subject + join application in Applications.AsTracking() on authorization.Application.Id equals application.Id + where application.Id.Equals(key) + select authorization).AsAsyncEnumerable(); + } /// /// 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)); } - return FindBySubjectClientAndStatus(Context, ConvertIdentifierFromString(client), subject, status) -#if !SUPPORTS_BCL_ASYNC_ENUMERABLE - .AsAsyncEnumerable(cancellationToken) -#endif - ; - } - - /// - /// Exposes a compiled query allowing to retrieve the authorizations matching the specified parameters. - /// - private static readonly -#if SUPPORTS_BCL_ASYNC_ENUMERABLE - Func> -#else - Func> -#endif - FindBySubjectClientStatusAndType = - // 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. + // 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. - EF.CompileAsyncQuery((TContext context, TKey identifier, string subject, string status, string type) => - from authorization in context.Set() - .Include(authorization => authorization.Application) - .AsTracking() - where authorization.Subject == subject && - authorization.Status == status && - authorization.Type == type - join application in context.Set().AsTracking() on authorization.Application.Id equals application.Id - where application.Id.Equals(identifier) - select authorization); + + var key = ConvertIdentifierFromString(client); + + return (from authorization in Authorizations.Include(authorization => authorization.Application).AsTracking() + where authorization.Subject == subject && authorization.Status == status + join application in Applications.AsTracking() on authorization.Application.Id equals application.Id + where application.Id.Equals(key) + select authorization).AsAsyncEnumerable(); + } /// /// 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)); } - return FindBySubjectClientStatusAndType(Context, ConvertIdentifierFromString(client), subject, status, type) -#if !SUPPORTS_BCL_ASYNC_ENUMERABLE - .AsAsyncEnumerable(cancellationToken) -#endif - ; + // 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 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. + + 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(); } /// @@ -439,30 +386,6 @@ namespace OpenIddict.EntityFrameworkCore .WhereAwait(async authorization => new HashSet( await GetScopesAsync(authorization, cancellationToken), StringComparer.Ordinal).IsSupersetOf(scopes)); - /// - /// Exposes a compiled query allowing to retrieve the list of - /// authorizations corresponding to the specified application identifier. - /// - private static readonly -#if SUPPORTS_BCL_ASYNC_ENUMERABLE - Func> -#else - Func> -#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() - .Include(authorization => authorization.Application) - .AsTracking() - join application in context.Set().AsTracking() on authorization.Application.Id equals application.Id - where application.Id.Equals(identifier) - select authorization); - /// /// Retrieves the list of authorizations corresponding to the specified application identifier. /// @@ -477,23 +400,18 @@ namespace OpenIddict.EntityFrameworkCore 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 - ; - } + // 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 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. - /// - /// Exposes a compiled query allowing to retrieve an authorization using its unique identifier. - /// - private static readonly Func> FindById = - EF.CompileAsyncQuery((TContext context, TKey identifier) => - (from authorization in context.Set() - .Include(authorization => authorization.Application) - .AsTracking() - where authorization.Id.Equals(identifier) - select authorization).FirstOrDefault()); + var key = ConvertIdentifierFromString(identifier); + + return (from authorization in Authorizations.Include(authorization => authorization.Application).AsTracking() + join application in Applications.AsTracking() on authorization.Application.Id equals application.Id + where application.Id.Equals(identifier) + select authorization).AsAsyncEnumerable(); + } /// /// Retrieves an authorization using its unique identifier. @@ -501,34 +419,19 @@ namespace OpenIddict.EntityFrameworkCore /// The unique identifier associated with the authorization. /// The that can be used to abort the operation. /// The authorization corresponding to the identifier. - public virtual ValueTask FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken) + public virtual async ValueTask FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(identifier)) { throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier)); } - return new ValueTask(FindById(Context, ConvertIdentifierFromString(identifier))); - } + var key = ConvertIdentifierFromString(identifier); - /// - /// Exposes a compiled query allowing to retrieve all the - /// authorizations corresponding to the specified subject. - /// - private static readonly -#if SUPPORTS_BCL_ASYNC_ENUMERABLE - Func> -#else - Func> -#endif - FindBySubject = - - EF.CompileAsyncQuery((TContext context, string subject) => - from authorization in context.Set() - .Include(authorization => authorization.Application) - .AsTracking() - where authorization.Subject == subject - select authorization); + return await (from authorization in Authorizations.Include(authorization => authorization.Application).AsTracking() + where authorization.Id.Equals(key) + select authorization).FirstOrDefaultAsync(cancellationToken); + } /// /// 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)); } - return FindBySubject(Context, subject) -#if !SUPPORTS_BCL_ASYNC_ENUMERABLE - .AsAsyncEnumerable(cancellationToken) -#endif - ; + return (from authorization in Authorizations.Include(authorization => authorization.Application).AsTracking() + where authorization.Subject == subject + select authorization).AsAsyncEnumerable(); } /// diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictScopeStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictScopeStore.cs index 5a56c621..27991a20 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictScopeStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictScopeStore.cs @@ -14,7 +14,6 @@ using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Query; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Options; using Newtonsoft.Json; @@ -177,15 +176,6 @@ namespace OpenIddict.EntityFrameworkCore } } - /// - /// Exposes a compiled query allowing to retrieve a scope using its unique identifier. - /// - private static readonly Func> FindById = - EF.CompileAsyncQuery((TContext context, TKey identifier) => - (from scope in context.Set().AsTracking() - where scope.Id.Equals(identifier) - select scope).FirstOrDefault()); - /// /// Retrieves a scope using its unique identifier. /// @@ -195,24 +185,19 @@ namespace OpenIddict.EntityFrameworkCore /// A that can be used to monitor the asynchronous operation, /// whose result returns the scope corresponding to the identifier. /// - public virtual ValueTask FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken) + public virtual async ValueTask FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(identifier)) { throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier)); } - return new ValueTask(FindById(Context, ConvertIdentifierFromString(identifier))); - } + var key = ConvertIdentifierFromString(identifier); - /// - /// Exposes a compiled query allowing to retrieve a scope using its name. - /// - private static readonly Func> FindByName = - EF.CompileAsyncQuery((TContext context, string name) => - (from scope in context.Set().AsTracking() - where scope.Name == name - select scope).FirstOrDefault()); + return await (from scope in Scopes.AsTracking() + where scope.Id.Equals(key) + select scope).FirstOrDefaultAsync(cancellationToken); + } /// /// Retrieves a scope using its name. @@ -223,30 +208,18 @@ namespace OpenIddict.EntityFrameworkCore /// A that can be used to monitor the asynchronous operation, /// whose result returns the scope corresponding to the specified name. /// - public virtual ValueTask FindByNameAsync([NotNull] string name, CancellationToken cancellationToken) + public virtual async ValueTask FindByNameAsync([NotNull] string name, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(name)) { throw new ArgumentException("The scope name cannot be null or empty.", nameof(name)); } - return new ValueTask(FindByName(Context, name)); + return await (from scope in Scopes.AsTracking() + where scope.Name == name + select scope).FirstOrDefaultAsync(cancellationToken); } - /// - /// Exposes a compiled query allowing to retrieve a list of scopes using their name. - /// - private static readonly -#if SUPPORTS_BCL_ASYNC_ENUMERABLE - Func, IAsyncEnumerable> -#else - Func, AsyncEnumerable> -#endif - FindByNames = EF.CompileAsyncQuery((TContext context, ImmutableArray names) => - from scope in context.Set().AsTracking() - where names.Contains(scope.Name) - select scope); - /// /// Retrieves a list of scopes using their name. /// @@ -261,34 +234,11 @@ namespace OpenIddict.EntityFrameworkCore throw new ArgumentException("Scope names cannot be null or empty.", nameof(names)); } - return FindByNames(Context, names) -#if !SUPPORTS_BCL_ASYNC_ENUMERABLE - .AsAsyncEnumerable(cancellationToken) -#endif - ; + return (from scope in Scopes.AsTracking() + where names.Contains(scope.Name) + select scope).AsAsyncEnumerable(); } - /// - /// Exposes a compiled query allowing to retrieve all the scopes that contain the specified resource. - /// - private static readonly -#if SUPPORTS_BCL_ASYNC_ENUMERABLE - Func> -#else - Func> -#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().AsTracking() - where scope.Resources.Contains(resource) - select scope); - /// /// Retrieves all the scopes that contain the specified resource. /// @@ -303,11 +253,12 @@ namespace OpenIddict.EntityFrameworkCore throw new ArgumentException("The resource cannot be null or empty.", nameof(resource)); } - return FindByResource(Context, resource) -#if !SUPPORTS_BCL_ASYNC_ENUMERABLE - .AsAsyncEnumerable(cancellationToken) -#endif - .WhereAwait(async scope => (await GetResourcesAsync(scope, cancellationToken)).Contains(resource, StringComparer.Ordinal)); + var scopes = (from scope in Scopes.AsTracking() + where scope.Resources.Contains(resource) + select scope).AsAsyncEnumerable(); + + return scopes.WhereAwait(async scope => + (await GetResourcesAsync(scope, cancellationToken)).Contains(resource, StringComparer.Ordinal)); } /// diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs index f8e902ad..1611565b 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs @@ -15,7 +15,6 @@ using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Options; @@ -197,32 +196,6 @@ namespace OpenIddict.EntityFrameworkCore } } - /// - /// Exposes a compiled query allowing to retrieve the tokens corresponding - /// to the specified subject and associated with the application identifier. - /// - private static readonly -#if SUPPORTS_BCL_ASYNC_ENUMERABLE - Func> -#else - Func> -#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() - .Include(token => token.Application) - .Include(token => token.Authorization) - .AsTracking() - where token.Subject == subject - join application in context.Set().AsTracking() on token.Application.Id equals application.Id - where application.Id.Equals(identifier) - select token); - /// /// Retrieves the tokens corresponding to the specified /// 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)); } - return FindBySubjectAndClient(Context, ConvertIdentifierFromString(client), subject) -#if !SUPPORTS_BCL_ASYNC_ENUMERABLE - .AsAsyncEnumerable(cancellationToken) -#endif - ; - } - - /// - /// Exposes a compiled query allowing to retrieve the tokens matching the specified parameters. - /// - private static readonly -#if SUPPORTS_BCL_ASYNC_ENUMERABLE - Func> -#else - Func> -#endif - FindBySubjectClientAndStatus = - // 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, string status) => - from token in context.Set() - .Include(token => token.Application) - .Include(token => token.Authorization) - .AsTracking() - where token.Subject == subject && token.Status == status - join application in context.Set().AsTracking() on token.Application.Id equals application.Id - where application.Id.Equals(identifier) - select token); + + var key = ConvertIdentifierFromString(client); + + return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking() + where token.Subject == subject + join application in Applications.AsTracking() on token.Application.Id equals application.Id + where application.Id.Equals(key) + select token).AsAsyncEnumerable(); + } /// /// 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)); } - return FindBySubjectClientAndStatus(Context, ConvertIdentifierFromString(client), subject, status) -#if !SUPPORTS_BCL_ASYNC_ENUMERABLE - .AsAsyncEnumerable(cancellationToken) -#endif - ; - } - - /// - /// Exposes a compiled query allowing to retrieve the tokens matching the specified parameters. - /// - private static readonly -#if SUPPORTS_BCL_ASYNC_ENUMERABLE - Func> -#else - Func> -#endif - FindBySubjectClientStatusAndType = - // 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, string status, string type) => - from token in context.Set() - .Include(token => token.Application) - .Include(token => token.Authorization) - .AsTracking() - where token.Subject == subject && - token.Status == status && - token.Type == type - join application in context.Set().AsTracking() on token.Application.Id equals application.Id - where application.Id.Equals(identifier) - select token); + + var key = ConvertIdentifierFromString(client); + + return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking() + where token.Subject == subject && + token.Status == status + join application in Applications.AsTracking() on token.Application.Id equals application.Id + where application.Id.Equals(key) + select token).AsAsyncEnumerable(); + } /// /// 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)); } - return FindBySubjectClientStatusAndType(Context, ConvertIdentifierFromString(client), subject, status, type) -#if !SUPPORTS_BCL_ASYNC_ENUMERABLE - .AsAsyncEnumerable(cancellationToken) -#endif - ; - } - - /// - /// Exposes a compiled query allowing to retrieve the list of - /// tokens corresponding to the specified application identifier. - /// - private static readonly -#if SUPPORTS_BCL_ASYNC_ENUMERABLE - Func> -#else - Func> -#endif - FindByApplicationId = - - // 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 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) => - from token in context.Set() - .Include(token => token.Application) - .Include(token => token.Authorization) - .AsTracking() - join application in context.Set().AsTracking() on token.Application.Id equals application.Id - where application.Id.Equals(identifier) - select token); + + var key = ConvertIdentifierFromString(client); + + return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking() + where token.Subject == subject && + token.Status == status && + token.Type == type + join application in Applications.AsTracking() on token.Application.Id equals application.Id + where application.Id.Equals(key) + select token).AsAsyncEnumerable(); + } /// /// 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)); } - return FindByApplicationId(Context, ConvertIdentifierFromString(identifier)) -#if !SUPPORTS_BCL_ASYNC_ENUMERABLE - .AsAsyncEnumerable(cancellationToken) -#endif - ; - } - - /// - /// Exposes a compiled query allowing to retrieve the list of - /// tokens corresponding to the specified authorization identifier. - /// - private static readonly -#if SUPPORTS_BCL_ASYNC_ENUMERABLE - Func> -#else - Func> -#endif - FindByAuthorizationId = - // 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, - // this compiled query uses an explicit join before applying the equality check. + // filtered using token.Application.Id.Equals(key). To work around this issue, + // 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. - EF.CompileAsyncQuery((TContext context, TKey identifier) => - from token in context.Set() - .Include(token => token.Application) - .Include(token => token.Authorization) - .AsTracking() - join authorization in context.Set().AsTracking() on token.Authorization.Id equals authorization.Id - where authorization.Id.Equals(identifier) - select token); + + var key = ConvertIdentifierFromString(identifier); + + return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking() + join application in Applications.AsTracking() on token.Application.Id equals application.Id + where application.Id.Equals(key) + select token).AsAsyncEnumerable(); + } /// /// 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)); } - return FindByAuthorizationId(Context, ConvertIdentifierFromString(identifier)) -#if !SUPPORTS_BCL_ASYNC_ENUMERABLE - .AsAsyncEnumerable(cancellationToken) -#endif - ; - } + // 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, + // 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. - /// - /// Exposes a compiled query allowing to retrieve a token using its unique identifier. - /// - private static readonly Func> FindById = - EF.CompileAsyncQuery((TContext context, TKey identifier) => - (from token in context.Set() - .Include(token => token.Application) - .Include(token => token.Authorization) - .AsTracking() - where token.Id.Equals(identifier) - select token).FirstOrDefault()); + var key = ConvertIdentifierFromString(identifier); + + return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking() + join authorization in Authorizations.AsTracking() on token.Authorization.Id equals authorization.Id + where authorization.Id.Equals(key) + select token).AsAsyncEnumerable(); + } /// /// Retrieves a token using its unique identifier. @@ -488,28 +383,19 @@ namespace OpenIddict.EntityFrameworkCore /// A that can be used to monitor the asynchronous operation, /// whose result returns the token corresponding to the unique identifier. /// - public virtual ValueTask FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken) + public virtual async ValueTask FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(identifier)) { throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier)); } - return new ValueTask(FindById(Context, ConvertIdentifierFromString(identifier))); - } + var key = ConvertIdentifierFromString(identifier); - /// - /// Exposes a compiled query allowing to retrieve the list of - /// tokens corresponding to the specified reference identifier. - /// - private static readonly Func> FindByReferenceId = - EF.CompileAsyncQuery((TContext context, string identifier) => - (from token in context.Set() - .Include(token => token.Application) - .Include(token => token.Authorization) - .AsTracking() - where token.ReferenceId == identifier - select token).FirstOrDefault()); + return await (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking() + where token.Id.Equals(key) + select token).FirstOrDefaultAsync(cancellationToken); + } /// /// Retrieves the list of tokens corresponding to the specified reference identifier. @@ -521,34 +407,18 @@ namespace OpenIddict.EntityFrameworkCore /// A that can be used to monitor the asynchronous operation, /// whose result returns the tokens corresponding to the specified reference identifier. /// - public virtual ValueTask FindByReferenceIdAsync([NotNull] string identifier, CancellationToken cancellationToken) + public virtual async ValueTask FindByReferenceIdAsync([NotNull] string identifier, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(identifier)) { throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier)); } - return new ValueTask(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); } - /// - /// Exposes a compiled query allowing to retrieve the - /// list of tokens corresponding to the specified subject. - /// - private static readonly -#if SUPPORTS_BCL_ASYNC_ENUMERABLE - Func> -#else - Func> -#endif - FindBySubject = EF.CompileAsyncQuery((TContext context, string subject) => - from token in context.Set() - .Include(token => token.Application) - .Include(token => token.Authorization) - .AsTracking() - where token.Subject == subject - select token); - /// /// Retrieves the list of tokens corresponding to the specified subject. /// @@ -562,11 +432,9 @@ namespace OpenIddict.EntityFrameworkCore throw new ArgumentException("The subject cannot be null or empty.", nameof(subject)); } - return FindBySubject(Context, subject) -#if !SUPPORTS_BCL_ASYNC_ENUMERABLE - .AsAsyncEnumerable(cancellationToken) -#endif - ; + return (from token in Tokens.Include(token => token.Application).Include(token => token.Authorization).AsTracking() + where token.Subject == subject + select token).AsAsyncEnumerable(); } ///