Search code examples
c#linqlambdawhere-clausedynamicobject

How do I search a dynamic object IEnumerable<dynamic> using a dynamically built lambda expression?


Previously, I had great help on my previous question, thank you vyrp , How do I create and populate a dynamic object using a dynamically built lambda expression

I'm now looking to search the dynamic object, and as before, I don't know the objects properties, and therefore what I'm searching until runtime.

Here's the code that builds the dynamic object:

        // Get list of optional fields
        var optFieldList = await _tbList_FieldRepository.GetAsync(lf => lf.ListID == listId && lf.DisplayInList == true);
        // order list of optional fields
        optFieldList.OrderBy(lf => lf.DisplayOrder);

        // Get base Data excluding Inactive if applicable
        IEnumerable<tbList_Data> primaryData = await _tbList_DataRepository.GetAsync(ld => ld.ListID == listId && (ld.IsActive == includeInactive ? ld.IsActive : true));

        // Build IEnumerable<dynamic> from base results plus any optional fields to be displayed in table   
        var results = primaryData.Select(pd => {
            dynamic result = new System.Dynamic.ExpandoObject();
            result.Id = pd.ID;
            result.PrimaryData = pd.PrimaryData;
            result.DisplayOrder = pd.DisplayOrder;
            result.IsActive = pd.IsActive;
            foreach (var optField in optFieldList)
            {
                switch (optField.FieldType.ToLower()) {
                    case "text":
                        ((IDictionary<string, object>)result).Add(optField.FieldName, pd.tbList_DataText.Where(ld => ld.DataRowID == pd.ID && ld.ListColumnID == optField.ID).Select(ld => ld.DataField).DefaultIfEmpty("").First());
                        break;
                }
            }
            return result;
        });

For the purpose of testing, I have 2 dynamic fields, "PhoneNumber" and "FuelType"

I can search the known field(s) i.e. PrimaryData, no problem, as below.

        results = results.Where(r => r.PrimaryData.Contains(searchString));

And the following will work if I know the field PhoneNumber at design time

        results = results.Where(r => r.PhoneNumber.Contains(searchString));

but what I want to do, is something like:

results = results.Where(r => r.PrimaryData.Contains(searchString) 
    || foreach(var optField in optFieldList)
    {
        r.optField.FieldName.Contains(searchString)
    })

ending up with

results = results.Where(r => 
    r.PrimaryData.Contains(searchString) 
    || r.PhoneNumber.Contains(searchString) ||    
    r.FuelType.Contains(searchString));

but obviously that code doesn't work. I've tried a bunch of different attempts, none successful, so I'm looking for suggestions. Thanks


Solution

  • Since you know that the dynamic element of your query is actually ExpandoObject, hence IDictionary<string, object>>, you can safely cast it to dictionary interface and use it to access the property values by name, while Enumerable.Any method can be used to simulate dynamic || condition:

    results = results.Where(r => r.PrimaryData.Contains(searchString)
        || optFieldList.Any(f =>
        {
            object value;
            return ((IDictionary<string, object>)r).TryGetValue(f.FieldName, out value)
                && value is string && ((string)value).Contains(searchString);
        }));