Search code examples
.netlinq

How to filter a List<MyObject> where at least a property have a match?


I've a list of Object such as:

var data = ctx.Clinics.AsNoTracking().OrderBy(p => p.Name).Select(p => new
{
    ID = p.ID,
    Name = p.Name,
    Region = p.Regions.Description,
    City = p.City,
    Address = p.Address,
    Phone = p.Phone,
    InterestPassive = p.InterestPassive,
    InterestGlad = p.InterestGlad,
    Gross = p.Activities.Where(e => e.Converted.HasValue && e.Converted.Value && e.DateActivity.HasValue).Sum(s => (decimal?)s.FirstEstimate) ?? 0,
    DiscountApplied = p.Activities.Where(e => e.Converted.HasValue && e.Converted.Value && e.DateActivity.HasValue && e.Discount != null).Sum(e => (e.FirstEstimate / 100) * e.Discount) ?? 0,
}).ToList();

which is a result set for a DataTable.

On this list, I'd like to apply a filter on every property of the list which "contains" a input string (such as free textbox that will search everywhere in a grid):

string searchTerm = Request.Form.GetValues("search[value]").FirstOrDefault();

Is there a way on LINQ to search a value for every property within a list of Objects?


Solution

  • If you don't mind using Reflection, and you intend to search all public properties, you can use the following extensions to do a Contains search against every public property:

    public static class IQueryableEFExt {
        #region Predicates
        // findTerm - string that must be contained by any property
        // r => s => r.AllPropertyValues().Any(pv => pv.ToString().Contains(findTerm))
        public static Func<T, bool> IsAnyContains<T>(string findTerm) => r => r.AnyPropertyContains(findTerm);
    #endregion
    #region Where
        // findTerm - string that must be contained by any property
        // dbq.Where(r => r.AllPropertyValues().Any(pv => pv.ToString().Contains(findTerm)))
        public static IEnumerable<T> WhereAnyContains<T>(this IEnumerable<T> src, string findTerm) => src.Where(IsAnyContains<T>(findTerm));
    #endregion
    #region OrderBy
        // findTerm - string that must be contained by any property
        // dbq.OrderBy(r => r.AllPropertyValues().Any(pv => pv.ToString().Contains(findTerm)) ? 1 : 2)
        public static IOrderedEnumerable<T> OrderByAnyContains<T>(this IEnumerable<T> src, string findTerm)
            => src.OrderBy(r => IsAnyContains<T>(findTerm)(r) ? 1 : 2);
    
        // findTerm - string that must be contained by any property
        // dbq.OrderByDescending(r => r.AllPropertyValues().Any(pv => pv.ToString().Contains(findTerm)) ? 1 : 2)
        public static IOrderedQueryable<T> OrderByLikeAnyDescending<T>(this IQueryable<T> dbq, string findTerm)
            => dbq.OrderByDescending(r => IsAnyContains<T>(findTerm)(r));
    #endregion
    }
    

    This code depends on a custom extension method:

    public static bool AnyPropertyContains<T>(this T anObject, string substr) => typeof(T).GetProperties().Any(pf => pf.GetValue(anObject).ToString().Contains(substr));    
    

    If a little performance is important to you, you could refactor to cache the return value of GetProperties() to not do that lookup every time.

    You would use it like:

    var ans = data.WhereAnyContains(searchTerm);