ExpressionEvaluator visits expression before translation of executing query. But it skips visiting ConstantExpression with IQueryable type. When that "constants" are being translated in process of outer query translation the error occurs if inner query contains not-plain evaluatable expressions because they are not considered as evaluatable. E.g. if inner query uses arrays (access it's elements, applies Contains, In), the outer query translation fails See example below:

namespace Sample
{
    using System;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;
    using System.Runtime.CompilerServices;
    using Xtensive.Orm;
    using Xtensive.Orm.Configuration;

    [HierarchyRoot]
    public class MyEntity : Entity
    {
        [Field][Key] public int Id { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var dc = new DomainConfiguration("sqlserver", "Data Source=.; Initial Catalog=DO40-Tests;Connection Timeout=300;Integrated Security = true;Max pool size=10");

            dc.Types.Register(typeof(Program).Assembly);
            dc.UpgradeMode = DomainUpgradeMode.Recreate;

            var sessionConfiguration = new SessionConfiguration(SessionOptions.AutoActivation | SessionOptions.ServerProfile);

            using (var domain = Domain.Build(dc))
            {
                using (var session = domain.OpenSession(sessionConfiguration))
                using (session.Activate())
                using (session.OpenTransaction(TransactionOpenMode.New))
                {
                    ShowError();
                }
            }
        }

        private static void ShowError()
        {
            var id = 0;
            var ids = new int[] { id };

            var queryElem = Query.All<MyEntity>().Where(it => it.Id == id).Select(it => it.Id);
            var queryArrayElem = Query.All<MyEntity>().Where(it => it.Id == ids[0]).Select(it => it.Id);

            Query.All<MyEntity>().Where(it => queryElem.Contains(it.Id)).ToArray();
            Console.WriteLine("Ok");

            Query.All<MyEntity>().Where(it => queryArrayElem.Contains(it.Id)).ToArray();
            Console.WriteLine("Ok");

            Query.All<MyEntity>().Where(CreateFilter(queryElem)).ToArray();
            Console.WriteLine("Ok");

            Query.All<MyEntity>().Where(CreateFilter(queryArrayElem)).ToArray();
            Console.WriteLine("Error");
        }

        private static Expression<Func<MyEntity, bool>> CreateFilter(IQueryable<int> query)
        {
            var param = Expression.Parameter(typeof(MyEntity), "it");
            var idMemberExpr = Expression.MakeMemberAccess(param, typeof(MyEntity).GetProperty(nameof(MyEntity.Id)));

            var containsMethod = typeof(Queryable).GetMethods()
                .Single(it => it.Name == nameof(Queryable.Contains) && it.GetParameters().Length == 2)
                .MakeGenericMethod(typeof(int));

            var containsCall = Expression.Call(containsMethod, Expression.Constant(query), idMemberExpr);

            return Expression.Lambda<Func<MyEntity, bool>>(containsCall, param);
        }
    }
}

Finally, I face exception: System.NotSupportedException: Binary expression '@.ids[0]' of type 'ArrayIndex' is not supported

asked Jul 12 '19 at 07:45

Platonov's gravatar image

Platonov
5778


One Answer:

Hello,

Must be something wrong with your CreateFilter() method. You can compare results of work of your query and the expression created by LinQ. My guess is that you don't need the Expression.Constant(query), just use query.Expression instead.

answered Sep 17 '19 at 05:58

Alexey%20Kulakov's gravatar image

Alexey Kulakov
77225

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