1

My application is ASP.NET MVC 5 / SQL Server.

I am trying to select specific columns from a list based on an array:

First list has 200 columns: Age, Gender, .....

var list1 = _reportRepository.ShowMasteView().ToList(); 

Second list has 20 columns: Age, Gender, ......

From the view I select the items to be displayed:

string[] lits2 = showColumn.Where(c => c.Value == true).Select(c=> c.Key).ToArray();

I get enter image description here

To get these two specific columns, I tried

 var nList = list1.Select(t2 => lits2.Any(t1 => t2.Contains(t1)));

I get an error

Can not resolve symbol "Contains"

I was able to do it using the following

var keys = "Age,Gender";
var connection = 
ConfigurationManager.ConnectionStrings["DALEntities"].ConnectionString;
            using (var dataAdapter = new SqlDataAdapter("SELECT " + keys
             + " from dbo.vw_MasterView", connection))
            {
                var dataTable = new DataTable();

                dataAdapter.Fill(dataTable);
                dataAdapter.FillSchema(dataTable, SchemaType.Mapped);
                return dataTable;
            }

Is there a better way in linq?

Erik Philips
  • 53,428
  • 11
  • 128
  • 150
hncl
  • 2,295
  • 7
  • 63
  • 129
  • What is the list type of list1? If this is a class you probably need something like `t2.PropertyName.Contains(t1)`. Furthermore I assume instead of `.Select` it should be `.Where`. `Any` returns a bool. With `Select` you would get a list of bools. – noox Jun 14 '19 at 20:33
  • IQueryable LIST – hncl Jun 14 '19 at 23:12
  • List? I'm also not sure what you really want. You write "... list has 200 columns..." A list has no columns. Do you mean a list of strings (List) with about 200 entries where each one is a column name? But this would not make any sense either. Is it a list of string arrays or some other collections? – noox Jun 15 '19 at 08:30
  • The list come from a database table that has 200 columns. I need to select only the columns using string array. – hncl Jun 15 '19 at 15:10
  • It's still not very clear what the input list is and what the output should be. I assume list1 is a list of objects where each object has 200 fields/properties - representing the 200 columns. Maybe you should look into this: https://stackoverflow.com/a/21085468/5550687 Here only one property is selected dynamically, but you can probably extend this. And it would be better to only select the needed columns in the database query and not to load all 200 and then use only 2. But this would be even more complicated I think. – noox Jun 15 '19 at 17:04

3 Answers3

1

From my understand it appears you are trying to extract/select a dynamic object that only has the desired properties/columns.

This can be achieved by building a dynamic expression/function to apply to the Select

The following builds an expression based on the model type and the provided properties

static class DynamicExtensions {
    public static IQueryable<dynamic> SelectDynamic<TModel>(this IQueryable<TModel> query, ISet<string> propertyNames) {
        var selector = query.BuildSelectorFor(propertyNames);
        return query.Select(selector);
    }

    static Expression<Func<TModel, dynamic>> BuildSelectorFor<TModel>(this IQueryable<TModel> query, ISet<string> propertyNames) {
        var modelType = typeof(TModel);
        var properties = modelType.GetProperties().Where(p => propertyNames.Contains(p.Name));

        // Manually build the expression tree for 
        // the lambda expression v => new { PropertyName = v.PropertyName, ... }

        // (TModel v) =>
        var parameter = Expression.Parameter(modelType, "v");
        // v.PropertyName
        var members = properties.Select(p => Expression.PropertyOrField(parameter, p.Name));
        var addMethod = typeof(IDictionary<string, object>).GetMethod(
                    "Add", new Type[] { typeof(string), typeof(object) });
        // { { "PropertyName", v.PropertyName}, ... }
        var elementInits = members.Select(m =>
            Expression.ElementInit(addMethod, Expression.Constant(m.Member.Name), Expression.Convert(m, typeof(object))));
        // new ExpandoObject()
        var newExpando = Expression.New(typeof(ExpandoObject));
        // new ExpandoObject() { { "PropertyName", v.PropertyName}, ... }
        var expando = Expression.ListInit(newExpando, elementInits);
        // (TModel v) => new ExpandoObject() { { "PropertyName", v.PropertyName}, ... }
        var lambdaExpression = Expression.Lambda<Func<TModel, dynamic>>(expando, parameter);
        return lambdaExpression;
    }
}

This takes advantage of ExpandoObject whose members can be dynamically added and removed at run time.

The following test was used as an example of how the above function is invoked.

[TestMethod]
public void DynamicList() {
    var list1 = new List<Person>
    {
        new Person{ Gender = "Male", Age = 10, FirstName = "Nama1", SampleNumber = 12},
        new Person{ Gender = "Male", Age = 12, FirstName = "Nama2", SampleNumber = 13},
        new Person{ Gender = "Female", Age = 13, FirstName = "Nama3", SampleNumber = 14},
        new Person{ Gender = "Male", Age = 14, FirstName = "Nama4", SampleNumber = 15},
    };
    var keys = new string[] { "Age", "Gender", };

    var nList = list1.AsQueryable().SelectDynamic(new HashSet<string>(keys));

    foreach (IDictionary<string, object> row in nList) {
        var msg = $"{{ {keys[0]} = {row[keys[0]]}, {keys[1]} = {row[keys[1]]} }}";
        Debug.WriteLine(msg);
    }
}

and produces the following output

{ Age = 10, Gender = Male }
{ Age = 12, Gender = Male }
{ Age = 13, Gender = Female }
{ Age = 14, Gender = Male }

The dynamic objects can be used in the View and it is a simple matter of calling the desired members.

For example suppose you have a model as follows

public class MyViewModel {
    public string MyProperty { get; set; }
    public string[] Keys  { get; set; }
    public List<dynamic> MyDynamicProperty { get; set; }
}

that was populated with data and given to the view

var list1 = _reportRepository.ShowMasteView(); 
var keys = new string[] { "Age", "Gender", };
var nList = list1.AsQueryable().SelectDynamic(new HashSet<string>(keys));

var viewModel = new MyViewModel {
    MyProperty = "Hello World",
    MyDynamicProperty = nList.ToList(),
    Keys = keys
};

return View(viewModel);

Then in the view you can use the model as desired, casting to get access to members in the expando object.

@model MyViewModel

...

<h2>@Model.MyProperty</h2>
<table>
    <tr>
@foreach(string key in Model.Keys) {
      <th>@key</th>
}
    </tr>
@foreach (IDictionary<string, object> row in Model.MyDynamicProperty) {
    <tr>
    @foreach(string key in Model.Keys) {
      <td>@row[@key]</td>
    }
    </tr>
}
</table>
Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • I am grateful, that exactly what I needed. Just two questions, how I can use the dynamic objects in the view, and how I can use it in a View Model or as Json. Thank you. – hncl Jun 17 '19 at 03:48
  • That is great, I will try to figure out how to make a DataTable because I am using the list to generate Kendo ui MVC Grid. Thank you for your great help. – hncl Jun 17 '19 at 04:33
  • I have two errors implementing the the ViewModel: Cannot implicitly convert type 'System.Linq.IQueryable' to 'System.Collections.Generic.List' in the controller, and The name 'key' does not exist in the current context in the View, _reportRepository.ShowMasteView() is returning a table from MS SQL. – hncl Jun 17 '19 at 16:06
  • I was able to figure out the second error @foreach @ was missing. – hncl Jun 17 '19 at 22:44
  • I still can't figure out the error Cannot implicitly convert type 'System.Linq.IQueryable' to 'System.Collections.Generic.List' in the controller. – hncl Jun 18 '19 at 16:33
  • @hncl I made that update yesterday when you mentioned it. `MyDynamicProperty = nList.ToList(),` just need to convert the querable to a list – Nkosi Jun 18 '19 at 16:34
  • I was able to generate DataTable, still having a challenge converting Dynamic View to Json. – hncl Jun 20 '19 at 06:20
0

I think you just need to use Contains on your list2.

var nList = list1.Where(t => lits2.Contains(t1));

Contains is a method for Lists. The code you had was trying to use it on a string.

James
  • 168
  • 8
0

If you have two list of a person's class

public class Person
{
    public int id { get; set; }
    public string name { get; set; }
}

If the lists are as below:

  var list1 = new List<Person>
       {
           new Person{ id = 1, name = "Nama1"},
           new Person{ id = 2, name = "Nama2"},
           new Person{ id = 3, name = "Nama3"},
           new Person{ id = 4, name = "Nama4"},
       };

        var list2 = new List<Person>
        {
            new Person{ id = 1, name = "Nama1"},
            new Person{ id = 2, name = "Nama2"},
        };

You can filter in the following ways

        var keys = list2.Select(x => x.id).ToList();

        var filter1= list1.Where(x => keys.Contains(x.id)).ToList();
        var filter2= list1.Where(x => keys.Contains(x.id)).Select(x => new { x.name }).ToList();
        var filter3= list1.Select(x => new
        {
            id = x.id,
            name = x.name,
            check = keys.Contains(x.id)
        }).Where(x => x.check).ToList();

If you have array of string you can use below code

array string same

var lis1 = new string[] {"name1", "name2","name3" };
var lis2 = new string[] { "name1" };

You can filter array of string in the following ways

 var items1= lis1.Where(x=>lis2.Contains(x)).ToList();
 var items= lis1.Select(x=> new { x, check= lis2.Contains(x) }).Where(x=>x.check == true).ToList();
Reza Jenabi
  • 3,884
  • 1
  • 29
  • 34
  • Thank you, very clear approach; my challenge is I am using an array that does not have id, just name. – hncl Jun 16 '19 at 15:06
  • Thank you. However, lis1 is an object (Sql table) not string. I still can't resolve Contains. – hncl Jun 16 '19 at 18:50