views:

47

answers:

2

I have an IQueryable whose Entity Framework 4 objects I would like to project to their DTO equivalents. One such object 'Person' is an EF4 class, and the corresponding POCO PersonP is a class I've defined. I am using Automapper to map between them. However, when I try the following code:

IQueryable<Person> originalModel = _repo.QueryAll();
IQueryable<PersonP> projection = originalModel.Select(e => Mapper.Map<Person, PersonP>(e));

The projection generates this error at runtime:

LINQ to Entities does not recognize the method 'TestSite.Models.PersonP Map[Person,PersonP](TestSite.DataLayer.Model.Person)' method, and this method cannot be translated into a store expression.

What is the appropriate syntax to create a IQueryable<PersonP> projection using Automapper? Thank you.

P.S. Automapper is configured correctly - I use it in other places to convert back and forth between Person and PersonP, i.e. Mapper.Map<Person, PersonP>(myPersonObject) correctly returns a PersonP object.

EDIT (more code):

I'm using this for a helper function to bind EF4 Entity POCOs (PersonP) to a Telerik Grid - which will not serialize the entities themselves properly since they contain circular references (i.e. navigation properties). My code looks like this:

public static GridModel GetGridModel<TEntity, TPoco>(IRepository<TEntity> repo, GridState gridState) where TEntity : EntityObject
{
 var originalModel = repo.QueryAll().ToGridModel(gridState);
 var projection = originalModel.Select(e => Mapper.Map<TEntity, TPoco>(e));


 return projection.ToGridModel(gridState); // applies filters, sorts, pages, etc...
}

the .ToGridModel method is an extension method on IQueryable and it returns a complex object which I cannot reliably parse - so this leads me to believe I have to perform the filtering after I've done the projection to POCOs.

UPDATE 2:

Trying to simplify things, I made a non-generic method like this:

public static GridModel GetGridModel2(IRepository<Client> repo, GridState gridState)
{
 IQueryable<Client> originalModel = repo.QueryAll();
 IQueryable<ClientP> projection = originalModel.Select(c => ClientToClientP(c));

 return projection.ToGridModel(gridState);
}

private static ClientP ClientToClientP(Client c)
{
 return new ClientP { Id = c.Id, FirstName = c.FirstName };
}

This code also fails when creating the projection. I notice that IQueryable.Select() has multiple overloads: Expression> being one of them. Could I represent this function/delegate call using one of these overloads?

+1  A: 

If you add .ToList() before the Select you can force the mapping to happen client-side (Linq to Objects) instead of server side (SQL). Just make sure you've done your filtering first so you aren't bringing the whole table over.

Hightechrider
Well I'm actually using a Telerik extension method to perform the filtering/grouping/sorting/etc and I cannot apply this to the EF Queryable because the Grid cannot serialize circular dependencies in those Entities. I've added more code.
Harper
You could map the entities using anonymous types: http://msdn.microsoft.com/en-us/library/bb738512.aspx but that misses the point of your Poco entities. It looks like the real issue here is that call to the monolithic `ToGridModel` method. That really needs splitting apart so you can filter and sort, then project, then do whatever else it needs to do. Can you apply the filters from it separately, then cross the boundary, then project and assemble the GridModel?
Hightechrider
Thank you for your suggestions. I will investigate if I can circumvent the ToGridModel() method.
Harper
+1  A: 

What is the appropriate syntax to create a IQueryable projection using Automapper?

There isn't one. Automapper doesn't do this. It's the wrong tool for this job.

One could create an Automapper-like tool to do a similar thing for query projections. I've considered it in the past, but always concluded that code using it would be less readable than the projection. I don't want to optimize code-writing time over code-reading time.

Your updated code doesn't work because it isn't an expression. If you do:

private static Expression<Func<Client, ClientP>> ClientP ClientToClientP()
{
    return c => new ClientP { Id = c.Id, FirstName = c.FirstName };
}

...and then:

IQueryable<Client> originalModel = repo.QueryAll();
Expression<Func<Client, ClientP>> exp = ClientToClientP();
IQueryable<ClientP> projection = originalModel.Select(exp);

...then it will work.

Craig Stuntz
This fails even with a regular method instead of AutoMapper. Please see UPDATED2 in my post.
Harper
See update. Non-expressions can't be translated to SQL.
Craig Stuntz
Thank you for your help. Your code works (corrected small typo in method sig) but as you mentioned, AutoMapper is unsuitable for this purpose and does not work. Could you please explain the technical reason why I cannot substitute the new ClientP { Id = .. } for an AutoMapper mapping - since the both return a ClientP object?
Harper
For the same reason your function didn't work: Because compiled functions can't be converted to SQL; only expressions can.
Craig Stuntz
To spell that out a bit further: The C# compiler will observe that your new method returns an `Expression<Function<…` and hence will transliterate the `new` into a SQL - compatible expression. But the `Mapper.Map` call would become an `Invoke` in the expression body, and that won't convert to SQL.
Craig Stuntz
Thank you very much!
Harper