There are two problems in the original query:
A. The in-memory call is not in the final projection: you have an OrderBy as the final one. The OrderBy can't be used in this case because: 1) the in-memory call must be in the final projection and 2) you can't put the orderby before the select because there is no Rank column in the DB to order, and the LINQ expression evaluator will try to produce SQL for the Rank property. In other words, if you want to "OrderBy" your result based on a custom property (rank) you will have to wait until the results are fetched (materialized in memory).
This is mentioned in the documentation as well.
B. Even if the in-memory function call would be in the final projection, there is another problem: the parameter is the entity itself, and it is not materialized yet. You can pass fields but no entities. The entity itself will be materialized at the end in case the fetch is not an anonymous one. That's why the stringChopper function that receives an string works well, because at runtime, the expression tree evaluator determine that the entity should be materialized first to pass the field value to the function. This doesn't happen if you pass the entity as parameter.
So, the only option that comes to my head that provides what you need (an un-fetched IQueryable and an assigned rank) is to build a projection and use a simpler in-memory function to assign the Rank based on the related value. The only downside (if you want to see it that way) is that you must specify the fields you want in the projection. Example (using my own code):
// define the function to assign the rank
Func<string, int> rankAssign = delegate(string country)
{
return country == countryToFilter ? 1 : 2;
};
// get the iqueryable ready to fetch
IQueryable<OrderEntity> ordersAsQueryable = from e in linqMetada.Order
join ec in linqMetada.Customer on e.CustomerId equals ec.CustomerId
select new OrderEntity {
OrderId = e.OrderId,
ShipCountry = e.ShipCountry,
....,
Rank = rankAssign(ec.Country)
};
Hope that helps