Closed tomaforn closed 9 months ago
Hi @tomaforn,
There are two types of base specification classes, Specification<T>
and Specification<T, TResult>
. If you are using the latter, then the specification must contain the Select
expression. That's by design. That's precisely what the error message says. We're not doing any automatic mapping if that's what you expected.
So, the usage will be as follows:
var posts = await db.Posts.WithSpecification(BlogPostByTitleSpec ).ToListAsync();
- Get post entities.var postsDtoByTitle = await db.Posts.WithSpecification(BlogPostByTitleDtoSpec ).ToListAsync();
- Get dtos directly. In this case the spec must contain the projection expression (the last example in your post).var postsDtoByTitle = await db.Posts.WithSpecification(BlogPostByTitleSpec).ProjectToType<PostDto>().ToListAsync();
- Using AutoMapper for projection. In this case, use BlogPostByTitleSpec
, not the BlogPostByTitleDtoSpec
spec. It means you're just building the IQueruable<Post>
to a given point, and then passing it to Automapper to add the select expression.Let me know if this helps to clarify the confusion.
Hi @fiseni,
Ah, so that explains why it wasn't working, thanks!
I was writing an extension method for IQueryable when i ran into the issue, something like this:
public static async Task<PaginationResponse<TDestination>> ProjectToPaginatedListAsync<T, TDestination>(
this IQueryable<T> query, ISpecification<T, TDestination> spec, int pageNumber, int pageSize, CancellationToken cancellationToken = default)
where T : class
where TDestination : class
{
int count = await query.WithSpecification(spec).CountAsync(cancellationToken);
if (count > 0)
{
var list = await query.WithSpecification(spec).ProjectToType<TDestination>().ToListAsync(cancellationToken);
return new PaginationResponse<TDestination>(list, count, pageNumber, pageSize);
}
return new PaginationResponse<TDestination>(new List<TDestination>(), 0, 0, 0);
}
I was hoping I could require the ISpecification<T, TDestination> base as input to deduce the target type and handle the projection inside, but from your answer I'm not sure that's possible?
I guess I could change it into something like this instead:
public static async Task<PaginationResponse<TDestination>> ProjectToPaginatedListAsync<T, TDestination>(
this IQueryable<T> query, ISpecification<T> spec, int pageNumber, int pageSize, CancellationToken cancellationToken = default)
where T : class
where TDestination : class
{
int count = await query.WithSpecification(spec).CountAsync(cancellationToken);
if (count > 0)
{
var list = await query.WithSpecification(spec).ProjectToType<TDestination>().ToListAsync(cancellationToken);
return new PaginationResponse<TDestination>(list, count, pageNumber, pageSize);
}
return new PaginationResponse<TDestination>(new List<TDestination>(), 0, 0, 0);
}
But it would be nice to be able to do
var pagedAndMappedResultByTitle = await db.Posts.ProjectToPaginatedListAsync(blogPostByTitleDtoSpec, 1, 5, CancellationToken.None);
instead of
var pagedAndMappedResultByTitle = await db.Posts.ProjectToPaginatedListAsync<Post, PostDto>(blogPostByTitleSpec, 1, 5, CancellationToken.None);
Do you know if it's possible to extend like in the first example, without having to manually map the types in the specification?
I see you're trying to automate the projection using Automapper (or other mapping tools) and also apply pagination.
We already have samples for that (you can find them under samples
folder in the repository). The App3
sample is exactly what you're trying to accomplish. You can find it here.
The usage becomes something as follows
var spec = new CustomerByIdSpec(id);
var result = await repo.ProjectToFirstOrDefaultAsync<CustomerDto>(spec, cancellationToken);
or
var spec = new CustomerSpec(filter);
var result = await repo.ProjectToListAsync<CustomerDto>(spec, filter, cancellationToken);
You want to define the return type as a generic parameter, since for a single customer specification you may want to project it to CustomerDto
, CustomerSimpleDto
, etc.
I had totally missed those samples, they look to be exactly what I was looking for as you said. Thanks for pointing me there!
I have the following specifications defined:
I use them like so (working example):
And like this (throws exception):
The exception thrown is:
I thought it was related somehow to the type change/mapping defined in the specification, so I've tried fiddeling with both Mapster and Automapper, for instance like this:
But that gives the same error.
The only way I have found around this problem is to define a Select directly in the specification, like this:
Can anyone explain what I'm doing wrong?