创建IQueryable <t>的通用过滤器,其中过滤器使用ISomeInterface

时间:2016-08-31 12:44:06

标签: c# linq generics multi-tenant iqueryable

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

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

  • HTTP调用命中瘦控制器,在那里它被解析为符合CQS的查询并传递给调解器进行处理
  • mediator被配置为通过管道提供所有请求,这有几个步骤

    1. 验证
    2. 缓存
    3. 任何查询特定的预处理程序
    4. 在处理程序中处理查询(返回IQueryable<TEntity>
    5. 任何查询特定的帖子处理程序
    6. 返回来电者(返回IQueryable<TEntity>

我的域模型有一堆类,它继承自一个用简单的IMultitenantEntity接口修饰的基类。

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

我的想法是注入另一个Post处理程序,特定于返回IQueryable<TEntity> TEntity IMultitenantEntity实现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); } } 接口的所有管道请求。很简单,由于铸造/类型问题,我无法使其工作。我确信我的思绪陷入了无限循环的愚蠢想法中,我需要有人请我离开这个循环,非常感谢你:))

这是我现在的Post处理程序:

System.Data.Entity.Infrastructure.DbQuery'1[IMultitenantEntity]

现在想象一下,a请求来自管道,查询处理程序返回IQueryable。毫不奇怪,一旦我们点击上面的处理程序,我们就会得到一个例外:

  

无法投射类型的对象   'System.Linq.IQueryable'1[Game]'   输入   'PostTenantQueryableFilterHandler.Handle()'。

如果我们调试IQueryable<Game>方法,我们可以看到TResponse按预期为Where(t => t.TenantId == 1),但为了能够包含where TResponse : IQueryable<IMultitenantEntity>部分,我添加了Where() ,这会导致LINQ 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>'。

我该怎么办才能将TEntity类型缩小到IQueryable<T>,但我仍然可以添加缺少的WHERE谓词?

编辑2:

所以这里真正的限制是调用方法存在于类中,它不知道PostTenantQueryableFilterHandler<TResponse, TEntity>作为单独类型的任何内容。

当我说,a请求来自管道并且查询处理程序返回IQueryable时,它不够详细。问题是,管道能够处理可以具有任何返回类型的查询处理程序,而不仅仅是TEntity。因此,管道知道的是请求的类型(通过它提供的查询)以及对已处理请求的响应类型。因此,当我在管道中连接IMultitenantEntity时,PostTenantQueryableFilterHandler始终为TEntity,后者返回原点 - TResponse不知道.Where(t => t.TenantId == 1)的具体类型并且管道无法提供它。

我们所知道的是PostTenantQueryableFilterHandler的具体类型,它实现了'IQueryable'。

长话短说,我仍然在寻找一种方法来将public class AsyncMediatorPipeline<TRequest, TResponse> : IAsyncRequestHandler<TRequest, TResponse> where TRequest : IAsyncRequest<TResponse> 过滤器添加到响应中。思绪,有人吗?

编辑3:

所以一切都非常清晰,我认为提供一些关于如何调用IAsyncQueryablePostRequestHandler<,>的背景将是有益的。

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

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

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

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

所有构造函数参数都使用AutoFac注入,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;
}

管道类以自己的public interface IAsyncPostRequestHandler<in TRequest, in TResponse> { Task Handle(TRequest request, TResponse response); } 方法处理所有请求:

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

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

{{1}}

而可查询的那个

{{1}}

希望这会对这个问题有所了解。

2 个答案:

答案 0 :(得分:2)

根据您的要求,我担心您必须使用手动非通用表达式/查询构建。

例如,这样的事情应该可以解决问题:

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

关键是您可以从传递的TEntityIQueryable属性)中获取实际的ElementType类型,但是因为您无法使用它来调用泛型方法(因为你所拥有的只是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);
    }
}

答案 1 :(得分:1)

这个答案提供了有用的信息以及它无效的原因:

Casting list of interface to concrete typed list

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