Gerar dinamicamente linq select com propriedades aninhadas

Atualmente, temos um pacote que gera linq select dinamicamente a partir dos campos da string. Ele funciona bem com propriedades simples, mas não foi projetado para funcionar com campos aninhados, como someObj.NestedObj.SomeField.

Nosso código atual funciona da seguinte forma no método de serviço:

_context.Shipments
    .Where(s => s.Id == request.Id) // it does not matter just an example
    .Select(request.Fields)
    .ToPage(request); // ToPage extension comes from a nuget package

O parâmetro "campos" do objeto de solicitação é apenas uma sequência separada por vírgulas, incluindo as propriedades do objeto Remessa.

Fiz uma refatoração para Remessa, agrupei alguns campos em uma nova classe denominada Endereço e adicione-a à Remessa como abaixo:

// before refactoring
class Shipment {
    // other fields...
    public string SenderAddress;
    public string SenderCityName;
    public string SenderCityId;

    public string RecipientAddress;
    public string CityName;
    public string CityId;
}

// after refactoring
class Shipment {
   // other fields...
   public Address Sender;
   public Address Recipient;
}

class Address {
    public string AddressText;
    public string CityName;
    public string CityId;
}

Para o mapeamento atual do banco de dados, adicionei os mapeamentos correspondentes como:

public class ShipmentMap : DataEntityTypeConfiguration<Shipment>
    {
        public ShipmentMap()
        {
            ToTable("Shipments");
            // other property mappings
            Property(s => s.Recipient.AddressText).HasMaxLength(1100).HasColumnName("RecipientAddress");
            Property(s => s.Recipient.CityName).HasMaxLength(100).HasColumnName("CityName");
            Property(s => s.Recipient.CityId).IsOptional().HasColumnName("CityId");

            Property(s => s.Sender.AddressText).HasMaxLength(1100).HasColumnName("SenderAddress");
            Property(s => s.Sender.CityName).HasMaxLength(100).HasColumnName("SenderCityName");
            Property(s => s.Sender.CityId).IsOptional().HasColumnName("SenderCityId");
        }
    }

DataEntityTypeConfiguration vem de pacotes de nuget como:

  public abstract class DataEntityTypeConfiguration<T> : EntityTypeConfiguration<T> where T : class
  {
    protected virtual void PostInitialize();
  }

Portanto, meu problema é que o select (fields) não funciona quando fields = "Recipient.CityId".

Como posso gerar dinamicamente linq para selecionar com campos aninhados?

Eu tentei abaixo usandoLINQ: seleção dinâmica mas não funciona.

// assume that request.Fields= "Recipient.CityId"

// in the service method
List<Shipment> x = _context.Shipments
    .Where(s => s.Id == request.Id)
    .Select(CreateNewStatement(request.Fields))
    .ToList();


 // I tried to generate select for linq here    
 Func<Shipment, Shipment> CreateNewStatement(string fields)
        {
            // input parameter "o"
            var xParameter = Expression.Parameter( typeof( Shipment ), "o" );

            // new statement "new Data()"
            var xNew = Expression.New( typeof( Shipment ) );

            // create initializers
            var bindings = fields.Split( ',' ).Select( o => o.Trim() )
                .Select(o =>
                {
                    string[] nestedProps = o.Split('.');
                    Expression mbr = xParameter;

                    foreach (var prop in nestedProps)
                        mbr = Expression.PropertyOrField(mbr, prop);

                    // property "Field1"
                    PropertyInfo mi = typeof( Shipment ).GetProperty( ((MemberExpression)mbr).Member.Name );
                    //
                    // original value "o.Field1"
                    var xOriginal = Expression.Property( xParameter, mi );

                    MemberBinding bnd = Expression.Bind( mi, xOriginal );
                    return bnd;
                });

            // initialization "new Data { Field1 = o.Field1, Field2 = o.Field2 }"
            var xInit = Expression.MemberInit( xNew, bindings );

            // expression "o => new Data { Field1 = o.Field1, Field2 = o.Field2 }"
            var lambda = Expression.Lambda<Func<Shipment,Shipment>>( xInit, xParameter );

            // compile to Func<Data, Data>
            return lambda.Compile();
        }

Ele lança uma exceção porque mbr se torna CityId após o loop e "mi" é nulo porque não há campo CityId na remessa. O que estou perdendo aqui? Como criar uma seleção dinâmica para uma determinada sequência com propriedades aninhadas?

ATUALIZAÇÃO:

Encontrei a solução e a adicionei como resposta, também criei uma essência do github para solução:

https://gist.github.com/mstrYoda/663789375b0df23e2662a53bebaf2c7c

questionAnswers(3)

yourAnswerToTheQuestion