在我的应用程序的设计,我想实现Pagination与应用于CQRS模式an implementation的Decorator模式的Cross Cutting Concern。
我也有一个multilayered architecture我认为分页是而不是业务逻辑的一部分(因此是一个横切关注)。这是已经做出的决定,不应在本主题中讨论。分页在CQRS横切关注点与simpleinjector
在我的设计,目的是表示层可以消耗具有特定的分页查询封闭泛型类型
IQueryHandler<GetAllItemsQuery, PaginatedQuery<Item>>
具有以下特征:
public class GetAllItemsQuery : PaginatedQuery<Item>
public class PaginatedQuery<TModel> :
IQuery<PaginatedResult<TModel>>, IQuery<IEnumerable<TModel>>
public class PaginatedResult<TModel>
的想法是,消费者应该为特定模型收到PaginatedResult
,该模型包含分页项目和一些元数据(例如,未应用分页执行的查询项目总数),以便UI可以呈现分页。
我的设计的主要理念是查询处理程序应该应用它的业务逻辑(例如获取所有项目)。它只描述它是如何做到这一点的,它不一定要执行查询。
在我的情况下,查询处理程序上的装饰器实际上在查询上应用分页并执行它(例如,通过在Linq to Entities查询上调用.ToArray()
)。
我想的是,我应该queryhandler这样实现:
public class GetAllItemsQueryHandler : IQueryHandler<GetAllItemsQuery, IEnumerable<Item>>
所以,处理程序的返回类型为IEnumerable<Item>
。这样处理程序被强制为Single Responsible。 我面临的问题可能是我使用Simple Injector的方式。因为我登记我的IQueryHandler<,>
像
container.Register(typeof(IQueryHandler<,>), assemblies);
这不会验证我的设计,因为一个明显的无效配置的:我注射IQueryHandler<GetAllItemsQuery, PaginatedResult<Item>>
到我的消费者,但实际上并没有实现它。处理程序执行IQueryHandler<GetAllItemsQuery, IEnumerable<Item>>
。
因此,作为一个解决方案,我试图执行一个Interceptor并注册条件(注意C# 7.0 local functions使用):
Type PaginationInterceptorFactory(TypeFactoryContext typeContext)
{
// IQueryHandler<TQuery, TResult> where TResult is PaginatedResult<TModel>
var queryType = typeContext.ServiceType.GetGenericArguments()[0]; // TQuery
var modelType = typeContext.ServiceType.GetGenericArguments()[1].GetGenericArguments()[0]; // TModel in PaginatedResult<TModel> as TResult
return typeof(PaginatedQueryHandlerInterceptor<,>).MakeGenericType(queryType, modelType);
}
bool PaginationInterceptorPredicate(PredicateContext predicateContext) =>
predicateContext.ServiceType.GetGenericArguments()[0].IsPaginatedQuery(); // if TQuery is of type PaginatedQuery<>
container.RegisterConditional(typeof(IQueryHandler<,>), PaginationInterceptorFactory, Lifestyle.Singleton, PaginationInterceptorPredicate);
,但是这给了我在验证一个例外:
System.InvalidOperationException occurred
Message=The configuration is invalid. Creating the instance for type [TYPE] failed. This operation is only valid on generic types.
Source=SimpleInjector
StackTrace:
at SimpleInjector.InstanceProducer.VerifyExpressionBuilding()
at SimpleInjector.Container.VerifyThatAllExpressionsCanBeBuilt(InstanceProducer[] producersToVerify)
at SimpleInjector.Container.VerifyThatAllExpressionsCanBeBuilt()
at SimpleInjector.Container.VerifyInternal(Boolean suppressLifestyleMismatchVerification)
at SimpleInjector.Container.Verify()
Inner Exception 1:
ActivationException: This operation is only valid on generic types.
Inner Exception 2:
InvalidOperationException: This operation is only valid on generic types.
的例外情况在操作是什么以及它为什么无效方面不是很清楚。也许我做错了什么?
这里是拦截器的实现:
public class PaginatedQueryHandlerInterceptor<TQuery, TModel> : IQueryHandler<TQuery, PaginatedResult<TModel>>
where TQuery : PaginatedQuery<TModel>
{
private readonly IQueryHandler<TQuery, IEnumerable<TModel>> _queryHandler;
public PaginatedQueryHandlerInterceptor(IQueryHandler<TQuery, IEnumerable<TModel>> queryHandler)
{
_queryHandler = queryHandler;
}
public PaginatedResult<TModel> Handle(TQuery query)
{
return (dynamic) _queryHandler.Handle(query);
}
}
和装饰:
public class PaginationQueryHandlerDecorator<TQuery, TResult> : IQueryHandler<TQuery, TResult>
where TQuery : class, IQuery<TResult>
{
private readonly IQueryHandler<TQuery, TResult> _decoratee;
public PaginationQueryHandlerDecorator(
IQueryHandler<TQuery, TResult> decoratee)
{
_decoratee = decoratee;
}
public TResult Handle(TQuery query)
{
query.ThrowIfNull(nameof(query));
var result = _decoratee.Handle(query);
if (query.IsPaginationQuery(out var paginatedQuery))
{
return Paginate(result, paginatedQuery.Pagination);
}
return result;
}
private static TResult Paginate(TResult result, Pagination pagination)
{
return Paginate(result as dynamic, pagination.Page, pagination.ItemsPerPage);
}
private static PaginatedResult<TModel> Paginate<TModel>(IEnumerable<TModel> result, int page, int itemsPerPage)
{
var items = result as TModel[] ?? result.ToArray();
var paginated = items.Skip(page * itemsPerPage).Take(itemsPerPage).ToArray();
return new PaginatedResult<TModel>
{
Items = paginated,
Count = items.Length
};
}
}
解决方案只有一个小问题。这就是说,当'PagedQueryHandler'调用'.ToArray()'时,我的DbContext被放置。因为我用'LifetimeScopeQueryHandlerProxy'装饰我的查询处理器。 – QuantumHive
这意味着你不应该修饰返回'IQueryable'实例的查询处理程序。或者确保LifetimeScopeQueryHandlerProxy检测它是否嵌套并阻止它启动并处理一个DbContext。但是,这对我的设计并不是一个问题,因为您的原始文章包含相同的问题。 –
Steven
我已经将我的初始设计重构为*稍有不同的方法*,现在完美地工作。我在原始设计中使用'IEnumerable'的原因是因为我的查询会从'PaginatedQuery'继承,迫使处理程序返回一个'IEnumerable',但让业务决定它是否是'IQueryable'。基础数据模型是超级高性能的)。在你的设计中,查询保持自然,泛型类型约束迫使处理程序返回'IQueryable'。缺点是处理程序被迫只返回一个查询。 – QuantumHive