2

Imagine I have a class like this

public class Person
{
    public string Surname {get; set;}
    public string GivenNames {get; set;}
    public DateTime DateOfBirth {get; set;}
}

The user selects which properties they want to retrieve and stores these in a List<String> RetrieveProperties

Now I want to use a linq select statement to select only those properties which the user has specified without knowing at design time what these are. Can I do something like this?

var result = qry.Where(x=>x.RetrieveProperties[i])

Getting an anonymous object like that would allow a DataGrid to bind to the collection and neatly display only what the user has selected.

Can this be done?

Bradley Uffner
  • 16,641
  • 3
  • 39
  • 76
Nick Forwood
  • 89
  • 1
  • 5
  • No you cannot do that. You can, however, use [EF Raw SQL](https://learn.microsoft.com/en-us/ef/core/querying/raw-sql). – CodingYoshi Oct 02 '19 at 00:06

3 Answers3

0

You can do this by dynamically creating the lambda you pass to Select:

Func<Data,Data> CreateNewStatement( string fields )
{
    // input parameter "o"
    var xParameter = Expression.Parameter( typeof( Data ), "o" );

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

    // create initializers
    var bindings = fields.Split( ',' ).Select( o => o.Trim() )
        .Select( o => {

            // property "Field1"
            var mi = typeof( Data ).GetProperty( o );

            // original value "o.Field1"
            var xOriginal = Expression.Property( xParameter, mi );

            // set value "Field1 = o.Field1"
            return Expression.Bind( mi, xOriginal );
        }
    );

    // 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<Data,Data>>( xInit, xParameter );

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

Then you can use it like this:

var result = list.Select( CreateNewStatement( "Field1, Field2" ) );

Dude0001
  • 3,019
  • 2
  • 23
  • 38
  • This doesn't compile for me. Can you provide a sample that instantiates `list` and compiles? – Rufus L Oct 02 '19 at 00:52
  • @thegeneral is correct you won't be able to select in to an anonymous type. This is just a sample of how you'd do it with known types. If you can reshape the data and the expected output to strings, then you could do something like above. This has been the only way I've been able to build LINQ dynamically. – Dude0001 Oct 02 '19 at 01:15
  • Unless I misunderstood the question, the OP gave us the type, though: `Person`. The properties to select are unknown. – Rufus L Oct 02 '19 at 01:32
0

Thanks for the input. I've got a work around that breaks my MVVM philosophy but as an amateur it will do because some of the responses involved techniques that are beyond my ability. I thought that the answers given were very elegant but I don't have time to research those techniques at this point, and I don't like to use code of which I don't have a good understanding.

Basically I crate an Expando object and the use the (IDictionary<string, object>) interface to add the properties. The DataGrid can't autogenerate columns from this kind of object so I use some code-behind on my view to create columns at run time according to which properties the user has selected.

Here is the code I use assuming I have an IEnumerable called result which contains the type of object my application works on


foreach(var r in result)
{
    dynamic x = new ExpandoObject();
    foreach(var p in PropertiesSelectedByUser)
    {
        ((IDictionary<string, object>)x)[p.Name] = p.GetPropertyValue(r);
    }
}

PropertiesSelectedByUser is a collection of a type which implements GetPropertyValue(Data d)

Nick Forwood
  • 89
  • 1
  • 5
0

You can try using ExpandoObject:

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Windows.Forms;
using System.Dynamic;

private void Test()
{
  // Initialize test data
  var items = new List<Person>();
  items.Add(new Person { Surname = "Surname 1", DateOfBirth = new DateTime(2019, 09, 02) });
  items.Add(new Person { Surname = "Surname 2", DateOfBirth = new DateTime(2019, 09, 02) });
  items.Add(new Person { Surname = "Surname 3", DateOfBirth = new DateTime(2019, 09, 03) });
  items.Add(new Person { Surname = "Surname 4", DateOfBirth = new DateTime(2019, 09, 04) });
  // Create test query
  var query = items.Where(item => item.DateOfBirth == new DateTime(2019, 09, 02));
  var RetrieveProperties = new List<string> { "Surname", "DateOfBirth" };
  var results = new List<ExpandoObject>();
  if ( RetrieveProperties.Count != 0 )
    foreach ( var item in query )
    {
      dynamic result = new ExpandoObject();
      var resultInterface = (IDictionary<string, object>)result;
      foreach ( var propertyName in RetrieveProperties )
      {
        var property = item.GetType().GetProperty(propertyName);
        if ( property != null )
          resultInterface[property.Name] = property.GetValue(item);
      }
      results.Add(result);
    }
  // Providing results in the datagrid
  DataGridViewTest.Rows.Clear();
  DataGridViewTest.Columns.Clear();
  int count = results.Count();
  if ( count > 0 )
  {
    foreach ( var property in results[0] )
      DataGridViewTest.Columns.Add(property.Key, property.Key);
    for ( int indexRow = 0; indexRow < count; indexRow++ )
    {
      DataGridViewTest.Rows.Add();
      int indexValue = 0;
      foreach ( var property in results[indexRow] )
        DataGridViewTest.Rows[indexRow].Cells[indexValue++].Value = property.Value.ToString();
    }
  }
}

Result:

enter image description here