我正在为多租户Web API实现自定义处理管道。这就产生了一个要求,即数据检索管道的最后一步必须确保,只有特定于给定租户的数据才会返回给调用者,我认为最好使用
对于数据检索,过程如下所示:
mediator被配置为通过管道提供所有请求,这有几个步骤
IQueryable<TEntity>
)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}}
希望这会对这个问题有所了解。
答案 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);
}
}
关键是您可以从传递的TEntity
(IQueryable
属性)中获取实际的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>()
方法强制转换每个集合并返回新集合。