在我的應用程序的設計,我想實現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