Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Better way to write this linq query?

Currently i am using a combination of switch operations to generate a linq query, and i am thinking the code is a little bloated.

Is there any way to optimize this code, maybe someway to dynamically build it?

public string[] GetPeopleAutoComplete(string filter, int maxResults, string searchType, string searchOption)
{
    var query = from people in _context.People select people;
    switch (searchOption)
    {
        case "StartsWith":
            switch (searchType)
            {
                case "IdentityCode":
                    query = query.Where(o => o.IdentityCode.StartsWith(filter));
                    return query.Select(o => o.IdentityCode).Take(maxResults).ToArray();
                case "Firstname":
                    query = query.Where(o => o.Firstname.StartsWith(filter));
                    return query.Select(o => o.Firstname).Distinct().Take(maxResults).ToArray();
                case "Surname":
                    query = query.Where(o => o.Surname.StartsWith(filter));
                    return query.Select(o => o.Surname).Distinct().Take(maxResults).ToArray();
            }
            break;

        case "EndsWith":
            switch (searchType)
            {
                case "IdentityCode":
                    query = query.Where(o => o.IdentityCode.EndsWith(filter));
                    return query.Select(o => o.IdentityCode).Take(maxResults).ToArray();
                case "Firstname":
                    query = query.Where(o => o.Firstname.EndsWith(filter));
                    return query.Select(o => o.Firstname).Distinct().Take(maxResults).ToArray();
                case "Surname":
                    query = query.Where(o => o.Surname.EndsWith(filter));
                    return query.Select(o => o.Surname).Distinct().Take(maxResults).ToArray();
            }
            break;

        case "Contains":
            switch (searchType)
            {
                case "IdentityCode":
                    query = query.Where(o => o.IdentityCode.Contains(filter));
                    return query.Select(o => o.IdentityCode).Take(maxResults).ToArray();
                case "Firstname":
                    query = query.Where(o => o.Firstname.Contains(filter));
                    return query.Select(o => o.Firstname).Distinct().Take(maxResults).ToArray();
                case "Surname":
                    query = query.Where(o => o.Surname.Contains(filter));
                    return query.Select(o => o.Surname).Distinct().Take(maxResults).ToArray();
            }
            break;
    }

    return query.Select(o => o.IdentityCode).Take(maxResults).ToArray();
}
like image 736
xqwzid Avatar asked Jan 20 '23 07:01

xqwzid


1 Answers

This is exactly where dynamically building expressions is useful:

public string[] GetPeopleAutoComplete(
    string filter, int maxResults, string searchType, string searchOption)
{
    IQueryable<Person> query = _context.People;

    var property = typeof(Person).GetProperty(searchType);
    var method = typeof(string).GetMethod(searchOption, new[] { typeof(string) });

    query = query.Where(WhereExpression(property, method, filter));

    var resultQuery = query.Select(SelectExpression(property));

    if (searchType == "Firstname" || searchType == "Lastname")
        resultQuery = resultQuery.Distinct();

    return resultQuery.Take(maxResults).ToArray();
}

Expression<Func<Person, bool>> WhereExpression(
    PropertyInfo property, MethodInfo method, string filter)
{
    var param = Expression.Parameter(typeof(Person), "o");
    var propExpr = Expression.Property(param, property);
    var methodExpr = Expression.Call(propExpr, method, Expression.Constant(filter));
    return Expression.Lambda<Func<Person, bool>>(methodExpr, param);
}

Expression<Func<Person, string>> SelectExpression(PropertyInfo property)
{
    var param = Expression.Parameter(typeof(Person), "o");
    var propExpr = Expression.Property(param, property);
    return Expression.Lambda<Func<Person, string>>(propExpr, param);
}

This doesn't solve your default case, but that should be relatively easy to add. Also, using reflection like this might be slow, so you might want to cache the results of GetProperty() and GetMethod().

Another thing to note is that the part that chooses whether to use Distinct() still depends on the property names, but maybe you have a better condition for that (or you could use attributes on the properties).

And the two helper methods don't need to know anything about Person, so it would be trivial to make them generic.

like image 160
svick Avatar answered Jan 25 '23 22:01

svick