Call a DTO translator inside IQueryable's Select
I have the following code to query an EntityContext (through a repository) and map it unto a DTO:
public class QueryQuestionsConsumer : IConsumerOf<QueryQuestionsRequest>
{
public void Consume(QueryQuestionsRequest request)
{
var repo = IoC.Resolve<IUnitOfWork>().CreateRepository<Question>();
var filter = FilterTranslator.CreateFilterExpression<Question>(request.Filters);
var questions = repo
.GetAll()
.Where(filter)
Result = questions.Select(question => QuestionTranslator.ToDTO(question)).ToArray()
}
}
This would obviously fail because ToDTO() isn't a recognized function in EntityFramework provider. I could create a DTO object using开发者_开发问答 an object initializer but I'd like to delegate it to another class (QuestionTranslator).
What do you do in this case?
UPDATED: Additionally, I don't want to hydrate a full Question Object to do that. I'd like to count on Provider's ability to create DTO objects.
Besides the obvious option of using questions.AsEnumerable().Select(...)
to force EF to retrieve full records and then mapping them client side, you can make your ToDTO method return an expression:
Expression<Func<Question, QuestionDTO>> ToDTO()
{
Expression<Func<Question, QuestionDTO>> exp =
question => new QuestionDTO { ... };
return exp;
}
You can convert it to an enumerable, then translate it locally:
Result = questions.AsEnumerable()
.Select(question => QuestionTranslator.ToDTO(question)).ToArray()
This will cause the query to be converted to a (local) enumerable, where it can be safely passed through your QuestionTranslator
.
There is a great blog series that explains how to implement this type of functionality without force-evaluating the query. It basically relies on implementing an IQueryProvider
. This is not a trivial task, and the following link gives a great case study of building one. http://blogs.msdn.com/b/mattwar/archive/2008/11/18/linq-links.aspx
I use AutoMapper to map from entities to DTO's because if you create a Mapping for Question
then it will automatically know how to Map IEnumerable<Question>
The problem with doing both the query and mapping in one statement is that it tends to cause your datamapper to throw hard to decipher errors when bad things happen (hard to tell if the problem was in the query execution or in the mapping)
I found it much easier to follow to "fire" the query by doing a .ToList()/.AsEnumerable()
etc, and then pass that variable to the mapper. This allows the exceptions to be very clear when problems occur, making it clear whether the problem was in the query or in the mapping.
See this post: Autoprojecting LINQ queries.
When you use it, you'll query will be like:
Result = questions.Project().To<QuestionDto>().ToArray()
Should happen on the server if using NHibernate/EF (the query sent to DB will only have the projected properties). Should still work normally against objects alread in memory (LINQ To Objects).
精彩评论