November Happy Hour will be moved to Thursday December 5th.

Projections on nested classes

Vote:
 

Hi,

Is it possible with EPiServer Find projections on nested classes?

e.g. the following gets indexed:

public class Game

{

   public string Name;

   public List Platforms;

}

When the line below is executed I get an exception (see end of message)

var projection = searchQuery.Select(x => new
{
Name = x.Name,
Variant = x.Platforms.FirstOrDefault()
});

Thanks

Danie

"Message": "An error has occurred.",
"ExceptionMessage": "An exception of type ArgumentException was thrown while projecting field Platforms.",
"ExceptionType": "EPiServer.Find.ProjectionException",
"StackTrace": " at EPiServer.Find.ProjectionHelper.ReplaceExpressionWithConstantFromField(Dictionary`2 args, Expression expression)\r\n at EPiServer.Find.Helpers.Linq.ExpressionReplacer`1.Visit(Expression expression)\r\n at EPiServer.Find.Helpers.Linq.ExpressionVisitor.VisitExpressionList(ReadOnlyCollection`1 original)\r\n at EPiServer.Find.Helpers.Linq.ExpressionVisitor.VisitMethodCall(MethodCallExpression m)\r\n at EPiServer.Find.Helpers.Linq.ExpressionVisitor.Visit(Expression exp)\r\n at EPiServer.Find.Helpers.Linq.ExpressionVisitor.VisitExpressionList(ReadOnlyCollection`1 original)\r\n at EPiServer.Find.Helpers.Linq.ExpressionVisitor.VisitNew(NewExpression nex)\r\n at EPiServer.Find.Helpers.Linq.ExpressionVisitor.Visit(Expression exp)\r\n at EPiServer.Find.ProjectionHelper.GetMappedSearchResultItem[TResult](SearchRequestBody requestBody, SearchHit`1 searchHit)\r\n at EPiServer.Find.ProjectionHelper.GetMappedResult[TResult](SearchRequestBody requestBody, SearchResults`1 jsonResult)\r\n at EPiServer.Find.SearchExtensions.GetProjectedResult[TResult](ISearch`1 search, SearchContext context)\r\n at EPiServer.Find.SearchExtensions.GetResult[TResult](ISearch`1 search)\r\n

(Closure , Object , Object[] )\r\n at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.<>c__DisplayClass10.b__9(Object instance, Object[] methodParameters)\r\n at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ExecuteAsync(HttpControllerContext controllerContext, IDictionary`2 arguments, CancellationToken cancellationToken)\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()\r\n at System.Web.Http.Controllers.ApiControllerActionInvoker.d__0.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()\r\n at System.Web.Http.Controllers.ActionFilterResult.d__2.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()\r\n at System.Web.Http.Dispatcher.HttpControllerDispatcher.d__1.MoveNext()",
"InnerException": {
"Message": "An error has occurred.",
"ExceptionMessage": "Argument types do not match",
"ExceptionType": "System.ArgumentException",
"StackTrace": " at System.Linq.Expressions.Expression.Constant(Object value, Type type)\r\n at EPiServer.Find.ProjectionHelper.ReplaceExpressionWithConstantFromField(Dictionary`2 args, Expression expression)"

#118138
Feb 27, 2015 15:40
Vote:
 

Hi,

Yes, but currently only for IEnumerable. You could either change your property to be of type IEnumerable<> instead of List<> or simply do a projection to 'Platforms' and issue the .FirstOrDefault() outside of the projection (as FirstOrDefault() is executed client side this causes no overhead as the entire Platform list is transfered anyway).

The best solution is to avoid the transfer altogether and simply extend game with a 'DefaultPlatform'-extension for game:

public static class GameExtensions
{
    public static Platform DefaultPlatform(this Game game)
    {
          return game.Platforms.FirstOrDefault();
    }
}

Index the extension:

client.Conventions.ForInstancesOf<Game>().IncludeField(x => x.DefaultPlatform());

Use 'DefaultPlatform in the projection:

searchQuery.Select(x => new
{
    Name = x.Name,
    Variant = x.DefaultPlatform()
});

/Henrik

#118203
Mar 02, 2015 14:19
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.