Search code examples
c#ignite

Ignite LINQ provider projection support for non-anonymous types


I'm trying to use projection in my LINQ query, found following example on https://www.gridgain.com/docs/latest/developers-guide/net-specific/net-linq:

var query = ignite.GetCache<EmployeeKey, Employee>(CacheName).AsCacheQueryable().Where(emp => emp.Value.IsIntern);
...
var custom = query.Select(emp => new {Id = emp.Key, Name = emp.Value.Name, Age = emp.Value.Age});

It seems that only C# anonymous types are supported right now. I tried to use queries below but they failed with NotSupportedException:

var query = ignite.GetCache<EmployeeKey, Employee>(CacheName).AsCacheQueryable().Where(emp => emp.Value.IsIntern);
...
var custom1 = query.Select(emp => new Employee { Id = emp.Key, Name = emp.Value.Name, Age = emp.Value.Age});
var custom2 = query.Select(emp => new Employee(emp.Key, emp.Value.Name, emp.Value.Age));

Are there any plans to support classes in select methods? When to expect this feature?

PS: Select projection with constructor call actually works but you cannot use Where clause after Select method. I've updated ThinClientSqlExample.LinqExample method from Apache.Ignite.Examples:

            IQueryable<Employee> query = cache.AsCacheQueryable()
                .Select(emp => emp.Value)               
                .Select(emp => new Employee(emp.Name, emp.Salary, emp.Address, null, null))
                .Where(emp => emp.Salary > 0);

And received exception:

System.NotSupportedException: 'Expression not supported: new Employee([emp].Value.Name, [emp].Value.Salary, [emp].Value.Address, null).Salary' 

Had a lot of debugging of Apache.Ignite/Remote.Linq and found a workaround to support Where method call after Select with constructor:

        public class SetNewExpressionMembersVisitor : ExpressionVisitor
        {
            protected override Expression VisitNew(NewExpression node)
            {
                var constructorParameters = node.Constructor.GetParameters();
                var typeMembers = node.Type.GetMembers();
                var constructorMembers = new MemberInfo[constructorParameters.Length];
                for (int i = 0; i < constructorParameters.Length; i++)
                {
                    var parameter = constructorParameters[i];
                    var member = typeMembers.FirstOrDefault(x => string.Compare(x.Name, parameter.Name, true) == 0);
                    if (member == null)
                    {
                        throw new NullReferenceException($"Unable to find member of type '{node.Type.FullName}' for '{parameter.Name}' constructor parameter");
                    }

                    constructorMembers[i] = member;
                }


                return Expression.New(node.Constructor, node.Arguments, constructorMembers);
            }
        }

Solution

  • query.Select(emp => new Employee(emp.Key, emp.Value.Name, emp.Value.Age));

    Constructor call projections should work, can you double check this? It works for me on recent Ignite versions (2.8+).

    query.Select(emp => new Employee(emp.Key, emp.Value.Name...

    MemberInit projections are not supported, I've filed a ticket, you can expect it in the next release.

    As a workaround, use intermediate anonymous type projection:

    var custom1 = query.Select(emp => new { Id = emp.Key, Name = emp.Value.Name})
                       .AsEnumerable()
                       .Select(emp => new Employee { Id = emp.Id, Name = emp.Name});