Browse Source

Use private static fields instead of IMemoryCache to cache compiled queries

pull/613/head
Kévin Chalet 8 years ago
parent
commit
c3bab03116
  1. 19
      src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs
  2. 19
      src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs
  3. 19
      src/OpenIddict.EntityFramework/Stores/OpenIddictScopeStore.cs
  4. 19
      src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs
  5. 134
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs
  6. 192
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs
  7. 121
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictScopeStore.cs
  8. 186
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs

19
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<OpenIddictEntityFrameworkOptions> options)
: base(cache, context, options)
{
}
}
@ -54,10 +58,14 @@ namespace OpenIddict.EntityFramework
where TContext : DbContext
where TKey : IEquatable<TKey>
{
public OpenIddictApplicationStore([NotNull] IMemoryCache cache, [NotNull] TContext context)
public OpenIddictApplicationStore(
[NotNull] IMemoryCache cache,
[NotNull] TContext context,
[NotNull] IOptionsMonitor<OpenIddictEntityFrameworkOptions> options)
{
Cache = cache;
Context = context;
Options = options;
}
/// <summary>
@ -70,6 +78,11 @@ namespace OpenIddict.EntityFramework
/// </summary>
protected TContext Context { get; }
/// <summary>
/// Gets the options associated with the current store.
/// </summary>
protected IOptionsMonitor<OpenIddictEntityFrameworkOptions> Options { get; }
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TApplication"/> entity.
/// </summary>

19
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<OpenIddictEntityFrameworkOptions> options)
: base(cache, context, options)
{
}
}
@ -54,10 +58,14 @@ namespace OpenIddict.EntityFramework
where TContext : DbContext
where TKey : IEquatable<TKey>
{
public OpenIddictAuthorizationStore([NotNull] IMemoryCache cache, [NotNull] TContext context)
public OpenIddictAuthorizationStore(
[NotNull] IMemoryCache cache,
[NotNull] TContext context,
[NotNull] IOptionsMonitor<OpenIddictEntityFrameworkOptions> options)
{
Cache = cache;
Context = context;
Options = options;
}
/// <summary>
@ -70,6 +78,11 @@ namespace OpenIddict.EntityFramework
/// </summary>
protected TContext Context { get; }
/// <summary>
/// Gets the options associated with the current store.
/// </summary>
protected IOptionsMonitor<OpenIddictEntityFrameworkOptions> Options { get; }
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TApplication"/> entity.
/// </summary>

19
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<TContext> : OpenIddictScopeStore<OpenIddictScope, TContext, string>
where TContext : DbContext
{
public OpenIddictScopeStore([NotNull] IMemoryCache cache, [NotNull] TContext context)
: base(cache, context)
public OpenIddictScopeStore(
[NotNull] IMemoryCache cache,
[NotNull] TContext context,
[NotNull] IOptionsMonitor<OpenIddictEntityFrameworkOptions> options)
: base(cache, context, options)
{
}
}
@ -46,10 +50,14 @@ namespace OpenIddict.EntityFramework
where TContext : DbContext
where TKey : IEquatable<TKey>
{
public OpenIddictScopeStore([NotNull] IMemoryCache cache, [NotNull] TContext context)
public OpenIddictScopeStore(
[NotNull] IMemoryCache cache,
[NotNull] TContext context,
[NotNull] IOptionsMonitor<OpenIddictEntityFrameworkOptions> options)
{
Cache = cache;
Context = context;
Options = options;
}
/// <summary>
@ -62,6 +70,11 @@ namespace OpenIddict.EntityFramework
/// </summary>
protected TContext Context { get; }
/// <summary>
/// Gets the options associated with the current store.
/// </summary>
protected IOptionsMonitor<OpenIddictEntityFrameworkOptions> Options { get; }
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TScope"/> entity.
/// </summary>

19
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<OpenIddictEntityFrameworkOptions> options)
: base(cache, context, options)
{
}
}
@ -54,10 +58,14 @@ namespace OpenIddict.EntityFramework
where TContext : DbContext
where TKey : IEquatable<TKey>
{
public OpenIddictTokenStore([NotNull] IMemoryCache cache, [NotNull] TContext context)
public OpenIddictTokenStore(
[NotNull] IMemoryCache cache,
[NotNull] TContext context,
[NotNull] IOptionsMonitor<OpenIddictEntityFrameworkOptions> options)
{
Cache = cache;
Context = context;
Options = options;
}
/// <summary>
@ -70,6 +78,11 @@ namespace OpenIddict.EntityFramework
/// </summary>
protected TContext Context { get; }
/// <summary>
/// Gets the options associated with the current store.
/// </summary>
protected IOptionsMonitor<OpenIddictEntityFrameworkOptions> Options { get; }
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TApplication"/> entity.
/// </summary>

134
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<OpenIddictEntityFrameworkCoreOptions> options)
: base(cache, context, options)
{
}
}
@ -51,8 +56,11 @@ namespace OpenIddict.EntityFrameworkCore
where TContext : DbContext
where TKey : IEquatable<TKey>
{
public OpenIddictApplicationStore([NotNull] IMemoryCache cache, [NotNull] TContext context)
: base(cache, context)
public OpenIddictApplicationStore(
[NotNull] IMemoryCache cache,
[NotNull] TContext context,
[NotNull] IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options)
: base(cache, context, options)
{
}
}
@ -72,10 +80,14 @@ namespace OpenIddict.EntityFrameworkCore
where TContext : DbContext
where TKey : IEquatable<TKey>
{
public OpenIddictApplicationStore([NotNull] IMemoryCache cache, [NotNull] TContext context)
public OpenIddictApplicationStore(
[NotNull] IMemoryCache cache,
[NotNull] TContext context,
[NotNull] IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options)
{
Cache = cache;
Context = context;
Options = options;
}
/// <summary>
@ -88,6 +100,11 @@ namespace OpenIddict.EntityFrameworkCore
/// </summary>
protected TContext Context { get; }
/// <summary>
/// Gets the options associated with the current store.
/// </summary>
protected IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> Options { get; }
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TApplication"/> entity.
/// </summary>
@ -256,6 +273,15 @@ namespace OpenIddict.EntityFrameworkCore
}
}
/// <summary>
/// Exposes a compiled query allowing to retrieve an application using its client identifier.
/// </summary>
private static 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>
/// Retrieves an application using its client identifier.
/// </summary>
@ -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<TApplication>().AsTracking()
where application.ClientId == id
select application).FirstOrDefault());
});
return query(Context, identifier);
return FindByClientId(Context, identifier);
}
/// <summary>
/// Exposes a compiled query allowing to retrieve an application using its unique identifier.
/// </summary>
private static 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>
/// Retrieves an application using its unique identifier.
/// </summary>
@ -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<TApplication>().AsTracking()
where application.Id.Equals(key)
select application).FirstOrDefault());
});
return query(Context, ConvertIdentifierFromString(identifier));
return FindById(Context, ConvertIdentifierFromString(identifier));
}
/// <summary>
/// Exposes a compiled query allowing to retrieve all the applications
/// associated with the specified post_logout_redirect_uri.
/// </summary>
private static Func<TContext, string, AsyncEnumerable<TApplication>> 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>
/// Retrieves all the applications associated with the specified post_logout_redirect_uri.
/// </summary>
@ -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<TApplication>().AsTracking()
where application.PostLogoutRedirectUris.Contains(uri)
select application);
});
var builder = ImmutableArray.CreateBuilder<TApplication>();
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();
}
/// <summary>
/// Exposes a compiled query allowing to retrieve all the
/// applications associated with the specified redirect_uri.
/// </summary>
private static Func<TContext, string, AsyncEnumerable<TApplication>> 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<TApplication>().AsTracking()
where application.RedirectUris.Contains(address)
select application);
/// <summary>
/// Retrieves all the applications associated with the specified redirect_uri.
/// </summary>
@ -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<TApplication>().AsTracking()
where application.RedirectUris.Contains(uri)
select application);
});
var builder = ImmutableArray.CreateBuilder<TApplication>();
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))
{

192
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<OpenIddictEntityFrameworkCoreOptions> options)
: base(cache, context, options)
{
}
}
@ -51,8 +56,11 @@ namespace OpenIddict.EntityFrameworkCore
where TContext : DbContext
where TKey : IEquatable<TKey>
{
public OpenIddictAuthorizationStore([NotNull] IMemoryCache cache, [NotNull] TContext context)
: base(cache, context)
public OpenIddictAuthorizationStore(
[NotNull] IMemoryCache cache,
[NotNull] TContext context,
[NotNull] IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options)
: base(cache, context, options)
{
}
}
@ -72,10 +80,14 @@ namespace OpenIddict.EntityFrameworkCore
where TContext : DbContext
where TKey : IEquatable<TKey>
{
public OpenIddictAuthorizationStore([NotNull] IMemoryCache cache, [NotNull] TContext context)
public OpenIddictAuthorizationStore(
[NotNull] IMemoryCache cache,
[NotNull] TContext context,
[NotNull] IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options)
{
Cache = cache;
Context = context;
Options = options;
}
/// <summary>
@ -88,6 +100,11 @@ namespace OpenIddict.EntityFrameworkCore
/// </summary>
protected TContext Context { get; }
/// <summary>
/// Gets the options associated with the current store.
/// </summary>
protected IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> Options { get; }
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TApplication"/> entity.
/// </summary>
@ -232,6 +249,24 @@ 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 Func<TContext, TKey, string, AsyncEnumerable<TAuthorization>> 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>
/// 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));
}
/// <summary>
/// Exposes a compiled query allowing to retrieve the authorizations matching the specified parameters.
/// </summary>
private static Func<TContext, TKey, string, string, AsyncEnumerable<TAuthorization>> 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<TAuthorization>()
.Include(authorization => authorization.Application)
.AsTracking()
where authorization.Subject == principal
join application in context.Set<TApplication>().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<TAuthorization>()
.Include(authorization => authorization.Application)
.AsTracking()
where authorization.Subject == subject && authorization.Status == status
join application in context.Set<TApplication>().AsTracking() on authorization.Application.Id equals application.Id
where application.Id.Equals(identifier)
select authorization);
/// <summary>
/// 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));
}
/// <summary>
/// Exposes a compiled query allowing to retrieve the authorizations matching the specified parameters.
/// </summary>
private static Func<TContext, TKey, string, string, string, AsyncEnumerable<TAuthorization>> 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<TAuthorization>()
.Include(authorization => authorization.Application)
.AsTracking()
where authorization.Subject == principal && authorization.Status == state
join application in context.Set<TApplication>().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<TAuthorization>()
.Include(authorization => authorization.Application)
.AsTracking()
where authorization.Subject == subject &&
authorization.Status == status &&
authorization.Type == type
join application in context.Set<TApplication>().AsTracking() on authorization.Application.Id equals application.Id
where application.Id.Equals(identifier)
select authorization);
/// <summary>
/// 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<TAuthorization>()
.Include(authorization => authorization.Application)
.AsTracking()
where authorization.Subject == principal &&
authorization.Status == state &&
authorization.Type == kind
join application in context.Set<TApplication>().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));
}
/// <summary>
/// Exposes a compiled query allowing to retrieve an authorization using its unique identifier.
/// </summary>
private static Func<TContext, TKey, Task<TAuthorization>> FindById =
EF.CompileAsyncQuery((TContext context, TKey identifier) =>
(from authorization in context.Set<TAuthorization>()
.Include(authorization => authorization.Application)
.AsTracking()
where authorization.Id.Equals(identifier)
select authorization).FirstOrDefault());
/// <summary>
/// Retrieves an authorization using its unique identifier.
/// </summary>
@ -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<TAuthorization>()
.Include(authorization => authorization.Application)
.AsTracking()
where authorization.Id.Equals(key)
select authorization).FirstOrDefault());
});
return query(Context, ConvertIdentifierFromString(identifier));
return FindById(Context, ConvertIdentifierFromString(identifier));
}
/// <summary>
/// Retrieves all the authorizations corresponding to the specified subject.
/// Exposes a compiled query allowing to retrieve all the
/// authorizations corresponding to the specified subject.
/// </summary>
private static Func<TContext, string, AsyncEnumerable<TAuthorization>> 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>
/// Retrieves .
/// </summary>
/// <param name="subject">The subject associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
@ -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<TAuthorization>()
.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));
}
/// <summary>

121
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<TContext> : OpenIddictScopeStore<OpenIddictScope, TContext, string>
where TContext : DbContext
{
public OpenIddictScopeStore([NotNull] IMemoryCache cache, [NotNull] TContext context)
: base(cache, context)
public OpenIddictScopeStore(
[NotNull] IMemoryCache cache,
[NotNull] TContext context,
[NotNull] IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options)
: base(cache, context, options)
{
}
}
@ -43,8 +48,11 @@ namespace OpenIddict.EntityFrameworkCore
where TContext : DbContext
where TKey : IEquatable<TKey>
{
public OpenIddictScopeStore([NotNull] IMemoryCache cache, [NotNull] TContext context)
: base(cache, context)
public OpenIddictScopeStore(
[NotNull] IMemoryCache cache,
[NotNull] TContext context,
[NotNull] IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options)
: base(cache, context, options)
{
}
}
@ -60,10 +68,14 @@ namespace OpenIddict.EntityFrameworkCore
where TContext : DbContext
where TKey : IEquatable<TKey>
{
public OpenIddictScopeStore([NotNull] IMemoryCache cache, [NotNull] TContext context)
public OpenIddictScopeStore(
[NotNull] IMemoryCache cache,
[NotNull] TContext context,
[NotNull] IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options)
{
Cache = cache;
Context = context;
Options = options;
}
/// <summary>
@ -76,6 +88,11 @@ namespace OpenIddict.EntityFrameworkCore
/// </summary>
protected TContext Context { get; }
/// <summary>
/// Gets the options associated with the current store.
/// </summary>
protected IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> Options { get; }
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TScope"/> entity.
/// </summary>
@ -163,6 +180,15 @@ namespace OpenIddict.EntityFrameworkCore
}
}
/// <summary>
/// Exposes a compiled query allowing to retrieve a scope using its unique identifier.
/// </summary>
private static 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>
/// Retrieves a scope using its unique identifier.
/// </summary>
@ -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<TScope>().AsTracking()
where scope.Id.Equals(key)
select scope).FirstOrDefault());
});
return query(Context, ConvertIdentifierFromString(identifier));
return FindById(Context, ConvertIdentifierFromString(identifier));
}
/// <summary>
/// Exposes a compiled query allowing to retrieve a scope using its name.
/// </summary>
private static 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>
/// Retrieves a scope using its name.
/// </summary>
@ -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<TScope>().AsTracking()
where scope.Name == id
select scope).FirstOrDefault());
});
return query(Context, name);
return FindByName(Context, name);
}
/// <summary>
/// Exposes a compiled query allowing to retrieve a list of scopes using their name.
/// </summary>
private static Func<TContext, ImmutableArray<string>, AsyncEnumerable<TScope>> FindByNames =
EF.CompileAsyncQuery((TContext context, ImmutableArray<string> names) =>
from scope in context.Set<TScope>().AsTracking()
where names.Contains(scope.Name)
select scope);
/// <summary>
/// Retrieves a list of scopes using their name.
/// </summary>
@ -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<string> ids) =>
from scope in context.Set<TScope>().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));
}
/// <summary>
/// Exposes a compiled query allowing to retrieve all the scopes that contain the specified resource.
/// </summary>
private static Func<TContext, string, AsyncEnumerable<TScope>> 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>
/// Retrieves all the scopes that contain the specified resource.
/// </summary>
@ -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<TScope>().AsTracking()
where scope.Resources.Contains(value)
select scope);
});
var builder = ImmutableArray.CreateBuilder<TScope>();
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))

186
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<OpenIddictEntityFrameworkCoreOptions> options)
: base(cache, context, options)
{
}
}
@ -51,8 +56,11 @@ namespace OpenIddict.EntityFrameworkCore
where TContext : DbContext
where TKey : IEquatable<TKey>
{
public OpenIddictTokenStore([NotNull] IMemoryCache cache, [NotNull] TContext context)
: base(cache, context)
public OpenIddictTokenStore(
[NotNull] IMemoryCache cache,
[NotNull] TContext context,
[NotNull] IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options)
: base(cache, context, options)
{
}
}
@ -72,10 +80,14 @@ namespace OpenIddict.EntityFrameworkCore
where TContext : DbContext
where TKey : IEquatable<TKey>
{
public OpenIddictTokenStore([NotNull] IMemoryCache cache, [NotNull] TContext context)
public OpenIddictTokenStore(
[NotNull] IMemoryCache cache,
[NotNull] TContext context,
[NotNull] IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options)
{
Cache = cache;
Context = context;
Options = options;
}
/// <summary>
@ -88,6 +100,11 @@ namespace OpenIddict.EntityFrameworkCore
/// </summary>
protected TContext Context { get; }
/// <summary>
/// Gets the options associated with the current store.
/// </summary>
protected IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> Options { get; }
/// <summary>
/// Gets the database set corresponding to the <typeparamref name="TApplication"/> entity.
/// </summary>
@ -185,6 +202,24 @@ namespace OpenIddict.EntityFrameworkCore
}
}
/// <summary>
/// Exposes a compiled query allowing to retrieve the list of
/// tokens corresponding to the specified application identifier.
/// </summary>
private static Func<TContext, TKey, AsyncEnumerable<TToken>> 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<TToken>()
.Include(token => token.Application)
.Include(token => token.Authorization)
.AsTracking()
join application in context.Set<TApplication>().AsTracking() on token.Application.Id equals application.Id
where application.Id.Equals(identifier)
select token);
/// <summary>
/// Retrieves the list of tokens corresponding to the specified application identifier.
/// </summary>
@ -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<TToken>()
.Include(token => token.Application)
.Include(token => token.Authorization)
.AsTracking()
join application in context.Set<TApplication>().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));
}
/// <summary>
/// Exposes a compiled query allowing to retrieve the list of
/// tokens corresponding to the specified authorization identifier.
/// </summary>
private static Func<TContext, TKey, AsyncEnumerable<TToken>> 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<TToken>()
.Include(token => token.Application)
.Include(token => token.Authorization)
.AsTracking()
join authorization in context.Set<TAuthorization>().AsTracking() on token.Authorization.Id equals authorization.Id
where authorization.Id.Equals(identifier)
select token);
/// <summary>
/// Retrieves the list of tokens corresponding to the specified authorization identifier.
/// </summary>
@ -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<TToken>()
.Include(token => token.Application)
.Include(token => token.Authorization)
.AsTracking()
join authorization in context.Set<TAuthorization>().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));
}
/// <summary>
/// Exposes a compiled query allowing to retrieve a token using its unique identifier.
/// </summary>
private static Func<TContext, TKey, Task<TToken>> FindById =
EF.CompileAsyncQuery((TContext context, TKey identifier) =>
(from token in context.Set<TToken>()
.Include(token => token.Application)
.Include(token => token.Authorization)
.AsTracking()
where token.Id.Equals(identifier)
select token).FirstOrDefault());
/// <summary>
/// Retrieves a token using its unique identifier.
/// </summary>
@ -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<TToken>()
.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));
}
/// <summary>
/// Exposes a compiled query allowing to retrieve the list of
/// tokens corresponding to the specified reference identifier.
/// </summary>
private static 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>
/// 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<TToken>()
.Include(token => token.Application)
.Include(token => token.Authorization)
.AsTracking()
where token.ReferenceId == id
select token).FirstOrDefault());
});
return query(Context, identifier);
return FindByReferenceId(Context, identifier);
}
/// <summary>
/// Exposes a compiled query allowing to retrieve the
/// list of tokens corresponding to the specified subject.
/// </summary>
private static Func<TContext, string, AsyncEnumerable<TToken>> 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>
/// Retrieves the list of tokens corresponding to the specified subject.
/// </summary>
@ -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<TToken>()
.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));
}
/// <summary>

Loading…
Cancel
Save