-
Notifications
You must be signed in to change notification settings - Fork 25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Command and Query Responsibility Segregation pattern #3
Comments
I would suggest not passing along Expression objects with the query. Strive the messages you sent in your system (commands, queries, events) to be serializable. This means that they can only be composed of primitives, or other serializable DTOs. Not only will this allow you to keep your messages simple, and keep you from passing along entities and other internal stuff, being able to serialize those messages allows you to plug-in cross-cutting concerns that depend on this serializability, such as audit trailing, logging, diagnosis and caching. Just as keeping the query messages simple, the same holds for the return types. For instance, do not return Applying sorting and paging after you do the transformation from entity to DTO can cause the executed SQL query to become much more complex. In an application I was working on, this even caused the SQL queries to becomes that much more complex, that it became practically impossible for our DBA to tune the database. This caused quite severe performance problems. Moving paging and sorting inside the query handler on the other hand, gives you more freedom to optimize and tune your LINQ query and therefore simplify the constructed SQL query. With that out of the way, you can use the following data structures to apply paging: public sealed class PagingInformation
{
public readonly int PageIndex;
public readonly int PageSize;
public PagingInformation(int pageIndex, int pageSize)
{
if (pageIndex < 0) throw new ArgumentOutOfRangeException("pageIndex");
if (pageSize <= 0) throw new ArgumentOutOfRangeException("pageSize");
this.PageIndex = pageIndex;
this.PageSize = pageSize;
}
}
public sealed class PagedResult<T> {
public readonly PagingInformation Paging;
public readonly int PageCount;
public readonly int ItemCount;
public readonly ReadOnlyCollection<T> Page;
public PagedResult(
PagingInformation paging, int pageCount, int itemCount, ReadOnlyCollection<T> page) {
this.Paging = paging;
this.PageCount = pageCount;
this.ItemCount = itemCount;
this.Page = page;
}
public static PagedResult<T> ApplyPaging(IQueryable<T> query, PagingInformation paging) {
int count = query.Count();
var page = query.Skip(paging.PageSize * paging.PageIndex).Take(paging.PageSize).ToList();
return new PagedResult<T>(
paging: paging,
pageCount: (count + (paging.PageSize - 1)) / paging.PageSize,
itemCount: count,
page: page.AsReadOnly());
}
} Using these two data structures, you can construct your query messages that look as follows: public class SearchOrdersQuery : IQuery<PagedResult<OrderDetails>> {
public string SearchText { get; set; }
public PagingInformation Paging { get; set; }
} Here the The corresponding handler, might look like this: public class SearchOrdersQueryHandler : IQueryHandler<SearchOrdersQuery, PagedResult<OrderDetails>> {
private readonly NorthwindDbContext context;
public SearchOrdersQueryHandler(NorthwindDbContext context) {
this.context = context;
}
public PagedResult<OrderDetails> Handle(SearchOrdersQuery query) {
var results =
from order in this.context.Orders
where order.Description.Contains(query.SearchText)
select new OrderDetails
{
OrderId = order.Id,
Customer = order.Customer.Name,
OrderDate = order.OrderDate
};
return PagedResult<OrderDetails>.ApplyPaging(results, query.Paging);
}
} For sorting on the other hand, I think there are a couple of different ways to achieve this and it depends a bit on what is most convenient for the client. Probably the most easy -yet flexible- way to achieve this is using the System.Linq.Dynamic NuGet package and passing in the ordering information as string: var page = this.queryProcessor.Execute(new SearchOrdersQuery
{
Ordering = "OrderDate DESC, Customer ASC, OrderId DESC",
SearchText = model.SearchText,
Paging = model.Paging,
}); Here we simply pass in the property names of the OrderDetails DTO in the Ordering string property. This is obviously not nice from a compile-time perspective, but using the C# 6.0 new SearchOrdersQuery
{
Ordering = string.Format("{0} DESC, {1} ASC, {2} DESC",
nameof(OrderDetails.OrderDate),
nameof(OrderDetails.Customer),
nameof(OrderDetails.OrderId))
} C# 6.0 however, is currently just a future dream. It will probably take quite some time before it will reach RTM. Besides this, the nameof operator will always strip everything before the last dot. So nameof(OrderDetails.Address.Country.IsoCode) will simply return the "IsoCode" string literal. Because of those reasons, we might want to write our own 'improved' nameof method, as discussed here. The query would look as follows: new SearchOrdersQuery
{
Ordering = string.Format("{0} DESC, {1} ASC, {2} DESC",
Utilities.NameOf<OrderDetails>(o => o.OrderDate),
Utilities.NameOf<OrderDetails>(o => o.Customer),
Utilities.NameOf<OrderDetails>(o => o.OrderId))
} Using the System.Linq.Dynamic NuGet package we can simply apply the ordering in our query handler as follows: public PagedResult<OrderDetails> Handle(SearchOrdersQuery query) {
var results =
from order in this.context.Orders
where order.Description.Contains(query.SearchText)
select new OrderDetails
{
OrderId = order.Id,
Customer = order.Customer.Name,
OrderDate = order.OrderDate
};
if (!string.IsNullOrEmpty(query.Ordering)) {
// using System.Linq.Dynamic
results = results.OrderBy(query.Ordering);
}
return PagedResult<OrderDetails>.ApplyPaging(results, query.Paging);
} Obviously, since the supplied Ordering string maps directly on the property names of the OrderDetails DTO, it will take a bit more work to apply the ordering on the original |
Migrated from: https://solidservices.codeplex.com/discussions/574373
LauriSaar wrote:
The text was updated successfully, but these errors were encountered: