3
1

Hi,

I'm having an "unable to translate" issue when trying to use Generics in expression to have reusable code logic across different objects projection in group by. Below is a simplified code base to reproduce the issue (note in workaround section that when "flatening" the generics, it is working). This is something i don't want to do as it would duplicate logics for every different way of grouping I might do. Do you see my mistake, or something that could be fixed in DO ?

Model:

public enum Level  {    Intern,    Junior,    Senior,    Lead  }
public enum Department  {    RandD,    It,    Marketing,    Sales  }

[HierarchyRoot]
public class Employee : Entity
{
    [Field, Key]
    public long Id { get; private set; }
    [Field]
    public long? FloorNumber { get; set; }
    [Field]
    public long? OfficeNumber { get; set; }
    [Field]
    public long? Salary { get; set; }
    [Field]
    public Level Level { get; set; }
    [Field]
    public Department Department { get; set; }
}

Expression:

public class PersonSimplifiedByOffice : PersonSimplified<PersonSimplifiedByOffice>
{
    public long OfficeNumber;
    public static Expression<Func<PersonSimplifiedByOffice, bool>> IsBigOffice = e => e.OfficeNumber < 3;
}
public class PersonSimplified<T> : PersonSimplified where T: PersonSimplified
{
    public static Expression<Func<T, bool>> Require360Eval = e => e.Department != Department.Sales && (e.Level == Level.Junior || e.Level == Level.Senior);
    public static Expression<Func<T, bool>> NeedProductTraining = e => e.Department != Department.It && e.Level != Level.Intern;   
}
public class PersonSimplified
{
    public Level Level;
    public Department Department;
    public long? Cost;
}

Query:

var q = Query.All<Employee>()
    .Where(e => e.FloorNumber == 1)
    .Select(e=>new PersonSimplifiedByOffice()
    {
        OfficeNumber = e.OfficeNumber.GetValueOrDefault(),
        Cost = e.Salary,
        Department = e.Department,
        Level = e.Level
    })
    .GroupBy(g => g.OfficeNumber).Select(g => new 
    {
        OfficeNumber = g.Key,
        TotalCost = g.Sum(e=>e.Cost),
        BifOfficeCost = (g as IQueryable<PersonSimplifiedByOffice>).Where(PersonSimplifiedByOffice.IsBigOffice).Sum(e=>e.Cost),
        TrainingCount = (g as IQueryable<PersonSimplifiedByOffice>).Where(PersonSimplifiedByOffice.NeedProductTraining).Sum(e=>e.Cost),
    });

Exception:

Xtensive.Orm.QueryTranslationException: Unable to translate 'Query.All().Where(e => (e.FloorNumber == ((Nullable<Int64>)1))).Select(e => new PersonSimplifiedByOffice() {
  OfficeNumber = e.OfficeNumber.GetValueOrDefault(),
  Cost = e.Salary,
  Department = e.Department,
  Level = e.Level
}).GroupBy(g => g.OfficeNumber).Select(g => new @<OfficeNumber, TotalCost, BifOfficeCost, TrainingCount>(
  g.Key,
  g.Sum(e => e.Cost),
  (g as IQueryable<PersonSimplifiedByOffice>).Where(.IsBigOffice).Sum(e => e.Cost),
  (g as IQueryable<PersonSimplifiedByOffice>).Where(.NeedProductTraining).Sum(e => e.Cost)
))' expression. See inner exception for details. ---> System.NotSupportedException: Specified method is not supported.
   at Xtensive.Orm.Providers.ExpressionProcessor.VisitMemberInit(MemberInitExpression mi)
   at Xtensive.Linq.ExpressionVisitor`1.Visit(Expression e)
   at Xtensive.Orm.Providers.ExpressionProcessor.VisitMemberAccess(MemberExpression m)
   at Xtensive.Linq.ExpressionVisitor`1.Visit(Expression e)
   at Xtensive.Orm.Providers.ExpressionProcessor.VisitUnary(UnaryExpression expression)
   at Xtensive.Linq.ExpressionVisitor`1.Visit(Expression e)
   at Xtensive.Orm.Providers.ExpressionProcessor.VisitLambda(LambdaExpression l)
   at Xtensive.Linq.ExpressionVisitor`1.Visit(Expression e)
   at Xtensive.Orm.Providers.ExpressionProcessor.Translate()
   at Xtensive.Orm.Providers.SqlCompiler.ProcessExpression(LambdaExpression le, List`1[] sourceColumns)
   at Xtensive.Orm.Providers.SqlCompiler.VisitCalculate(CalculateProvider provider)
   at Xtensive.Orm.Rse.Compilation.Compiler`1.Compile(CompilableProvider cp)
   at Xtensive.Orm.Providers.SqlCompiler.VisitAggregate(AggregateProvider provider)
   at Xtensive.Orm.Rse.Compilation.Compiler`1.Compile(CompilableProvider cp)
   at Xtensive.Orm.Providers.SqlCompiler.VisitApply(ApplyProvider provider)
   at Xtensive.Orm.Rse.Compilation.Compiler`1.Compile(CompilableProvider cp)
   at Xtensive.Orm.Providers.SqlCompiler.VisitApply(ApplyProvider provider)
   at Xtensive.Orm.Rse.Compilation.Compiler`1.Compile(CompilableProvider cp)
   at Xtensive.Orm.Rse.Compilation.Compiler`1.Xtensive.Orm.Rse.Compilation.ICompiler.Compile(CompilableProvider provider)
   at Xtensive.Orm.Providers.CompilationService.Compile(CompilableProvider provider, CompilerConfiguration configuration)
   at Xtensive.Orm.Linq.Translator.Translate[TResult](ProjectionExpression projection, IEnumerable`1 tupleParameterBindings)
   at Xtensive.Orm.Linq.QueryProvider.Translate[TResult](Expression expression, CompilerConfiguration compilerConfiguration)
   --- End of inner exception stack trace ---
   at Xtensive.Orm.Linq.QueryProvider.Translate[TResult](Expression expression, CompilerConfiguration compilerConfiguration)
   at Xtensive.Orm.Linq.QueryProvider.Execute[TResult](Expression expression)
   at Xtensive.Orm.Linq.Queryable`1.GetEnumerator()
Inner Exception: System.NotSupportedException: Specified method is not supported.
   at Xtensive.Orm.Providers.ExpressionProcessor.VisitMemberInit(MemberInitExpression mi)
   at Xtensive.Linq.ExpressionVisitor`1.Visit(Expression e)
   at Xtensive.Orm.Providers.ExpressionProcessor.VisitMemberAccess(MemberExpression m)
   at Xtensive.Linq.ExpressionVisitor`1.Visit(Expression e)
   at Xtensive.Orm.Providers.ExpressionProcessor.VisitUnary(UnaryExpression expression)
   at Xtensive.Linq.ExpressionVisitor`1.Visit(Expression e)
   at Xtensive.Orm.Providers.ExpressionProcessor.VisitLambda(LambdaExpression l)
   at Xtensive.Linq.ExpressionVisitor`1.Visit(Expression e)
   at Xtensive.Orm.Providers.ExpressionProcessor.Translate()
   at Xtensive.Orm.Providers.SqlCompiler.ProcessExpression(LambdaExpression le, List`1[] sourceColumns)
   at Xtensive.Orm.Providers.SqlCompiler.VisitCalculate(CalculateProvider provider)
   at Xtensive.Orm.Rse.Compilation.Compiler`1.Compile(CompilableProvider cp)
   at Xtensive.Orm.Providers.SqlCompiler.VisitAggregate(AggregateProvider provider)
   at Xtensive.Orm.Rse.Compilation.Compiler`1.Compile(CompilableProvider cp)
   at Xtensive.Orm.Providers.SqlCompiler.VisitApply(ApplyProvider provider)
   at Xtensive.Orm.Rse.Compilation.Compiler`1.Compile(CompilableProvider cp)
   at Xtensive.Orm.Providers.SqlCompiler.VisitApply(ApplyProvider provider)
   at Xtensive.Orm.Rse.Compilation.Compiler`1.Compile(CompilableProvider cp)
   at Xtensive.Orm.Rse.Compilation.Compiler`1.Xtensive.Orm.Rse.Compilation.ICompiler.Compile(CompilableProvider provider)
   at Xtensive.Orm.Providers.CompilationService.Compile(CompilableProvider provider, CompilerConfiguration configuration)
   at Xtensive.Orm.Linq.Translator.Translate[TResult](ProjectionExpression projection, IEnumerable`1 tupleParameterBindings)
   at Xtensive.Orm.Linq.QueryProvider.Translate[TResult](Expression expression, CompilerConfiguration compilerConfiguration)

Workaround:

public class PersonFlatByOffice
{
    //PersonSimplifiedByOffice
    public long OfficeNumber;
    public static Expression<Func<PersonFlatByOffice, bool>> IsBigOffice = e => e.OfficeNumber < 3;

    //PersonSimplified<PersonFlatByOffice>
    public static Expression<Func<PersonFlatByOffice, bool>> Require360Eval = e => e.Department != Department.Sales && (e.Level == Level.Junior || e.Level == Level.Senior);
    public static Expression<Func<PersonFlatByOffice, bool>> NeedProductTraining = e => e.Department != Department.It && e.Level != Level.Intern;

    //PersonSimplified
    public Level Level;
    public Department Department;
    public long? Cost;
}

var stats = Query.All<Employee>()
    .Where(e => e.FloorNumber == 1)
    .Select(e=>new PersonFlatByOffice()
    {
        OfficeNumber = e.OfficeNumber.GetValueOrDefault(),
        Cost = e.Salary,
        Department = e.Department,
        Level = e.Level
    })
    .GroupBy(g => g.OfficeNumber).Select(g => new 
    {
        OfficeNumber = g.Key,
        TotalCost = g.Sum(e=>e.Cost),
        BifOfficeCost = (g as IQueryable<PersonFlatByOffice>).Where(PersonFlatByOffice.IsBigOffice).Sum(e=>e.Cost),
        TrainingCount = (g as IQueryable<PersonFlatByOffice>).Where(PersonFlatByOffice.NeedProductTraining).Count(),
    });

Many thanks

Note:

This works fine with Entity Framework:

public class CompanyContext: DbContext 
{ 
  public DbSet<Employee> Employees { get; set; }
}

using (var ctx = new CompanyContext())
{
        ctx.Populate();
        ctx.SaveChanges();
        var stat1 = ctx.Employees
          .Select(e => new PersonSimplifiedByFloor
          {
            Floor = e.FloorNumber ?? 0,
            Cost = e.Salary,
            Department = e.Department,
            Level = e.Level
          })
          .GroupBy(g => g.Floor).Select(g => new
          {
            Floor = g.Key,
            TotalCost = g.Sum(e => e.Cost),
            EvaluationCount = g.AsQueryable().Where(PersonSimplifiedByFloor.Require360Eval).Count(),
            HasEvacuationManager = g.AsQueryable().Where(PersonSimplifiedByFloor.EvacuationManager).Any(),
          });
}

asked Apr 23 at 03:25

rle's gravatar image

rle
6737

edited Apr 25 at 07:11

Edited to state that it works with EF 6.

(Apr 25 at 07:18) rle rle's gravatar image

One Answer:

Hello rle,

Thank you for the report. This is a DO problem, I have confirmed it and it should be fixed. We've created a task for it and now we're currently investigating the cause.

answered Apr 28 at 01:36

Alexey%20Kulakov's gravatar image

Alexey Kulakov
63215

Your answer
Please start posting your answer anonymously - your answer will be saved within the current session and published after you log in or create a new account. Please try to give a substantial answer, for discussions, please use comments and please do remember to vote (after you log in)!
toggle preview

powered by OSQA