diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs index 9efe5086..4b59dc15 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs @@ -17,6 +17,7 @@ using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Options; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OpenIddict.Abstractions; @@ -33,8 +34,11 @@ namespace OpenIddict.EntityFramework OpenIddictToken, TContext, string> where TContext : DbContext { - public OpenIddictApplicationStore([NotNull] IMemoryCache cache, [NotNull] TContext context) - : base(cache, context) + public OpenIddictApplicationStore( + [NotNull] IMemoryCache cache, + [NotNull] TContext context, + [NotNull] IOptionsMonitor options) + : base(cache, context, options) { } } @@ -54,10 +58,14 @@ namespace OpenIddict.EntityFramework where TContext : DbContext where TKey : IEquatable { - public OpenIddictApplicationStore([NotNull] IMemoryCache cache, [NotNull] TContext context) + public OpenIddictApplicationStore( + [NotNull] IMemoryCache cache, + [NotNull] TContext context, + [NotNull] IOptionsMonitor options) { Cache = cache; Context = context; + Options = options; } /// @@ -70,6 +78,11 @@ namespace OpenIddict.EntityFramework /// protected TContext Context { get; } + /// + /// Gets the options associated with the current store. + /// + protected IOptionsMonitor Options { get; } + /// /// Gets the database set corresponding to the entity. /// diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs index 89ad29a5..0c9f0814 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs @@ -17,6 +17,7 @@ using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Options; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OpenIddict.Abstractions; @@ -33,8 +34,11 @@ namespace OpenIddict.EntityFramework OpenIddictToken, TContext, string> where TContext : DbContext { - public OpenIddictAuthorizationStore([NotNull] IMemoryCache cache, [NotNull] TContext context) - : base(cache, context) + public OpenIddictAuthorizationStore( + [NotNull] IMemoryCache cache, + [NotNull] TContext context, + [NotNull] IOptionsMonitor options) + : base(cache, context, options) { } } @@ -54,10 +58,14 @@ namespace OpenIddict.EntityFramework where TContext : DbContext where TKey : IEquatable { - public OpenIddictAuthorizationStore([NotNull] IMemoryCache cache, [NotNull] TContext context) + public OpenIddictAuthorizationStore( + [NotNull] IMemoryCache cache, + [NotNull] TContext context, + [NotNull] IOptionsMonitor options) { Cache = cache; Context = context; + Options = options; } /// @@ -70,6 +78,11 @@ namespace OpenIddict.EntityFramework /// protected TContext Context { get; } + /// + /// Gets the options associated with the current store. + /// + protected IOptionsMonitor Options { get; } + /// /// Gets the database set corresponding to the entity. /// diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictScopeStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictScopeStore.cs index 82728ad9..c3e702c5 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictScopeStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictScopeStore.cs @@ -15,6 +15,7 @@ using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Options; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OpenIddict.Abstractions; @@ -29,8 +30,11 @@ namespace OpenIddict.EntityFramework public class OpenIddictScopeStore : OpenIddictScopeStore where TContext : DbContext { - public OpenIddictScopeStore([NotNull] IMemoryCache cache, [NotNull] TContext context) - : base(cache, context) + public OpenIddictScopeStore( + [NotNull] IMemoryCache cache, + [NotNull] TContext context, + [NotNull] IOptionsMonitor options) + : base(cache, context, options) { } } @@ -46,10 +50,14 @@ namespace OpenIddict.EntityFramework where TContext : DbContext where TKey : IEquatable { - public OpenIddictScopeStore([NotNull] IMemoryCache cache, [NotNull] TContext context) + public OpenIddictScopeStore( + [NotNull] IMemoryCache cache, + [NotNull] TContext context, + [NotNull] IOptionsMonitor options) { Cache = cache; Context = context; + Options = options; } /// @@ -62,6 +70,11 @@ namespace OpenIddict.EntityFramework /// protected TContext Context { get; } + /// + /// Gets the options associated with the current store. + /// + protected IOptionsMonitor Options { get; } + /// /// Gets the database set corresponding to the entity. /// diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs index 3a66c3ec..e3855376 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs @@ -17,6 +17,7 @@ using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Options; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OpenIddict.Abstractions; @@ -33,8 +34,11 @@ namespace OpenIddict.EntityFramework OpenIddictAuthorization, TContext, string> where TContext : DbContext { - public OpenIddictTokenStore([NotNull] IMemoryCache cache, [NotNull] TContext context) - : base(cache, context) + public OpenIddictTokenStore( + [NotNull] IMemoryCache cache, + [NotNull] TContext context, + [NotNull] IOptionsMonitor options) + : base(cache, context, options) { } } @@ -54,10 +58,14 @@ namespace OpenIddict.EntityFramework where TContext : DbContext where TKey : IEquatable { - public OpenIddictTokenStore([NotNull] IMemoryCache cache, [NotNull] TContext context) + public OpenIddictTokenStore( + [NotNull] IMemoryCache cache, + [NotNull] TContext context, + [NotNull] IOptionsMonitor options) { Cache = cache; Context = context; + Options = options; } /// @@ -70,6 +78,11 @@ namespace OpenIddict.EntityFramework /// protected TContext Context { get; } + /// + /// Gets the options associated with the current store. + /// + protected IOptionsMonitor Options { get; } + /// /// Gets the database set corresponding to the entity. /// diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs index cde98d9c..a956a8d1 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs @@ -16,8 +16,10 @@ 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; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OpenIddict.Abstractions; @@ -34,8 +36,11 @@ namespace OpenIddict.EntityFrameworkCore OpenIddictToken, TContext, string> where TContext : DbContext { - public OpenIddictApplicationStore([NotNull] IMemoryCache cache, [NotNull] TContext context) - : base(cache, context) + public OpenIddictApplicationStore( + [NotNull] IMemoryCache cache, + [NotNull] TContext context, + [NotNull] IOptionsMonitor options) + : base(cache, context, options) { } } @@ -51,8 +56,11 @@ namespace OpenIddict.EntityFrameworkCore where TContext : DbContext where TKey : IEquatable { - public OpenIddictApplicationStore([NotNull] IMemoryCache cache, [NotNull] TContext context) - : base(cache, context) + public OpenIddictApplicationStore( + [NotNull] IMemoryCache cache, + [NotNull] TContext context, + [NotNull] IOptionsMonitor options) + : base(cache, context, options) { } } @@ -72,10 +80,14 @@ namespace OpenIddict.EntityFrameworkCore where TContext : DbContext where TKey : IEquatable { - public OpenIddictApplicationStore([NotNull] IMemoryCache cache, [NotNull] TContext context) + public OpenIddictApplicationStore( + [NotNull] IMemoryCache cache, + [NotNull] TContext context, + [NotNull] IOptionsMonitor options) { Cache = cache; Context = context; + Options = options; } /// @@ -88,6 +100,11 @@ namespace OpenIddict.EntityFrameworkCore /// protected TContext Context { get; } + /// + /// Gets the options associated with the current store. + /// + protected IOptionsMonitor Options { get; } + /// /// Gets the database set corresponding to the entity. /// @@ -256,6 +273,15 @@ namespace OpenIddict.EntityFrameworkCore } } + /// + /// Exposes a compiled query allowing to retrieve an application using its client identifier. + /// + private static 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. /// @@ -272,19 +298,18 @@ namespace OpenIddict.EntityFrameworkCore throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier)); } - var query = Cache.GetOrCreate("b7c5b6ad-572f-4106-9a32-4f1dc1981b73", entry => - { - entry.SetPriority(CacheItemPriority.NeverRemove); - - return EF.CompileAsyncQuery((TContext context, string id) => - (from application in context.Set().AsTracking() - where application.ClientId == id - select application).FirstOrDefault()); - }); - - return query(Context, identifier); + return FindByClientId(Context, identifier); } + /// + /// Exposes a compiled query allowing to retrieve an application using its unique identifier. + /// + private static 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. /// @@ -301,19 +326,24 @@ namespace OpenIddict.EntityFrameworkCore throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier)); } - var query = Cache.GetOrCreate("91082918-b9b9-4701-b969-54e33999a1b9", entry => - { - entry.SetPriority(CacheItemPriority.NeverRemove); - - return EF.CompileAsyncQuery((TContext context, TKey key) => - (from application in context.Set().AsTracking() - where application.Id.Equals(key) - select application).FirstOrDefault()); - }); - - return query(Context, ConvertIdentifierFromString(identifier)); + return FindById(Context, ConvertIdentifierFromString(identifier)); } + /// + /// Exposes a compiled query allowing to retrieve all the applications + /// associated with the specified post_logout_redirect_uri. + /// + private static Func> 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); + /// /// Retrieves all the applications associated with the specified post_logout_redirect_uri. /// @@ -329,25 +359,9 @@ namespace OpenIddict.EntityFrameworkCore { throw new ArgumentException("The address cannot be null or empty.", nameof(address)); } - - // 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 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 query = Cache.GetOrCreate("a805ce67-2c5b-4617-a772-56413dccac45", entry => - { - entry.SetPriority(CacheItemPriority.NeverRemove); - - return EF.CompileAsyncQuery((TContext context, string uri) => - from application in context.Set().AsTracking() - where application.PostLogoutRedirectUris.Contains(uri) - select application); - }); - var builder = ImmutableArray.CreateBuilder(); - foreach (var application in await query(Context, address).ToListAsync(cancellationToken)) + foreach (var application in await FindByPostLogoutRedirectUri(Context, address).ToListAsync(cancellationToken)) { foreach (var uri in await GetPostLogoutRedirectUrisAsync(application, cancellationToken)) { @@ -367,6 +381,21 @@ namespace OpenIddict.EntityFrameworkCore builder.ToImmutable(); } + /// + /// Exposes a compiled query allowing to retrieve all the + /// applications associated with the specified redirect_uri. + /// + private static Func> 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 + // 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.RedirectUris.Contains(address) + select application); + /// /// Retrieves all the applications associated with the specified redirect_uri. /// @@ -383,24 +412,9 @@ namespace OpenIddict.EntityFrameworkCore throw new ArgumentException("The address cannot be null or empty.", nameof(address)); } - // 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 query = Cache.GetOrCreate("b3883c99-646f-4027-9855-dad8370b977a", entry => - { - entry.SetPriority(CacheItemPriority.NeverRemove); - - return EF.CompileAsyncQuery((TContext context, string uri) => - from application in context.Set().AsTracking() - where application.RedirectUris.Contains(uri) - select application); - }); - var builder = ImmutableArray.CreateBuilder(); - foreach (var application in await query(Context, address).ToListAsync(cancellationToken)) + foreach (var application in await FindByRedirectUri(Context, address).ToListAsync(cancellationToken)) { foreach (var uri in await GetRedirectUrisAsync(application, cancellationToken)) { diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs index 32c634c5..a10c67b5 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs @@ -16,8 +16,10 @@ 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; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OpenIddict.Abstractions; @@ -34,8 +36,11 @@ namespace OpenIddict.EntityFrameworkCore OpenIddictToken, TContext, string> where TContext : DbContext { - public OpenIddictAuthorizationStore([NotNull] IMemoryCache cache, [NotNull] TContext context) - : base(cache, context) + public OpenIddictAuthorizationStore( + [NotNull] IMemoryCache cache, + [NotNull] TContext context, + [NotNull] IOptionsMonitor options) + : base(cache, context, options) { } } @@ -51,8 +56,11 @@ namespace OpenIddict.EntityFrameworkCore where TContext : DbContext where TKey : IEquatable { - public OpenIddictAuthorizationStore([NotNull] IMemoryCache cache, [NotNull] TContext context) - : base(cache, context) + public OpenIddictAuthorizationStore( + [NotNull] IMemoryCache cache, + [NotNull] TContext context, + [NotNull] IOptionsMonitor options) + : base(cache, context, options) { } } @@ -72,10 +80,14 @@ namespace OpenIddict.EntityFrameworkCore where TContext : DbContext where TKey : IEquatable { - public OpenIddictAuthorizationStore([NotNull] IMemoryCache cache, [NotNull] TContext context) + public OpenIddictAuthorizationStore( + [NotNull] IMemoryCache cache, + [NotNull] TContext context, + [NotNull] IOptionsMonitor options) { Cache = cache; Context = context; + Options = options; } /// @@ -88,6 +100,11 @@ namespace OpenIddict.EntityFrameworkCore /// protected TContext Context { get; } + /// + /// Gets the options associated with the current store. + /// + protected IOptionsMonitor Options { get; } + /// /// Gets the database set corresponding to the entity. /// @@ -232,6 +249,24 @@ 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 Func> 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. @@ -256,27 +291,26 @@ namespace OpenIddict.EntityFrameworkCore throw new ArgumentException("The client cannot be null or empty.", nameof(client)); } + return ImmutableArray.CreateRange(await FindBySubjectAndClient(Context, + ConvertIdentifierFromString(client), subject).ToListAsync(cancellationToken)); + } + + /// + /// Exposes a compiled query allowing to retrieve the authorizations matching the specified parameters. + /// + private static Func> 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 method is overriden to use 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. - var query = Cache.GetOrCreate("a3235f5b-2be5-452e-a43e-b10f5d6a01ff", entry => - { - entry.SetPriority(CacheItemPriority.NeverRemove); - - return EF.CompileAsyncQuery((TContext context, TKey key, string principal) => - from authorization in context.Set() - .Include(authorization => authorization.Application) - .AsTracking() - where authorization.Subject == principal - join application in context.Set().AsTracking() on authorization.Application.Id equals application.Id - where application.Id.Equals(key) - select authorization); - }); - - return ImmutableArray.CreateRange(await query(Context, - ConvertIdentifierFromString(client), subject).ToListAsync(cancellationToken)); - } + 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); /// /// Retrieves the authorizations matching the specified parameters. @@ -308,27 +342,28 @@ namespace OpenIddict.EntityFrameworkCore throw new ArgumentException("The status cannot be null or empty.", nameof(status)); } + return ImmutableArray.CreateRange(await FindBySubjectClientAndStatus(Context, + ConvertIdentifierFromString(client), subject, status).ToListAsync(cancellationToken)); + } + + /// + /// Exposes a compiled query allowing to retrieve the authorizations matching the specified parameters. + /// + private static Func> 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 method is overriden to use 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. - var query = Cache.GetOrCreate("bdf6b8aa-cd27-4b23-9961-fdd896a6660e", entry => - { - entry.SetPriority(CacheItemPriority.NeverRemove); - - return EF.CompileAsyncQuery((TContext context, TKey key, string principal, string state) => - from authorization in context.Set() - .Include(authorization => authorization.Application) - .AsTracking() - where authorization.Subject == principal && authorization.Status == state - join application in context.Set().AsTracking() on authorization.Application.Id equals application.Id - where application.Id.Equals(key) - select authorization); - }); - - return ImmutableArray.CreateRange(await query(Context, - ConvertIdentifierFromString(client), subject, status).ToListAsync(cancellationToken)); - } + 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); /// /// Retrieves the authorizations matching the specified parameters. @@ -366,30 +401,21 @@ namespace OpenIddict.EntityFrameworkCore throw new ArgumentException("The type cannot be null or empty.", nameof(type)); } - // 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 query = Cache.GetOrCreate("5d06e679-70cd-4f4b-94f7-19ffe9d5d8ab", entry => - { - entry.SetPriority(CacheItemPriority.NeverRemove); - - return EF.CompileAsyncQuery((TContext context, TKey key, string principal, string state, string kind) => - from authorization in context.Set() - .Include(authorization => authorization.Application) - .AsTracking() - where authorization.Subject == principal && - authorization.Status == state && - authorization.Type == kind - join application in context.Set().AsTracking() on authorization.Application.Id equals application.Id - where application.Id.Equals(key) - select authorization); - }); - - return ImmutableArray.CreateRange(await query(Context, + return ImmutableArray.CreateRange(await FindBySubjectClientStatusAndType(Context, ConvertIdentifierFromString(client), subject, status, type).ToListAsync(cancellationToken)); } + /// + /// Exposes a compiled query allowing to retrieve an authorization using its unique identifier. + /// + private static 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()); + /// /// Retrieves an authorization using its unique identifier. /// @@ -406,23 +432,23 @@ namespace OpenIddict.EntityFrameworkCore throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier)); } - var query = Cache.GetOrCreate("0f00e136-9277-484a-8ee4-12bf1d889c43", entry => - { - entry.SetPriority(CacheItemPriority.NeverRemove); - - return EF.CompileAsyncQuery((TContext context, TKey key) => - (from authorization in context.Set() - .Include(authorization => authorization.Application) - .AsTracking() - where authorization.Id.Equals(key) - select authorization).FirstOrDefault()); - }); - - return query(Context, ConvertIdentifierFromString(identifier)); + return FindById(Context, ConvertIdentifierFromString(identifier)); } /// - /// Retrieves all the authorizations corresponding to the specified subject. + /// Exposes a compiled query allowing to retrieve all the + /// authorizations corresponding to the specified subject. + /// + private static Func> FindBySubject = + EF.CompileAsyncQuery((TContext context, string subject) => + from authorization in context.Set() + .Include(authorization => authorization.Application) + .AsTracking() + where authorization.Subject == subject + select authorization); + + /// + /// Retrieves . /// /// The subject associated with the authorization. /// The that can be used to abort the operation. @@ -438,19 +464,7 @@ namespace OpenIddict.EntityFrameworkCore throw new ArgumentException("The subject cannot be null or empty.", nameof(subject)); } - var query = Cache.GetOrCreate("31e53867-fa49-4d1b-b846-acce353acd0b", entry => - { - entry.SetPriority(CacheItemPriority.NeverRemove); - - return EF.CompileAsyncQuery((TContext context, string principal) => - from authorization in context.Set() - .Include(authorization => authorization.Application) - .AsTracking() - where authorization.Subject == principal - select authorization); - }); - - return ImmutableArray.CreateRange(await query(Context, subject).ToListAsync(cancellationToken)); + return ImmutableArray.CreateRange(await FindBySubject(Context, subject).ToListAsync(cancellationToken)); } /// diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictScopeStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictScopeStore.cs index 40bdb38b..e18045d5 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictScopeStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictScopeStore.cs @@ -13,7 +13,9 @@ 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; using Newtonsoft.Json.Linq; using OpenIddict.Abstractions; @@ -28,8 +30,11 @@ namespace OpenIddict.EntityFrameworkCore public class OpenIddictScopeStore : OpenIddictScopeStore where TContext : DbContext { - public OpenIddictScopeStore([NotNull] IMemoryCache cache, [NotNull] TContext context) - : base(cache, context) + public OpenIddictScopeStore( + [NotNull] IMemoryCache cache, + [NotNull] TContext context, + [NotNull] IOptionsMonitor options) + : base(cache, context, options) { } } @@ -43,8 +48,11 @@ namespace OpenIddict.EntityFrameworkCore where TContext : DbContext where TKey : IEquatable { - public OpenIddictScopeStore([NotNull] IMemoryCache cache, [NotNull] TContext context) - : base(cache, context) + public OpenIddictScopeStore( + [NotNull] IMemoryCache cache, + [NotNull] TContext context, + [NotNull] IOptionsMonitor options) + : base(cache, context, options) { } } @@ -60,10 +68,14 @@ namespace OpenIddict.EntityFrameworkCore where TContext : DbContext where TKey : IEquatable { - public OpenIddictScopeStore([NotNull] IMemoryCache cache, [NotNull] TContext context) + public OpenIddictScopeStore( + [NotNull] IMemoryCache cache, + [NotNull] TContext context, + [NotNull] IOptionsMonitor options) { Cache = cache; Context = context; + Options = options; } /// @@ -76,6 +88,11 @@ namespace OpenIddict.EntityFrameworkCore /// protected TContext Context { get; } + /// + /// Gets the options associated with the current store. + /// + protected IOptionsMonitor Options { get; } + /// /// Gets the database set corresponding to the entity. /// @@ -163,6 +180,15 @@ namespace OpenIddict.EntityFrameworkCore } } + /// + /// Exposes a compiled query allowing to retrieve a scope using its unique identifier. + /// + private static 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. /// @@ -179,19 +205,18 @@ namespace OpenIddict.EntityFrameworkCore throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier)); } - var query = Cache.GetOrCreate("485f3372-2d38-4418-8d1e-acd8aa0c3555", entry => - { - entry.SetPriority(CacheItemPriority.NeverRemove); - - return EF.CompileAsyncQuery((TContext context, TKey key) => - (from scope in context.Set().AsTracking() - where scope.Id.Equals(key) - select scope).FirstOrDefault()); - }); - - return query(Context, ConvertIdentifierFromString(identifier)); + return FindById(Context, ConvertIdentifierFromString(identifier)); } + /// + /// Exposes a compiled query allowing to retrieve a scope using its name. + /// + private static Func> FindByName = + EF.CompileAsyncQuery((TContext context, string name) => + (from scope in context.Set().AsTracking() + where scope.Name == name + select scope).FirstOrDefault()); + /// /// Retrieves a scope using its name. /// @@ -208,19 +233,18 @@ namespace OpenIddict.EntityFrameworkCore throw new ArgumentException("The scope name cannot be null or empty.", nameof(name)); } - var query = Cache.GetOrCreate("71cb2f7a-7adb-4b3e-8833-772e254f9b03", entry => - { - entry.SetPriority(CacheItemPriority.NeverRemove); - - return EF.CompileAsyncQuery((TContext context, string id) => - (from scope in context.Set().AsTracking() - where scope.Name == id - select scope).FirstOrDefault()); - }); - - return query(Context, name); + return FindByName(Context, name); } + /// + /// Exposes a compiled query allowing to retrieve a list of scopes using their name. + /// + private static Func, AsyncEnumerable> 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. /// @@ -238,19 +262,23 @@ namespace OpenIddict.EntityFrameworkCore throw new ArgumentException("Scope names cannot be null or empty.", nameof(names)); } - var query = Cache.GetOrCreate("e19404af-8586-4693-9b9c-8185b653ee2d", entry => - { - entry.SetPriority(CacheItemPriority.NeverRemove); - - return EF.CompileAsyncQuery((TContext context, ImmutableArray ids) => - from scope in context.Set().AsTracking() - where ids.Contains(scope.Name) - select scope); - }); - - return ImmutableArray.CreateRange(await query(Context, names).ToListAsync(cancellationToken)); + return ImmutableArray.CreateRange(await FindByNames(Context, names).ToListAsync(cancellationToken)); } + /// + /// Exposes a compiled query allowing to retrieve all the scopes that contain the specified resource. + /// + private static Func> 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. /// @@ -268,24 +296,9 @@ namespace OpenIddict.EntityFrameworkCore throw new ArgumentException("The resource cannot be null or empty.", nameof(resource)); } - // 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 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 query = Cache.GetOrCreate("45bae754-72fc-422c-b3a2-90867600a029", entry => - { - entry.SetPriority(CacheItemPriority.NeverRemove); - - return EF.CompileAsyncQuery((TContext context, string value) => - from scope in context.Set().AsTracking() - where scope.Resources.Contains(value) - select scope); - }); - var builder = ImmutableArray.CreateBuilder(); - foreach (var scope in await query(Context, resource).ToListAsync(cancellationToken)) + foreach (var scope in await FindByResource(Context, resource).ToListAsync(cancellationToken)) { var resources = await GetResourcesAsync(scope, cancellationToken); if (resources.Contains(resource, StringComparer.Ordinal)) diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs index ee7db43b..e031e744 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs @@ -16,8 +16,10 @@ 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; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OpenIddict.Abstractions; @@ -34,8 +36,11 @@ namespace OpenIddict.EntityFrameworkCore OpenIddictAuthorization, TContext, string> where TContext : DbContext { - public OpenIddictTokenStore([NotNull] IMemoryCache cache, [NotNull] TContext context) - : base(cache, context) + public OpenIddictTokenStore( + [NotNull] IMemoryCache cache, + [NotNull] TContext context, + [NotNull] IOptionsMonitor options) + : base(cache, context, options) { } } @@ -51,8 +56,11 @@ namespace OpenIddict.EntityFrameworkCore where TContext : DbContext where TKey : IEquatable { - public OpenIddictTokenStore([NotNull] IMemoryCache cache, [NotNull] TContext context) - : base(cache, context) + public OpenIddictTokenStore( + [NotNull] IMemoryCache cache, + [NotNull] TContext context, + [NotNull] IOptionsMonitor options) + : base(cache, context, options) { } } @@ -72,10 +80,14 @@ namespace OpenIddict.EntityFrameworkCore where TContext : DbContext where TKey : IEquatable { - public OpenIddictTokenStore([NotNull] IMemoryCache cache, [NotNull] TContext context) + public OpenIddictTokenStore( + [NotNull] IMemoryCache cache, + [NotNull] TContext context, + [NotNull] IOptionsMonitor options) { Cache = cache; Context = context; + Options = options; } /// @@ -88,6 +100,11 @@ namespace OpenIddict.EntityFrameworkCore /// protected TContext Context { get; } + /// + /// Gets the options associated with the current store. + /// + protected IOptionsMonitor Options { get; } + /// /// Gets the database set corresponding to the entity. /// @@ -185,6 +202,24 @@ namespace OpenIddict.EntityFrameworkCore } } + /// + /// Exposes a compiled query allowing to retrieve the list of + /// tokens corresponding to the specified application identifier. + /// + private static Func> 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, + // 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); + /// /// Retrieves the list of tokens corresponding to the specified application identifier. /// @@ -201,28 +236,28 @@ namespace OpenIddict.EntityFrameworkCore throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier)); } - // 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, - // 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 query = Cache.GetOrCreate("9c2e8fed-053a-477c-be42-ec4037109be6", entry => - { - entry.SetPriority(CacheItemPriority.NeverRemove); - - return EF.CompileAsyncQuery((TContext context, TKey key) => - 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(key) - select token); - }); - - return ImmutableArray.CreateRange(await query(Context, + return ImmutableArray.CreateRange(await FindByApplicationId(Context, ConvertIdentifierFromString(identifier)).ToListAsync(cancellationToken)); } + /// + /// Exposes a compiled query allowing to retrieve the list of + /// tokens corresponding to the specified authorization identifier. + /// + private static Func> 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. + // 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); + /// /// Retrieves the list of tokens corresponding to the specified authorization identifier. /// @@ -239,28 +274,22 @@ namespace OpenIddict.EntityFrameworkCore throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier)); } - // 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. - var query = Cache.GetOrCreate("bfc92016-710f-492b-bcf0-48452a832950", entry => - { - entry.SetPriority(CacheItemPriority.NeverRemove); - - return EF.CompileAsyncQuery((TContext context, TKey key) => - 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(key) - select token); - }); - - return ImmutableArray.CreateRange(await query(Context, + return ImmutableArray.CreateRange(await FindByAuthorizationId(Context, ConvertIdentifierFromString(identifier)).ToListAsync(cancellationToken)); } + /// + /// Exposes a compiled query allowing to retrieve a token using its unique identifier. + /// + private static 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()); + /// /// Retrieves a token using its unique identifier. /// @@ -277,22 +306,22 @@ namespace OpenIddict.EntityFrameworkCore throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier)); } - var query = Cache.GetOrCreate("a311088f-3b26-4ca7-bbe5-6194d3375777", entry => - { - entry.SetPriority(CacheItemPriority.NeverRemove); - - return EF.CompileAsyncQuery((TContext context, TKey key) => - (from token in context.Set() - .Include(token => token.Application) - .Include(token => token.Authorization) - .AsTracking() - where token.Id.Equals(key) - select token).FirstOrDefault()); - }); - - return query(Context, ConvertIdentifierFromString(identifier)); + return FindById(Context, ConvertIdentifierFromString(identifier)); } + /// + /// Exposes a compiled query allowing to retrieve the list of + /// tokens corresponding to the specified reference identifier. + /// + private static 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()); + /// /// Retrieves the list of tokens corresponding to the specified reference identifier. /// Note: the reference identifier may be hashed or encrypted for security reasons. @@ -310,22 +339,22 @@ namespace OpenIddict.EntityFrameworkCore throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier)); } - var query = Cache.GetOrCreate("26fd749d-9198-466a-b78e-afbc1b4d89d3", entry => - { - entry.SetPriority(CacheItemPriority.NeverRemove); - - return EF.CompileAsyncQuery((TContext context, string id) => - (from token in context.Set() - .Include(token => token.Application) - .Include(token => token.Authorization) - .AsTracking() - where token.ReferenceId == id - select token).FirstOrDefault()); - }); - - return query(Context, identifier); + return FindByReferenceId(Context, identifier); } + /// + /// Exposes a compiled query allowing to retrieve the + /// list of tokens corresponding to the specified subject. + /// + private static Func> 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. /// @@ -342,20 +371,7 @@ namespace OpenIddict.EntityFrameworkCore throw new ArgumentException("The subject cannot be null or empty.", nameof(subject)); } - var query = Cache.GetOrCreate("61a80b05-d069-4e05-b2e2-66d4bc89b492", entry => - { - entry.SetPriority(CacheItemPriority.NeverRemove); - - return EF.CompileAsyncQuery((TContext context, string principal) => - from token in context.Set() - .Include(token => token.Application) - .Include(token => token.Authorization) - .AsTracking() - where token.Subject == principal - select token); - }); - - return ImmutableArray.CreateRange(await query(Context, subject).ToListAsync(cancellationToken)); + return ImmutableArray.CreateRange(await FindBySubject(Context, subject).ToListAsync(cancellationToken)); } ///