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

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

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

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

我的域模型有一堆 classes,它们继承自装饰有简单 IMultitenantEntity 接口的基础 class。

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

我的想法是注入另一个 Post 处理程序,特定于 return IQueryable<TEntity> 的所有管道请求,其中 TEntity 实现 IMultitenantEntity 接口。很简单,由于 casting/type 问题,我无法让它工作。我确定我的思想陷入了愚蠢想法的无限循环中,我需要有人让我摆脱这个循环,非常感谢:)

这是我现在的 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);
    }
}

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

Unable to cast object of type 'System.Data.Entity.Infrastructure.DbQuery'1[IMultitenantEntity]' to type '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>();

但还是不够,on

response = (TResponse)intermediary;

一样我变老

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

我应该怎么做才能使 TResponse 类型不缩小到 IQueryable<IMultitenantEntity> 但我仍然可以添加缺少的 WHERE 谓词?

编辑 2:

所以这里真正的限制是调用方法存在于 class 中,它对作为单独类型的 TEntity 一无所知。

当我说请求通过管道和查询处理程序 returns IQueryable 时,它​​不够详细。问题是,管道能够处理可以具有任何 return 类型的查询处理程序,而不仅仅是 IQueryable<T>。所以管道知道的是请求的类型(通过它提供的查询)和对已处理请求的响应类型。因此,当我在管道中连接 PostTenantQueryableFilterHandler<TResponse, TEntity> 时,TEntity 始终是 IMultitenantEntity,这又回到了第一个问题 - PostTenantQueryableFilterHandler 不知道 [=25= 的具体类型] 并且管道无法提供它。

我们所知道的是 TResponse 的具体类型,它实现了“IQueryable”。

长话短说,我仍在寻找将 .Where(t => t.TenantId == 1) 过滤器添加到响应中的方法。有想法吗?

编辑 3:

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

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

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

和现在的构造函数:

public异步中介管道( IAsyncRequestHandler 内部, IAsyncPreRequestHandler[] preRequestHandlers, IAsyncPostRequestHandler[] postRequestHandlers, IAsyncQueryablePostRequestHandler[] postQueryableRequestHandlers )

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

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

管道 class 在其自己的 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);
}

而可查询的

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

希望这能更清楚地说明这个问题。

这个答案提供了有用的信息以及它不起作用的原因:

Casting list of interface to concrete typed list

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

根据您的要求,恐怕您不得不求助于手动非通用 expression/query 构建。

例如,像这样的东西应该可以解决问题:

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

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

更新: 这是另一个更优雅(虽然很可能更慢)的基于动态调度功能的解决方案:

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