创建 IQueryable<T> 的通用过滤器,其中过滤器使用 ISomeInterface 中定义的 属性
Create generic filter of IQueryable<T> where the filter uses property defined in ISomeInterface
我正在为多租户网络实施自定义处理管道 API。这就产生了一个要求,即数据检索管道的最后一步必须确保只有特定于给定租户的数据被 returned 给调用者,我认为最好使用
对于数据检索,过程如下所示:
- HTTP 调用到达瘦控制器,在那里它被解析为符合 CQS 的查询并传递给中介进行处理
中介器配置为通过管道提供所有请求,这有几个步骤
- 验证
- 缓存
- 任何特定于查询的预处理程序
- 在处理程序中处理查询 (returns
IQueryable<TEntity>
)
- 任何特定于查询的 Post 处理程序
- 正在返回调用方 (returns
IQueryable<TEntity>
)
我的域模型有一堆 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);
}
}
关键是你可以从传递的IQueryable
(ElementType
属性)中得到实际的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);
}
}
我正在为多租户网络实施自定义处理管道 API。这就产生了一个要求,即数据检索管道的最后一步必须确保只有特定于给定租户的数据被 returned 给调用者,我认为最好使用
对于数据检索,过程如下所示:
- HTTP 调用到达瘦控制器,在那里它被解析为符合 CQS 的查询并传递给中介进行处理
中介器配置为通过管道提供所有请求,这有几个步骤
- 验证
- 缓存
- 任何特定于查询的预处理程序
- 在处理程序中处理查询 (returns
IQueryable<TEntity>
) - 任何特定于查询的 Post 处理程序
- 正在返回调用方 (returns
IQueryable<TEntity>
)
我的域模型有一堆 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);
}
}
关键是你可以从传递的IQueryable
(ElementType
属性)中得到实际的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);
}
}