Sebastian Zaklada Sebastian Zaklada - 3 months ago 24
C# Question

Create generic filter of IQueryable<T> where the filter uses property defined in ISomeInterface

I am implementing a custom processing pipeline for a multi-tenant web API. This creates a requirement where the last step of the data retrieval pipeline must be ensuring, that only data specific to a given tenant is returned to the caller, which I thought would be best done using a

For data retrieval, the process looks like this:


  • HTTP call hits the thin controller, where it's resolved to a CQS-compliant query and passed to the mediator for handling

  • mediator is configured to feed all requests through the pipeline, which has a few steps


    1. Validation

    2. Caching

    3. Any Query specific Pre handlers

    4. Handling the Query in the handler (returns
      IQueryable<TEntity>
      )

    5. Any Query specific Post handlers

    6. Returning to the caller (returns
      IQueryable<TEntity>
      )




My domain model has a bunch of classes, which inherit from a base class that is decorated with a simple IMultitenantEntity interface.

public interface IMultitenantEntity
{
long TenantId { get; set; }
}


My idea was to inject another Post handler, specific to all pipeline requests which return
IQueryable<TEntity>
where
TEntity
implements
IMultitenantEntity
interface. Simple enough, I can't get it to work due to casting/type issues. I am sure that my mind just got stuck in an infinite loop of dumb ideas and I need someone to please get me out of this loop, thank you very much :)

Here is my Post handler as it is right now:

public interface IAsyncQueryablePostRequestHandler<TResponse>
{
Task Handle(ref TResponse response);
}

public class PostTenantQueryableFilterHandler<TResponse> : IAsyncQueryablePostRequestHandler<TResponse>
where TResponse : IQueryable<IMultitenantEntity>
{
public Task Handle(ref TResponse response)
{
response = (TResponse)response.Where(t => t.TenantId == 1);
return Task.FromResult(1);
}
}


Now imagine, that the a request comes through the pipeline and the query handler returns IQueryable. Not surprisingly, once we hit the above handler, we get an exception:


Unable to cast object of type
'
System.Data.Entity.Infrastructure.DbQuery'1[IMultitenantEntity]
'
to type
'
System.Linq.IQueryable'1[Game]
'.


If we debug the
PostTenantQueryableFilterHandler.Handle()
method, we can see that TResponse is of
IQueryable<Game>
as intended, but to be able to include the
Where(t => t.TenantId == 1)
part, I added
where TResponse : IQueryable<IMultitenantEntity>
, and this causes the result of the LINQ
Where()
to be of the
IQueryable<IMultitenantEntity>
type, which is much more generic than
IQueryable<Game>
, hence it's not possible to cast from
IQueryable<IMultitenantEntity>
to
IQueryable<Game>
... and here is where I am stuck in my infinite mind loop.

Help! :)

EDIT:

I was able to wire the pipeline to use the following:

public class PostTenantQueryableFilterHandler<TResponse, TEntity> : IAsyncQueryablePostRequestHandler<TResponse, TEntity>
where TResponse : IQueryable<TEntity>
where TEntity : IMultitenantEntity


which enabled the possibility of

var intermediary = response.Where(t => t.TenantId == 1).Cast<TEntity>();


but it's still not enough, on

response = (TResponse)intermediary;


same I get same old


Unable to cast object of type
'
System.Data.Entity.Infrastructure.DbQuery'1[IMultitenantEntity]
'
to type
'
System.Linq.IQueryable'1[Game]
'.


What should I do so that the
TResponse
type is not scaled down to
IQueryable<IMultitenantEntity>
but I would still be able to add the missing WHERE predicate?

EDIT 2:

So the real limitation here is that the calling method lives within the class which does not know anything about the
TEntity
as a separate type.

When I said, that the a request comes through the pipeline and the query handler returns IQueryable it was not detailed enough. The thing is, the pipeline is capable of processing query handlers which can have any return types, not only
IQueryable<T>
. So what the pipeline knows is the type of the request (a query that's feeded through it) and the type of the response to a handled request. So when I wired the
PostTenantQueryableFilterHandler<TResponse, TEntity>
in the pipeline,
TEntity
was always
IMultitenantEntity
, which is back to square one -
PostTenantQueryableFilterHandler
does not know the concrete type of
TEntity
and the pipeline is not capable of providing it.

What we know is a concrete type of
TResponse
and that it implements `IQueryable'.

Long story short, I am still looking for a way to add the
.Where(t => t.TenantId == 1)
filter to the response. Thoughts, anyone?

EDIT 3:

So that everything is super-clear, I thought it would be benefitial to provide a little bit of background about how
PostTenantQueryableFilterHandler
is called.

As mentioned before, all requests are feeded through a common processing pipeline (separate async na sync implementations are available). The pipeline class signature is as follows:

public class AsyncMediatorPipeline<TRequest, TResponse>
: IAsyncRequestHandler<TRequest, TResponse>
where TRequest : IAsyncRequest<TResponse>


and the constructor, as it is now:

public AsyncMediatorPipeline(
IAsyncRequestHandler inner,
IAsyncPreRequestHandler[] preRequestHandlers,
IAsyncPostRequestHandler[] postRequestHandlers,
IAsyncQueryablePostRequestHandler[] postQueryableRequestHandlers
)

all constructor parameters are injected with AutoFac, the
IAsyncQueryablePostRequestHandler<,>
is configured as:

builder.RegisterGeneric(typeof(PostTenantQueryableFilterHandler<,>))
.As(typeof(IAsyncQueryablePostRequestHandler<,>))
.SingleInstance();


The pipeline class handles all requests in its own
Handle
method:

public async Task<TResponse> Handle(TRequest message)
{
// PRE handlers
foreach (var preRequestHandler in _preRequestHandlers)
{
await preRequestHandler.Handle(message);
}

// Request handler (this one is also decorated with additional pipeline, where all cross-cutting concerns such as validation, caching, requests logging etc. are handled
var result = await _inner.Handle(message);

// Our stubborn IQueryable<IMultitenantEntity> compatibile Where() filter handler
foreach (var postQueryableRequestHandler in _postQueryableRequestHandlers)
{
await postQueryableRequestHandler.Handle(ref result);
}

// POST handlers
foreach (var postRequestHandler in _postRequestHandlers)
{
await postRequestHandler.Handle(message, result);
}

return result;
}


I have two separate "post" handlers, because generic "post" handler does not allow to manipulate the response

public interface IAsyncPostRequestHandler<in TRequest, in TResponse>
{
Task Handle(TRequest request, TResponse response);
}


whereas the Queryable one does

public interface IAsyncQueryablePostRequestHandler<TResponse, in TEntity>
{
Task Handle(ref TResponse response);
}


Hopefully this sheds a little bit more light on the issue.

Answer

Based on your requirements, I'm afraid you have to resort to manual non generic expression/query building.

For instance, something like this should do the trick:

public interface IAsyncQueryablePostRequestHandler<TResponse>
{
    Task Handle(ref TResponse response);
}

public class PostTenantQueryableFilterHandler<TResponse> : IAsyncQueryablePostRequestHandler<TResponse>
    where TResponse : IQueryable<IMultitenantEntity>
{
    public Task Handle(ref TResponse response)
    {
        var parameter = Expression.Parameter(response.ElementType, "t");
        var predicate = Expression.Lambda(
            Expression.Equal(
                Expression.Property(parameter, "TenantId"),
                Expression.Constant(1)),
            parameter);
        var whereCall = Expression.Call(
            typeof(Queryable), "Where", new[] { parameter.Type },
            response.Expression, Expression.Quote(predicate));
        response = (TResponse)response.Provider.CreateQuery(whereCall);
        return Task.FromResult(1);
    }
}

The key point is that you can get the actual TEntity type from the passed IQueryable (ElementType property), but since you can't use it to call generic methods (because all you have is a Type object), you have to compose both Where predicate and the Where call using the presented non generic techniques.

Update: Here is another more elegant (although most likely slower) solution based on dynamic dispatch feature:

public interface IAsyncQueryablePostRequestHandler<TResponse>
{
    Task Handle(ref TResponse response);
}

public class PostTenantQueryableFilterHandler<TResponse> : IAsyncQueryablePostRequestHandler<TResponse>
    where TResponse : IQueryable<IMultitenantEntity>
{
    public Task Handle(ref TResponse response)
    {
        response = PostTenantQueryableFilter.Handle((dynamic)response);
        return Task.FromResult(1);
    }
}

static class PostTenantQueryableFilter
{
    public static IQueryable<TEntity> Handle<TEntity>(IQueryable<TEntity> response)
        where TEntity : class, IMultitenantEntity
    {
        return response.Where(t => t.TenantId == 1);
    }
}