0
votes

We have static classes to centralize our expressions

public static Expression<Func<PersonEntity, bool>> IsActivated()
    => pe => pe.ActivatedOn != null;

So we can just write e.g.

  DbContext.Persons
  .AsQueryable()
  .Where(PersonsQuery.IsActivated())
  .SingleOrDefault();

instead of

  .Where(pe => pe.ActivatedOn != null)

Which works in all kind of providers we use (e.g. Entity Framework).

But in Automapper this does not work in a Profile.

.ForMember(p => p.ActivatedPersons,
    opt => opt.MapFrom(e => e.Persons
            .AsQueryable()
            .Where(PersonsQuery.IsActivated())
            .SingleOrDefault()))

This results in

InvalidCastException: Unable to cast object of type 'System.Linq.Expressions.MethodCallExpression1' to type 'System.Linq.Expressions.LambdaExpression'.

If we use

.ForMember(p => p.ActivatedPersons,
    opt => opt.MapFrom(e => e.Persons
            .AsQueryable()
            .Where(pe => pe.ActivatedOn != null)
            .SingleOrDefault()))

it works.

Is there a way we can use our static expressions?

Exception Stack Details:

System.Linq.Expressions.ExpressionExtensions.UnwrapLambdaFromQuote(Expression expression) Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression) System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor) System.Linq.Expressions.ExpressionVisitor.Visit(Expression node) Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression) System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor) System.Linq.Expressions.ExpressionVisitor.Visit(Expression node) Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression) System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor) System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)F System.Linq.Expressions.ExpressionVisitor.VisitConditional(ConditionalExpression node) System.Linq.Expressions.ConditionalExpression.Accept(ExpressionVisitor visitor) System.Linq.Expressions.ExpressionVisitor.Visit(Expression node) System.Linq.Expressions.ExpressionVisitor.VisitMemberAssignment(MemberAssignment node) System.Linq.Expressions.ExpressionVisitor.VisitMemberBinding(MemberBinding node) System.Linq.Expressions.ExpressionVisitor.Visit(ReadOnlyCollection nodes, Func<T, T> elementVisitor) System.Linq.Expressions.ExpressionVisitor.VisitMemberInit(MemberInitExpression node) System.Linq.Expressions.MemberInitExpression.Accept(ExpressionVisitor visitor) System.Linq.Expressions.ExpressionVisitor.Visit(Expression node) Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor+PendingSelectorExpandingExpressionVisitor.Visit(Expression expression) Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.Expand(Expression query) Microsoft.EntityFrameworkCore.Query.QueryTranslationPreprocessor.Process(Expression query) Microsoft.EntityFrameworkCore.Query.RelationalQueryTranslationPreprocessor.Process(Expression query) Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor(Expression query) Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery(Expression query, bool async) Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore(IDatabase database, Expression query, IModel model, bool async) Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler+<>c__DisplayClass12_0.b__0() Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery(object cacheKey, Func<Func<QueryContext, TResult>> compiler) Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync(Expression query, CancellationToken cancellationToken) Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync(Expression expression, CancellationToken cancellationToken) Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable.GetAsyncEnumerator(CancellationToken cancellationToken) System.Runtime.CompilerServices.ConfiguredCancelableAsyncEnumerable.GetAsyncEnumerator() Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync(IQueryable source, CancellationToken cancellationToken)

1
Can you show call stack?Svyatoslav Danyliv
@SvyatoslavDanyliv sorry, added.Benjamin Abt

1 Answers

2
votes

Looks like it is known EF limitation. You can solve that only by third party extensions.

https://github.com/hazzik/DelegateDecompiler

https://github.com/axelheer/nein-linq

Simple sample using DelegateDecompiler

[Computed]
public static bool IsActivated(PersonEntity pe)
    => pe.ActivatedOn != null;

Automapper

.ForMember(p => p.ActivatedPersons,
    opt => opt.MapFrom(e => e.Persons
            .AsQueryable()
            .Where(pe => PersonsQuery.IsActivated(pe))
            .SingleOrDefault()))

Final query

DbContext.Persons
   .AsQueryable()
   .Where(pe => PersonsQuery.IsActivated(pe))
   .Decompile()
   .SingleOrDefault();

It is important to call Decompile or DecompileAsync

P.S.

I like how it was done by DelegateDecompiler, but nein-linq also usable. Also LINQKit can help here with its Invoke. But everything needs additional call Decompile() or ToInjectable() or AsExpandable() according to library.