1

I Want to apply dynamic project for my List Collection. User will be selecting the columns which are noting but properties of my List. I want to get subset of columns from LINQ Statement. I want to use Dynamic LINQ. Can any1 provide how to implement project with expression tree. I want to apply Dynamic projection on Azure table storage..

I try to implement below code,It is not working,it throw exception while reading the property:

Property XXXX is not defined for type 'System.String'

Code:

DataMovementDataContext dbMovement = new DataMovementDataContext();
var entity = dbMovement.ListofAccountingDocs2_1075s.AsQueryable();
Type type = entity.ElementType;
var entityParam = Expression.Parameter(entity.ElementType, "row");
Expression expr = entityParam;
string[] props = "AccountingDocumentNbr,GLCompanyCode,DocumentFiscalYearNbr".Split(',');
var epr =  GenerateMemberExpression<ListofAccountingDocs2_1075, string>("Name");

foreach (string prop in props)
{
    // use reflection (not ComponentModel) to mirror LINQ
    PropertyInfo pi = type.GetProperty(prop);
    expr = Expression.Property(expr, pi);
   //  type = pi.PropertyType; //Property 'System.String GLCompanyCode' is not defined for type 'System.String'
}

// row => row.Property
// var columnLambda = Expression.Lambda(  Expression.Property(entityParam, "GLCompanyCode"), entityParam);
var columnLambda = Expression.Lambda(Expression.Property(expr, "AccountingDocumentNbr,GLCompanyCode"), entityParam);

// Items.Select(row => row.Property)
var selectCall = Expression.Call(typeof(Queryable), "Select", new Type[] { entity.ElementType, columnLambda.Body.Type }, entity.Expression, columnLambda);

// Items.Select(row => row.Property).Distinct
var distinctCall = Expression.Call(typeof(Queryable), "Distinct", new Type[] { typeof(string) }, selectCall);

// colvalue => colvalue
var sortParam = Expression.Parameter(typeof(string), "AccountingDocumentNbr");
var columnResultLambda = Expression.Lambda(sortParam, sortParam);

// Items.Select(row => row.Property).Distinct.OrderBy(colvalue => colvalue)
var ordercall = Expression.Call(typeof(Queryable), "OrderBy",
           new Type[] { typeof(string), columnResultLambda.Body.Type },
           distinctCall, columnResultLambda);

var result =  entity.Provider.CreateQuery(ordercall);
foreach (var item in result)
{
    Console.Write(item);
}
abatishchev
  • 98,240
  • 88
  • 296
  • 433
user145610
  • 2,949
  • 4
  • 43
  • 75

2 Answers2

3

It would be way easier if you posted the class definitions for the models that you are using.

However, it looks like you are trying to get multiple properties by chaining them. This doesn't work. I think what you want is:

  new { 
      AccountingDocumentNbr = document.AccountingDocumentNbr, 
      GLCompanyCode = document.GLCompanyCode , 
      DocumentFiscalYearNbr = document.DocumentFiscalYearNbr
  };

but the foreach (string prop in props) loop is actually what you would need in order to give you:

  document.AccountingDocumentNbr.GLCompanyCode.DocumentFiscalYearNbr

Now, it looks like document.AccountingDocumentNbr is a string, because you are getting that Property 'System.String GLCompanyCode' is not defined for type 'System.String' error. When you look at it like this, the error makes sense... System.String doesn't have a GLCompanyCode property, and you are building a chained property expression that expects it to have one. What else could happen?

You're not going to be able to get the anonymous object it looks like you are targeting, unless there is an instance of that type already in your solution. This is because anonymous types are not dynamic types. They might look like it, but they are actually internal types compiled into the assembly, and they don't function any differently than any other class with the same members and custom overrides for Equals(object obj), GetHashCode(), and ToString(). So, unless you have a way to reference a class with the definition you are looking for, you're not going to be able to access these members using reflection (because they don't exist). You are much better off just using lambda expressions.

For a little added clarity, here's what the class definition for the anonymous type above would look like (pretty much).

public class <>f__AnonymousType0<T1,T2,T3>
{
    private readonly T1 accountingDocumentNbr;
    private readonly T2 glCompanyCode;
    private readonly T3 documentFiscalYearNbr;

    public T1 AccountingDocumentNbr
    {
        get { return accountingDocumentNbr; }
    }

    public T2 GLCompanyCode
    {
        get { return glCompanyCode; }
    }

    public T3 DocumentFiscalYearNbr
    {
        get { return documentFiscalYearNbr; }
    }

    public <>f__AnonymousType0(T1 accountingDocumentNbr, T2 glCompanyCode, T3 documentFiscalYearNbr)
    {
        this.accountingDocumentNbr = accountingDocumentNbr;
        this.glCompanyCode = glCompanyCode;
        this.documentFiscalYearNbr = documentFiscalYearNbr;
    }

    public override string ToString()
    {
        var builder = new StringBuilder();
        builder.Append("{ AccountingDocumentNbr = ");
        builder.Append(AccountingDocumentNbr);
        builder.Append(", GLCompanyCode = ");
        builder.Append(GLCompanyCode);
        builder.Append(", DocumentFiscalYearNbr = ");
        builder.Append(DocumentFiscalYearNbr);
        builder.Append(" }");
        return builder.ToString();
    }

    public override bool Equals(object value)
    {
        var type = value as <>f__AnonymousType0<T1,T2,T3>;
        return (type != null) && EqualityComparer<T1>.Default.Equals(type.AccountingDocumentNbr, AccountingDocumentNbr) && EqualityComparer<T2>.Default.Equals(type.GLCompanyCode, GLCompanyCode) && EqualityComparer<T3>.Default.Equals(type.DocumentFiscalYearNbr, DocumentFiscalYearNbr);
    }

    public override int GetHashCode()
    {
        int num = 0x7a2f0b42;
        num = (-1521134295*num) + EqualityComparer<T1>.Default.GetHashCode(AccountingDocumentNbr);
        num = (-1521134295*num) + EqualityComparer<T2>.Default.GetHashCode(GLCompanyCode);
        return (-1521134295*num) + EqualityComparer<T3>.Default.GetHashCode(DocumentFiscalYearNbr);
    }
}
smartcaveman
  • 41,281
  • 29
  • 127
  • 212
0

I was able to solve my problem.

public static Expression<Func<ListofAccountingDocs2_1075, ListofAccountingDocs2_1075>> BuildExpression(string parameters)
{  
    dynamic test = new ExpandoObject();
    var sourceMembers = typeof(ListofAccountingDocs2_1075).GetProperties();

    string[] selectparams =  parameters.Split(',');//property names are comma seperated

    foreach (var item in selectparams)
      {         
          ((IDictionary<string, object>)test).Add(item,string.Empty); 
      }
    IDictionary<string,object> test2 = new Dictionary<string,object>();

    List<PropertyInfo> destinationProperties = new List<PropertyInfo>();

    foreach (var item in ((IDictionary<string, object>)test))
    {
      var selectedColumn  = typeof(ListofAccountingDocs2_1075).GetProperties().FirstOrDefault(k =>
         k.Name.Equals(item.Key));

      if (selectedColumn != null)
          destinationProperties.Add(selectedColumn);
    }

    var name = "src";

    var parameterExpression = Expression.Parameter(typeof(ListofAccountingDocs2_1075), name);

    return Expression.Lambda<Func<ListofAccountingDocs2_1075, ListofAccountingDocs2_1075>>(
        Expression.MemberInit(
            Expression.New(typeof(ListofAccountingDocs2_1075)),
            destinationProperties.Select(k => Expression.Bind(k,
                Expression.Property(
                    parameterExpression, k.Name)
                )
            ).ToArray()
            ),
        parameterExpression
    );

}
Ryan T. Grimm
  • 1,307
  • 1
  • 9
  • 17
user145610
  • 2,949
  • 4
  • 43
  • 75