8

Is it possible to create a generic search method where key is unknown? for e.g Key for the List will be passed to the parameter and it performs a like search and return the filtered List.

Code should be something like:

public List<T> LikeSearch<T>(List<T> AllData,T key, string searchString)
{
  List<T> _list = new List<T>();
  //Perform the search on AllData based on searchString passed on the key   
  //given
 return _list;
}

Uses will be like:

Example 1

List<Users> _users = LikeSearch<Users>(AllUsers,'Name','sam');

Where AllUsers is the list of 100 users.

Example 2

List<Customers> _cust = LikeSearch<Customers>(AllCustomers,'City','London');

Where AllCustomers is the list of 100 Customers.

Please sugest

sajadre
  • 1,141
  • 2
  • 15
  • 30
Abhinaw Kaushik
  • 607
  • 6
  • 18
  • Why is `key` typed `T`? It seems to be a property name, shouldn't it always be a `string`? This can be solved using reflection. It'll be slow... – InBetween Aug 24 '16 at 10:19
  • Yes,correct sorry for that.... key will be string always here... – Abhinaw Kaushik Aug 24 '16 at 10:22
  • Take a look at [Dynamic LINQ](http://weblogs.asp.net/scottgu/dynamic-linq-part-1-using-the-linq-dynamic-query-library). – Alessandro D'Andria Aug 24 '16 at 10:30
  • Why not simply using linq? E.g. `_users.Where(user => user.Name == "sam")` and `_cust.Where(cust => cust.City == London)` ? it doesn't looks much longer than your `LikeSearch` usage example. – Sinatr Aug 24 '16 at 10:36
  • @Sinatr I think its because the OP only knows the member's *name*, he can't directly invoke it. You need reflection. `key` is actually a string, as per comments above. – InBetween Aug 24 '16 at 10:38
  • @InBetween, ah ok, then he has to use reflection indeed. Right inside linq `Where`. – Sinatr Aug 24 '16 at 10:39

3 Answers3

9

Assuming key always refers to a public property implemented by whatever type T is, you could do the following:

public static List<T> LikeSearch<T>(this List<T> data, string key, string searchString)
{
    var property = typeof(T).GetProperty(key, BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Instance);

    if (property == null)
        throw new ArgumentException($"'{typeof(T).Name}' does not implement a public get property named '{key}'.");

    //Equals
    return data.Where(d => property.GetValue(d).Equals(searchString)).ToList();

    //Contains:
    return data.Where(d => ((string)property.GetValue(d)).Contains(searchString)).ToList();
}
InBetween
  • 32,319
  • 3
  • 50
  • 90
  • @AbhinawKaushik You are welcome, always glad to help. I've fixed up the code a little, make sure you use the latest edit. – InBetween Aug 24 '16 at 11:08
  • Thanks again @InBetween I have already changed to contains for my uses. Many thanks. – Abhinaw Kaushik Aug 24 '16 at 11:49
  • Thanks a lot man! You solved my generic Linq search! `List searchableProperties; if (!string.IsNullOrEmpty(pagedRequest.SearchQuery)) { searchableProperties = objectType.GetProperties() .Where(p => p.GetCustomAttributes().OfType.Any()) .ToList(); query = query.Where(q => searchableProperties.Any(p => p.GetValue(q).ToString() == pagedRequest.SearchQuery)); }` – Cubelaster Nov 14 '18 at 20:20
2

I think this link will help you ... Questions are different but you could find your answer there.. For reference i am again posting here the answer ...

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Linq.Expressions;
using System.Reflection;
namespace Rextester
{
    public class Program
    {
        public static void Main(string[] args)
        {

            List<Demo> all= new List<Demo>();
            all.Add(new Demo{Name="a"});
            all.Add(new Demo{Name="ab"});
            all.Add(new Demo{Name="abc"});
            all.Add(new Demo{Name="cba"});
            all.Add(new Demo{Name="bac"});
            all.Add(new Demo{Name="ddd"});

            var t= Filter(all,"Name","a");

            Console.WriteLine(t.Count);
        }

        public static List<T> Filter<T>(List<T> Filterable, string PropertyName, object ParameterValue)
        {
          ConstantExpression c = Expression.Constant(ParameterValue);
          ParameterExpression p = Expression.Parameter(typeof(T), "xx");
          MemberExpression m = Expression.PropertyOrField(p, PropertyName);

          MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });  

          var containsMethodExp = Expression.Call(m, method, c);
          var Lambda= Expression.Lambda<Func<T, bool>>(containsMethodExp, p);           

          //var Lambda = Expression.Lambda<Func<T, Boolean>>(Expression.Equal(c, m), new[] { p });

          Func<T, Boolean> func = Lambda.Compile();
          return Filterable.Where(func).ToList();
        }
    }

    public class Demo
    {
        public string Name{get;set;}
    }
}
Community
  • 1
  • 1
Moumit
  • 8,314
  • 9
  • 55
  • 59
  • Moumit...Thanks for the help, this is working for a Equals scenario, I was looking for contains search. I think I need to do something with Expression.Equals to get Expression.Contains, if possible. anyways Thank you so much. – Abhinaw Kaushik Aug 24 '16 at 11:05
  • @AbhinawKaushik ...it's does not matter with which solution you will go ... Using `ExpressionTree` you also can achieve this .. – Moumit Aug 24 '16 at 11:53
0

With the linq method Where

list.Where(x => x.YourKey.Contains(searchString))

Example 1

List<Users> _users = AllUsers.Where(x => x.Name.Contains("sam"));

Example 2

List<Customers> _cust = AllCustomers.Where(x => x.City.Contains("London"));

You can write a method like this otherwise:

public List<T> LikeSearch<T>(List<T> list, Func<T, string> getKey, string searchString)
{
    return list.Where(x => getKey(x).Contains(searchString)).ToList();
}

And you can use it like this:

Example 1

List<Users> _users = LikeSearch(AllUsers, x => x.Name, "sam");

Example 2

List<Customers> _cust = LikeSearch(AllCustomers, x => x.City, "London");

EDIT: here a small benchmark about solutions proposed here

I benchmarked only the Contains version of everyone.

With this we can see (depending on your computer and stars...):

InBetween OneProperty: 00:00:00.0026050

Moumit OneProperty: 00:00:00.0013360

Mine OneProperty: 00:00:00.0010390

The two different classes are here to test if the number of properties change something

InBetween LotProperties: 00:00:00.0026378

Moumit LotProperties: 00:00:00.0012155

Mine LotProperties: 00:00:00.0010021

I'm really surprised how Moumit's solution is fast, I expected it to be slower with the compile at runtime. But nevertheless, we can see that GetProperty and GetValue are really slow.

The benchmark code:

    static void Main(string[] args)
    {
        int size = 10000;
        Dictionary<string, List<long>> time = new Dictionary<string, List<long>>()
        {
            {"InBetween OneProperty", new List<long>() },
            {"Moumit OneProperty", new List<long>() },
            {"Mine OneProperty", new List<long>() },
            {"InBetween LotProperties", new List<long>() },
            {"Moumit LotProperties", new List<long>() },
            {"Mine LotProperties", new List<long>() },
        };
        List<OneProperty> oneProperties = new List<OneProperty>();
        List<LotProperties> lotProperties = new List<LotProperties>();
        for (int i = 0; i < size; ++i)
        {
            oneProperties.Add(new OneProperty() { Key = i.ToString() });
            lotProperties.Add(new LotProperties() { Key = i.ToString() });
        }
        Stopwatch sw = new Stopwatch();
        for (int i = 0; i < 1000; ++i)
        {
            sw.Start();
            InBetween.LikeSearch(oneProperties, "Key", "999");
            sw.Stop();
            time["InBetween OneProperty"].Add(sw.Elapsed.Ticks);
            sw.Reset();
            sw.Start();
            Moumit.Filter(oneProperties, "Key", "999");
            sw.Stop();
            time["Moumit OneProperty"].Add(sw.Elapsed.Ticks);
            sw.Reset();
            sw.Start();
            Mine.LikeSearch(oneProperties, x => x.Key, "999");
            sw.Stop();
            time["Mine OneProperty"].Add(sw.Elapsed.Ticks);
            sw.Reset();

            sw.Start();
            InBetween.LikeSearch(lotProperties, "Key", "999");
            sw.Stop();
            time["InBetween LotProperties"].Add(sw.Elapsed.Ticks);
            sw.Reset();
            sw.Start();
            Moumit.Filter(lotProperties, "Key", "999");
            sw.Stop();
            time["Moumit LotProperties"].Add(sw.Elapsed.Ticks);
            sw.Reset();
            sw.Start();
            Mine.LikeSearch(lotProperties, x => x.Key, "999");
            sw.Stop();
            time["Mine LotProperties"].Add(sw.Elapsed.Ticks);
            sw.Reset();
        }
        foreach (string key in time.Keys)
            Console.WriteLine($"{key}: {new TimeSpan((long)time[key].Average())}");
        Console.ReadKey();
    }

    class OneProperty
    {
        public string Key { get; set; }
    }
    class LotProperties
    {
        public string A { get; set; }
        public string B { get; set; }
        public string C { get; set; }
        public string D { get; set; }
        public string E { get; set; }
        public string F { get; set; }
        public string G { get; set; }
        public string H { get; set; }
        public string I { get; set; }
        public string J { get; set; }
        public string K { get; set; }
        public string L { get; set; }
        public string M { get; set; }
        public string N { get; set; }
        public string O { get; set; }
        public string P { get; set; }
        public string Q { get; set; }
        public string R { get; set; }
        public string S { get; set; }
        public string T { get; set; }
        public string U { get; set; }
        public string V { get; set; }
        public string W { get; set; }
        public string X { get; set; }
        public string Y { get; set; }
        public string Z { get; set; }
        public string Key { get; set; }
    }
romain-aga
  • 1,441
  • 9
  • 14
  • The OP only knows `City`'s name, he can't directly invoke the property. – InBetween Aug 24 '16 at 10:37
  • Where did you read that ? He said that properties are unknown, maybe because the name can change, but he knows the name, how could he write the key string otherwise ? Your solution has still the same problem as mine if he doesn't know. – romain-aga Aug 24 '16 at 10:43
  • From the usage examples in the question and from commentaries. I dont follow you with my solution having the same problem. I use reflection to find the property with the given name. – InBetween Aug 24 '16 at 10:52
  • Guys, Unknown key meaning my List can be any class and Key will be one of the string Property of the class; So while writing a generic common method for search I don't know which class will be passed and what will be the key name. Hope I am making myself clear here. – Abhinaw Kaushik Aug 24 '16 at 11:07
  • @AbhinawKaushik You might want to see my edit, you might want to change the solution kept. – romain-aga Aug 24 '16 at 12:50