创建 IQueryable 的通用筛选器<T>,其中筛选器使用 ISomeInterface 中定义的属性



我正在为一个多租户web API实现一个自定义处理管道。这就产生了一个要求,即数据检索管道的最后一步必须确保只将特定于给定租户的数据返回给调用者,我认为最好使用

来完成此操作。

对于数据检索,过程如下所示:

  • HTTP调用到达瘦控制器,在瘦控制器中将其解析为符合cqs的查询并传递给中介以处理
  • 中介被配置为通过管道提供所有请求,该管道有几个步骤

      <
    1. 验证/gh>
    2. 任何特定于查询的预处理程序
    3. 处理处理程序中的查询(返回IQueryable<TEntity>)
    4. 任何查询特定的Post处理程序
    5. 返回呼叫方(返回IQueryable<TEntity>)

我的领域模型有一堆类,它们继承自一个基类,这个基类用一个简单的immultitentenentity接口装饰。

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

我的想法是注入另一个Post处理程序,特定于返回IQueryable<TEntity>的所有管道请求,其中TEntity实现IMultitenantEntity接口。很简单,我不能让它工作,由于铸造/类型问题。我确信我的大脑只是陷入了一个无限循环的愚蠢的想法,我需要有人请你把我从这个循环中解脱出来,非常感谢:)

这是我的Post处理程序,因为它现在:

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);
    }
}

现在想象一下,请求来自管道,查询处理程序返回IQueryable。毫不奇怪,一旦碰到上面的处理程序,就会得到一个异常:

无法强制转换类型的对象' System.Data.Entity.Infrastructure.DbQuery'1[IMultitenantEntity] '输入' System.Linq.IQueryable'1[Game] '.

如果我们调试PostTenantQueryableFilterHandler.Handle()方法,我们可以看到TResponse是IQueryable<Game>的预期,但为了能够包括Where(t => t.TenantId == 1)部分,我添加了where TResponse : IQueryable<IMultitenantEntity>,这导致LINQ Where()的结果是IQueryable<IMultitenantEntity>类型,这比IQueryable<Game>更通用,因此不可能从IQueryable<IMultitenantEntity>强制转换到IQueryable<Game>…这就是我陷入无限思维循环的地方。

帮助!:)

编辑:

我能够使用以下内容连接管道:

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

使

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

但这还不够,在

response = (TResponse)intermediary;

我变老了

无法强制转换类型的对象' System.Data.Entity.Infrastructure.DbQuery'1[IMultitenantEntity] '输入' System.Linq.IQueryable'1[Game] '.

我应该怎么做,使TResponse类型不按比例缩小到IQueryable<IMultitenantEntity>,但我仍然能够添加缺失的WHERE谓词?

编辑2:

所以这里真正的限制是调用方法存在于不知道作为单独类型的TEntity的任何东西的类中。

当我说请求来自管道并且查询处理程序返回IQueryable时,它还不够详细。问题是,管道能够处理具有任何返回类型的查询处理程序,而不仅仅是IQueryable<T>。因此,管道所知道的是请求的类型(通过它提供的查询)和对已处理请求的响应的类型。所以当我在管道中连接PostTenantQueryableFilterHandler<TResponse, TEntity>时,TEntity总是IMultitenantEntity,这又回到了原点——PostTenantQueryableFilterHandler不知道TEntity的具体类型,管道也没有能力提供。

我们所知道的是TResponse的一个具体类型,并且它实现了' IQueryable'。

长话短说,我仍然在寻找一种方法来添加.Where(t => t.TenantId == 1)过滤器的响应。思想,有人知道吗?

编辑3:

所以一切都非常清楚,我认为提供一些关于PostTenantQueryableFilterHandler如何调用的背景知识是有益的。

如前所述,所有请求都通过公共处理管道提供(可以使用单独的异步同步实现)。管道类签名如下:

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

和构造函数,就像现在一样:

公共AsyncMediatorPipeline (IAsyncRequestHandler内部,IAsyncPreRequestHandler [] preRequestHandlers,IAsyncPostRequestHandler [] postRequestHandlers,IAsyncQueryablePostRequestHandler [] postQueryableRequestHandlers)

所有构造函数参数都被AutoFac注入,IAsyncQueryablePostRequestHandler<,>被配置为:

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

管道类在自己的Handle方法中处理所有请求:

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;
}

我有两个单独的"post"处理程序,因为通用的"post"处理程序不允许操作响应

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

而Queryable

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

希望这能让你对这个问题有更多的了解。

根据您的要求,恐怕您必须求助于手动非通用表达式/查询构建。

例如,像这样的代码应该可以达到这个效果:

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);
    }
}

关键是您可以从传递的IQueryable (ElementType属性)中获得实际的TEntity类型,但是由于您不能使用它来调用泛型方法(因为您所拥有的只是一个Type对象),因此您必须使用所提供的非泛型技术组合Where谓词和Where调用。

Update:下面是另一个基于动态调度特性的更优雅(尽管很可能更慢)的解决方案:

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);
    }
}

这个答案提供了一个有用的信息以及为什么这个不工作的原因:

将接口列表转换为具体类型列表

这里的问题基本上是不能将接口的列表强制转换为具体类型的列表。如果结果不是一个列表,而是一个对象,则可以对其进行强制类型转换。在这种情况下,您必须强制转换每个集合,并使用Cast<TResponse>()ConvertAll<TResponse>()方法返回新集合。

最新更新