Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,19 @@ public virtual async Task<IActionResult> QueryAsync(CancellationToken cancellati
// to switch to in-memory processing for those queries. This is done by calling ToListAsync() on the
// IQueryable. This is not ideal, but it is the only way to support all of the OData query options.
IEnumerable<object>? results = null;
ExecuteQueryWithClientEvaluation(dataset, ds => results = (IEnumerable<object>)queryOptions.ApplyTo(ds, querySettings));
await ExecuteQueryWithClientEvaluationAsync(dataset, ds =>
{
results = (IEnumerable<object>)queryOptions.ApplyTo(ds, querySettings);
return Task.CompletedTask;
});

int count = 0;
FilterQueryOption? filter = queryOptions.Filter;
ExecuteQueryWithClientEvaluation(dataset, ds => { IQueryable<TEntity> q = (IQueryable<TEntity>)(filter?.ApplyTo(ds, new ODataQuerySettings()) ?? ds); count = q.Count(); });
await ExecuteQueryWithClientEvaluationAsync(dataset, async ds =>
{
IQueryable<TEntity> q = (IQueryable<TEntity>)(filter?.ApplyTo(ds, new ODataQuerySettings()) ?? ds);
count = await CountAsync(q, cancellationToken);
});

PagedResult result = BuildPagedResult(queryOptions, results, count);
Logger.LogInformation("Query: {Count} items being returned", result.Items.Count());
Expand Down Expand Up @@ -194,13 +202,13 @@ internal static string CreateNextLink(string queryString, int skip = 0, int top
/// <param name="reason">The reason if the client-side evaluator throws.</param>
/// <param name="clientSideEvaluator">The client-side evaluator</param>
[NonAction]
internal void CatchClientSideEvaluationException(Exception ex, string reason, Action clientSideEvaluator)
internal async Task CatchClientSideEvaluationExceptionAsync(Exception ex, string reason, Func<Task> clientSideEvaluator)
{
if (IsClientSideEvaluationException(ex) || IsClientSideEvaluationException(ex.InnerException))
{
try
{
clientSideEvaluator.Invoke();
await clientSideEvaluator.Invoke();
}
catch (Exception err)
{
Expand All @@ -220,18 +228,18 @@ internal void CatchClientSideEvaluationException(Exception ex, string reason, Ac
/// <param name="dataset">The dataset to be evaluated.</param>
/// <param name="evaluator">The base evaluation to be performed.</param>
[NonAction]
internal void ExecuteQueryWithClientEvaluation(IQueryable<TEntity> dataset, Action<IQueryable<TEntity>> evaluator)
internal async Task ExecuteQueryWithClientEvaluationAsync(IQueryable<TEntity> dataset, Func<IQueryable<TEntity>, Task> evaluator)
{
try
{
evaluator.Invoke(dataset);
await evaluator.Invoke(dataset);
}
catch (Exception ex) when (!Options.DisableClientSideEvaluation)
{
CatchClientSideEvaluationException(ex, "executing query", () =>
await CatchClientSideEvaluationExceptionAsync(ex, "executing query", async () =>
{
Logger.LogWarning("Error while executing query: possible client-side evaluation ({Message})", ex.InnerException?.Message ?? ex.Message);
evaluator.Invoke(dataset.ToList().AsQueryable());
await evaluator.Invoke(dataset.ToList().AsQueryable());
});
}
}
Expand All @@ -245,4 +253,18 @@ internal void ExecuteQueryWithClientEvaluation(IQueryable<TEntity> dataset, Acti
[SuppressMessage("Roslynator", "RCS1158:Static member in generic type should use a type parameter.")]
internal static bool IsClientSideEvaluationException(Exception? ex)
=> ex is not null and (InvalidOperationException or NotSupportedException);

/// <summary>
/// This is an overridable method that calls Count() on the provided queryable. You can override
/// this to calls a provider-specific count mechanism (e.g. CountAsync().
/// </summary>
/// <param name="query"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
[NonAction]
public virtual Task<int> CountAsync(IQueryable<TEntity> query, CancellationToken cancellationToken)
{
int result = query.Count();
return Task.FromResult(result);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,167 +41,173 @@ public void BuildPagedResult_NulLArg_BuildsPagedResult()

#region CatchClientSideEvaluationException
[Fact]
public void CatchClientSideEvaluationException_NotCCEE_ThrowsOriginalException()
public async Task CatchClientSideEvaluationException_NotCCEE_ThrowsOriginalException()
{
TableController<InMemoryMovie> controller = new() { Repository = new InMemoryRepository<InMemoryMovie>() };
ApplicationException exception = new("Original exception");

static void evaluator() { throw new ApplicationException("In evaluator"); }
static Task evaluator() { throw new ApplicationException("In evaluator"); }

Action act = () => controller.CatchClientSideEvaluationException(exception, "foo", evaluator);
act.Should().Throw<ApplicationException>().WithMessage("Original exception");
Func<Task> act = async () => await controller.CatchClientSideEvaluationExceptionAsync(exception, "foo", evaluator);
(await act.Should().ThrowAsync<ApplicationException>()).WithMessage("Original exception");
}

[Fact]
public void CatchClientSideEvaluationException_NotCCEE_WithInner_ThrowsOriginalException()
public async Task CatchClientSideEvaluationException_NotCCEE_WithInner_ThrowsOriginalException()
{
TableController<InMemoryMovie> controller = new() { Repository = new InMemoryRepository<InMemoryMovie>() };
ApplicationException exception = new("Original exception", new ApplicationException());

static void evaluator() { throw new ApplicationException("In evaluator"); }
static Task evaluator() { throw new ApplicationException("In evaluator"); }

Action act = () => controller.CatchClientSideEvaluationException(exception, "foo", evaluator);
act.Should().Throw<ApplicationException>().WithMessage("Original exception");
Func<Task> act = async () => await controller.CatchClientSideEvaluationExceptionAsync(exception, "foo", evaluator);
(await act.Should().ThrowAsync<ApplicationException>()).WithMessage("Original exception");
}

[Fact]
public void CatchClientSideEvaluationException_CCEE_ThrowsEvaluatorException()
public async Task CatchClientSideEvaluationException_CCEE_ThrowsEvaluatorException()
{
TableController<InMemoryMovie> controller = new() { Repository = new InMemoryRepository<InMemoryMovie>() };
NotSupportedException exception = new("Original exception", new ApplicationException("foo"));

static void evaluator() { throw new ApplicationException("In evaluator"); }
static Task evaluator() { throw new ApplicationException("In evaluator"); }

Action act = () => controller.CatchClientSideEvaluationException(exception, "foo", evaluator);
act.Should().Throw<ApplicationException>().WithMessage("In evaluator");
Func<Task> act = async () => await controller.CatchClientSideEvaluationExceptionAsync(exception, "foo", evaluator);
(await act.Should().ThrowAsync<ApplicationException>()).WithMessage("In evaluator");
}

[Fact]
public void CatchClientSideEvaluationException_CCEEInner_ThrowsEvaluatorException()
public async Task CatchClientSideEvaluationException_CCEEInner_ThrowsEvaluatorException()
{
TableController<InMemoryMovie> controller = new() { Repository = new InMemoryRepository<InMemoryMovie>() };
ApplicationException exception = new("Original exception", new NotSupportedException("foo"));

static void evaluator() { throw new ApplicationException("In evaluator"); }
static Task evaluator() { throw new ApplicationException("In evaluator"); }

Action act = () => controller.CatchClientSideEvaluationException(exception, "foo", evaluator);
act.Should().Throw<ApplicationException>().WithMessage("In evaluator");
Func<Task> act = async () => await controller.CatchClientSideEvaluationExceptionAsync(exception, "foo", evaluator);
(await act.Should().ThrowAsync<ApplicationException>()).WithMessage("In evaluator");
}

[Fact]
public void CatchClientSideEvaluationException_CCEE_ExecutesEvaluator()
public async Task CatchClientSideEvaluationException_CCEE_ExecutesEvaluator()
{
bool isExecuted = false;
TableController<InMemoryMovie> controller = new() { Repository = new InMemoryRepository<InMemoryMovie>() };
NotSupportedException exception = new("Original exception", new ApplicationException("foo"));
Action act = () => controller.CatchClientSideEvaluationException(exception, "foo", () => isExecuted = true);
act.Should().NotThrow();

Func<Task> act = async () => await controller.CatchClientSideEvaluationExceptionAsync(exception, "foo", () => { isExecuted = true; return Task.CompletedTask; });
await act.Should().NotThrowAsync();
isExecuted.Should().BeTrue();
}

[Fact]
public void CatchClientSideEvaluationException_CCEEInner_ExecutesEvaluator()
public async Task CatchClientSideEvaluationException_CCEEInner_ExecutesEvaluator()
{
bool isExecuted = false;
TableController<InMemoryMovie> controller = new() { Repository = new InMemoryRepository<InMemoryMovie>() };
ApplicationException exception = new("Original exception", new NotSupportedException("foo"));
Action act = () => controller.CatchClientSideEvaluationException(exception, "foo", () => isExecuted = true);
act.Should().NotThrow();

Func<Task> act = async () => await controller.CatchClientSideEvaluationExceptionAsync(exception, "foo", () => { isExecuted = true; return Task.CompletedTask; });
await act.Should().NotThrowAsync();
isExecuted.Should().BeTrue();
}
#endregion

#region ExecuteQueryWithClientEvaluation
[Fact]
public void ExecuteQueryWithClientEvaluation_ExecutesServiceSide()
public async Task ExecuteQueryWithClientEvaluation_ExecutesServiceSide()
{
TableController<InMemoryMovie> controller = new() { Repository = new InMemoryRepository<InMemoryMovie>() };
controller.Options.DisableClientSideEvaluation = true;

int evaluations = 0;
void evaluator(IQueryable<InMemoryMovie> dataset)
Task evaluator(IQueryable<InMemoryMovie> dataset)
{
evaluations++;
// if (evaluations == 1) throw new NotSupportedException("Server side");
// if (evaluations == 2) throw new NotSupportedException("Client side");
return Task.CompletedTask;
}

List<InMemoryMovie> dataset = [];

Action act = () => controller.ExecuteQueryWithClientEvaluation(dataset.AsQueryable(), evaluator);
Func<Task> act = async () => await controller.ExecuteQueryWithClientEvaluationAsync(dataset.AsQueryable(), evaluator);

act.Should().NotThrow();
await act.Should().NotThrowAsync();
evaluations.Should().Be(1);
}

[Fact]
public void ExecuteQueryWithClientEvaluation_ThrowsServiceSide_WhenClientEvaluationDisabled()
public async Task ExecuteQueryWithClientEvaluation_ThrowsServiceSide_WhenClientEvaluationDisabled()
{
TableController<InMemoryMovie> controller = new() { Repository = new InMemoryRepository<InMemoryMovie>() };
controller.Options.DisableClientSideEvaluation = true;

int evaluations = 0;
#pragma warning disable IDE0011 // Add braces
void evaluator(IQueryable<InMemoryMovie> dataset)
Task evaluator(IQueryable<InMemoryMovie> dataset)
{
evaluations++;
if (evaluations == 1) throw new NotSupportedException("Server side");
if (evaluations == 2) throw new NotSupportedException("Client side");
return Task.CompletedTask;
}
#pragma warning restore IDE0011 // Add braces

List<InMemoryMovie> dataset = [];

Action act = () => controller.ExecuteQueryWithClientEvaluation(dataset.AsQueryable(), evaluator);
Func<Task> act = async () => await controller.ExecuteQueryWithClientEvaluationAsync(dataset.AsQueryable(), evaluator);

act.Should().Throw<NotSupportedException>().WithMessage("Server side");
(await act.Should().ThrowAsync<NotSupportedException>()).WithMessage("Server side");
}

[Fact]
public void ExecuteQueryWithClientEvaluation_ExecutesClientSide_WhenClientEvaluationEnabled()
public async Task ExecuteQueryWithClientEvaluation_ExecutesClientSide_WhenClientEvaluationEnabled()
{
TableController<InMemoryMovie> controller = new() { Repository = new InMemoryRepository<InMemoryMovie>() };
controller.Options.DisableClientSideEvaluation = false;

int evaluations = 0;
#pragma warning disable IDE0011 // Add braces
void evaluator(IQueryable<InMemoryMovie> dataset)
Task evaluator(IQueryable<InMemoryMovie> dataset)
{
evaluations++;
if (evaluations == 1) throw new NotSupportedException("Server side");
//if (evaluations == 2) throw new NotSupportedException("Client side");
return Task.CompletedTask;
}
#pragma warning restore IDE0011 // Add braces

List<InMemoryMovie> dataset = [];

Action act = () => controller.ExecuteQueryWithClientEvaluation(dataset.AsQueryable(), evaluator);
Func<Task> act = async () => await controller.ExecuteQueryWithClientEvaluationAsync(dataset.AsQueryable(), evaluator);

act.Should().NotThrow();
await act.Should().NotThrowAsync();
evaluations.Should().Be(2);
}

[Fact]
public void ExecuteQueryWithClientEvaluation_ThrowsClientSide_WhenClientEvaluationEnabled()
public async Task ExecuteQueryWithClientEvaluation_ThrowsClientSide_WhenClientEvaluationEnabled()
{
TableController<InMemoryMovie> controller = new() { Repository = new InMemoryRepository<InMemoryMovie>() };
controller.Options.DisableClientSideEvaluation = false;

int evaluations = 0;
#pragma warning disable IDE0011 // Add braces
void evaluator(IQueryable<InMemoryMovie> dataset)
Task evaluator(IQueryable<InMemoryMovie> dataset)
{
evaluations++;
if (evaluations == 1) throw new NotSupportedException("Server side", new ApplicationException("Inner exception"));
if (evaluations == 2) throw new NotSupportedException("Client side");
return Task.CompletedTask;
}
#pragma warning restore IDE0011 // Add braces

List<InMemoryMovie> dataset = [];

Action act = () => controller.ExecuteQueryWithClientEvaluation(dataset.AsQueryable(), evaluator);
Func<Task> act = async () => await controller.ExecuteQueryWithClientEvaluationAsync(dataset.AsQueryable(), evaluator);

act.Should().Throw<NotSupportedException>().WithMessage("Client side");
(await act.Should().ThrowAsync<NotSupportedException>()).WithMessage("Client side");
evaluations.Should().Be(2);
}
#endregion
Expand Down
Loading