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(),
});
}
Edited to state that it works with EF 6.