2

How to create dynamic search to list using different field from a model?

public class UserModel
    {
        public int UserId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string DisplayName
        {
            get {return $"{ LastName }, { FirstName }"; }            
        }
     }
// Sample search
private void Search(string fieldname, string searchString)
        {
            List<UserModel> data = Users.Where(x => x.fieldname == searchString).ToList();
        }

3 Answers3

3

You can use an expression parameter:

using System.Linq.Expressions;

//...

private List<UserModel> Search<T>(Expression<Func<UserModel, T>> criteria) 
{
  return Users.Where(criteria).ToList();
}

Used like this:

var matches = model.Search(x => x.FirstName == "Bob");
Jacob
  • 77,566
  • 24
  • 149
  • 228
  • These answer is the perfect solution for my problem. The most detailed way to do this is in here [link](https://stackoverflow.com/questions/1246576/dynamic-where-for-listt). – Dash_Xyz_Abc May 07 '20 at 05:20
  • @Enigmativity: it _was_ a little ambiguous if dynamic search criteria meant based on user data from the outside (strings) or dynamic from within the code base (a caller wants to use a different criteria). It appears it was the latter, though yours works for the former. – Jacob May 07 '20 at 14:49
1

The quick-and-dirty way is to use a switch with predefined property names. I don't recommend allowing users to specify the exact property or column to filter by because that will introduce privacy risks and security holes (e.g. as it would allow users to search for other users by email, name, or password without you realising it).

private async Task<List<UserModel>> SearchUsersAsync( String fieldname, String value )
{
    IQueryable<User> q = this.Users;

    switch( fieldName )
    {
    case nameof(UserModel.Name):
        q = q.Where( u => u.Name == value );
        break;

    case nameof(UserModel.Bio):
        q = q.Where( u => u.Bio.Contains( value ) );
        break;

    case nameof(UserModel.Email):
        q = q.Where( u => u.Email == value );
        break;

    default:
        throw new ArgumentOutOfRangeException( "Unsupported property name." );
    }

    List<User> users = await q
        .OrderBy( u => u.UserId )
        .ToListAsync()
        .ConfigureAwait(false);

    List<UserModel> userViewModels = users
        .Select( u => UserModel.FromUserEntity( u ) )
        .ToList();

    return userViewModels;
}

Using this approach, you can avoid the ArgumentOutOfRangeException in normal flow by using an enum instead, which can be a route parameter (and also support user-defined ordering too!):

enum UserProperty
{
    None,
    Name,
    Bio,
    Email
}

internal static class QueryExtensions
{
    public static IQueryable<User> WhereProperty( this IQueryable<User> q, UserProperty prop, String value )
    {
        if( String.IsNullOrWhiteSpace( value ) ) return q;

        switch( prop)
        {
        case UserProperty.None:
            return q;

        case UserProperty.Name:
            return q.Where( u => u.Name == value );

        caseUserProperty.Bio:
            return q.Where( u => u.Bio.Contains( value ) );

        case UserProperty.Email:
            return q.Where( u => u.Email == value );

        default:
            throw new ArgumentOutOfRangeException( "Unsupported property name." );
        }
    }

    public static IOrderedQueryable<User> OrderByProperty( this IQueryable<User> q, UserProperty prop, Boolean asc )
    {
        switch( prop )
        {
        case UserProperty.None:
            return q;

        case UserProperty.Name:
            return asc ? q.OrderBy( u => u.Name ) : q.OrderByDescending( u => u.Name );

        case UserProperty.Bio:
             return asc ? q.OrderBy( u => u.Bio ) : q.OrderByDescending( u => u.Bio );

        case UserProperty.Email:
             return asc ? q.OrderBy( u => u.Email ) : q.OrderByDescending( u => u.Email );

        default:
            throw new ArgumentOutOfRangeException( "Unsupported property name." );
        }
    }
}

And these extension methods can be used like so:

private async Task<List<UserModel>> SearchUsersAsync( UserProperty filterProp = UserProperty.None, String filterValue = null, UserProperty sortProp = UserProperty.None, Boolean sortAscending = true )
{
    List<User> users = await this.Users
        .WhereProperty( filterProp, filterValue )
        .OrderByProperty( sortProp, sortAscending )
        .ToListAsync()
        .ConfigureAwait(false);

    List<UserModel> userViewModels = users
        .Select( u => UserModel.FromUserEntity( u ) )
        .ToList();

    return userViewModels;
}
Dai
  • 141,631
  • 28
  • 261
  • 374
0

I find dictionaries are useful for keeping all of the hard-coded:

private Dictionary<string, Func<UserModel, string, bool>> _filters =
    new Dictionary<string, Func<UserModel, string, bool>>()
    {
        { "FirstName", (u, x) => u.FirstName == x },
        { "LastName", (u, x) => u.LastName == x },
        { "DisplayName", (u, x) => u.DisplayName == x },
    };

private void Search(string fieldname, string searchString)
{
    List<UserModel> data = Users.Where(u => _filters[fieldname](u, searchString)).ToList();
}
Enigmativity
  • 113,464
  • 11
  • 89
  • 172