I have a page that allow the user to search by multiple fields. One can be used or all. The user can set the operator per field to Equals, Contains, Starts With, etc... Looks like this
I am using EntityFrame Work and retrieving the data using a lamba like this one:
listOfPeople = adDB.People.Where(x => x.LastName.StartsWith(lastName) && x.FirstName.StartsWith(firstName)).OrderBy(x => x.LastName)
The question, How do I dynamically create the where clause depending on the data provided by the user?
You can use something along the lines of a Func factory to do this since the where clause takes in a Func.
Example:
public class Program
{
public static void Main(string[] args)
{
var people = new[]
{
new Person {FirstName = "Hello", LastName = "World"},
new Person {FirstName = "Foo", LastName = "Bar"},
};
Console.WriteLine(people.Where(FuncFactory.GetFilterFunc<Person>(FilterType.Contains, x => x.FirstName, "ello")).Any());
Console.WriteLine(people.Where(FuncFactory.GetFilterFunc<Person>(FilterType.Equals, x => x.FirstName, "ello")).Any());
Console.WriteLine(people.Where(FuncFactory.GetFilterFunc<Person>(FilterType.Contains, x => x.LastName, "ar")).Any());
Console.WriteLine(people.Where(FuncFactory.GetFilterFunc<Person>(FilterType.Equals, x => x.LastName, "ar")).Any());
Console.ReadKey();
}
}
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public enum FilterType
{
Contains,
Equals
}
public static class FuncFactory
{
public static Func<T, bool> GetFilterFunc<T>(FilterType filterType, Func<T, IComparable> propFunc, string filter)
{
switch (filterType)
{
case FilterType.Contains:
return x => (propFunc(x) as string).Contains(filter);
case FilterType.Equals:
return x => (propFunc(x) as string).Equals(filter);
default:
throw new ArgumentException("Invalid FilterType");
}
}
}
You probably want to use the Predicate Builder:
http://www.albahari.com/nutshell/predicatebuilder.aspx
Related
I have the following code snippet, and I can't seem to come up with an elegant way to refactor it so I'm not violating copy/paste programming. Please note the snippet is not complete, but I believe it should have the necessary information to provide decent context. Any help would be much appreciated!
public static void Main()
{
IEnumerable<Person> dataSet = new List<Person>
{
new Person{ ID = "1", PrimaryName = "Prim", SecondaryName = "Sec"},
new Person{ ID = "2", PrimaryName = "test", SecondaryName = "Sec2"},
new Person{ ID = "3", PrimaryName = "test", SecondaryName = "Sec3"}
};
string attribute = "LastName";
OperatorValue queryOperator = OperatorValue.Equal;
string value = "test";
switch (attribute)
{
case ("LastName"):
if (queryOperator.Equals(OperatorValue.Equal))
{
dataSet = dataSet.Where(p => p.PrimaryName.Equals(value));
}
else if (queryOperator.Equals(OperatorValue.NotEquals))
{
dataSet = dataSet.Where(p => !p.PrimaryName.Equals(value));
}
else if (queryOperator.Equals(OperatorValue.StartsWith))
{
dataSet = dataSet.Where(p => p.PrimaryName.StartsWith(value));
}
else
{
dataSet = dataSet.Where(p => p.PrimaryName.Contains(value));
}
break;
case ("FirstName"):
if (queryOperator.Equals(OperatorValue.Equal))
{
dataSet = dataSet.Where(p => p.SecondaryName.Equals(value));
}
else if (queryOperator.Equals(OperatorValue.NotEquals))
{
dataSet = dataSet.Where(p => !p.SecondaryName.Equals(value));
}
else if (queryOperator.Equals(OperatorValue.StartsWith))
{
dataSet = dataSet.Where(p => p.SecondaryName.StartsWith(value));
}
else
{
dataSet = dataSet.Where(p => p.SecondaryName.Contains(value));
}
break;
case ("ID"):
if (queryOperator.Equals(OperatorValue.Equal))
{
dataSet = dataSet.Where(p => p.ID.Equals(value));
}
else if (queryOperator.Equals(OperatorValue.NotEquals))
{
dataSet = dataSet.Where(p => !p.ID.Equals(value));
}
else if (queryOperator.Equals(OperatorValue.StartsWith))
{
dataSet = dataSet.Where(p => p.ID.StartsWith(value));
}
else
{
dataSet = dataSet.Where(p => p.ID.Contains(value));
}
break;
}
foreach (Person person in dataSet)
Console.WriteLine(person.ID);
}
public enum OperatorValue
{
Equal,
NotEquals,
StartsWith
}
public class Person
{
public string ID { get; set; }
public string SecondaryName { get; set; }
public string PrimaryName { get; set; }
}
There are more cases in the switch statement which I would like to treat the same way, so you can see this is starting to get ugly fast. I'm trying to come up with a helper method that I could just call for each case, but I'm completely stuck and out of ideas. I appreciate any help.
So the value of attribute defines which property you want to select. Apparently these properties are all string properties.
With the value of operatorValue you want to select the action with the selected string property: equal, unequal, contains, etc.
It seems to me, that you have an enum OperatorValue, something like this:
enum OperatorValue
{
Equals,
NotEquals,
StartsWith,
Contains,
}
I'll write extension methods similar to the already existing Queryable.Where. See extension methods demystified
Convert string queryOperator to OperatorValue:
public static OperatorValue ToOperatorValue(this string queryOperator)
{
// TODO: exception if input null
return Enum.Parse(typeof(OperatorValue), queryOperator);
// TODO: decide what to do if queryOperator has not existing enum value
}
Usage:
string queryOperatorTxt = ...
OperatorValue operator = queryOperatorTxt.ToOperatorValue();
Of course we also need a method to convert the string attribute into a propertySelector:
public static Expression<Func<Person, string>> ToPropertySelector(this string attributeTxt)
{
// TODO: check incorrect parameters
Expression<Func<Person, string>> propertySelector;
switch (attributeTxt)
{
case "LastName":
propertySelector = (person) => person.PrimaryName;
break;
case "FirstName":
propertySelector = (person) => person.SecondaryName;
break;
... etc
default:
// TODO: decide what to do with unknown attributeTxt
}
}
usage:
string attributeTxt = ...
Expression<Func<Person, string>> propertySelector = attributeTxt.ToPropertySelector();
Now that we know how to convert the queryOperator and attribute, we can create an extension for the Where:
public static IQueryable<Person> Where(
this IQueryable<Person> source,
Expression<Func<Person, string>> propertySelector,
OperatorValue operator,
string value)
{
// TODO: exceptions if incorrect parameters
switch (operator)
{
case OperatorValue.Equals:
return source.Where(item => propertySelector(item) == value);
case OperatorValue.NotEquals:
return source.Where(item => propertySelector(item) != value);
case OperatorValue.StartsWith:
return source.Where(item => propertySelector(item).StartsWith(value);
case OperatorValue.Contains:
return source.Where(item => propertySelector(item).Contains(value);
default:
// TODO
}
}
Put it all together:
IQueryable<Person> dataSet = db.Persons;
string attributeTxt = ...
string queryOperatorTxt = ...
string value = ...
Expression<Func<Person, string>> propertySelector = attributeTxt.ToPropertySelector();
OperatorValue operation = queryOperatorTxt.ToOperatorValue();
IQueryable<Person> queryPersons = dataSet.Where(propertySelector, operation);
Well, doesn't that look like a neat LINQ statement!
If you want to make the code reusable and not having to copy/paste everytime you need want to filter by a new property. You can do the following:
public static void Main()
{
IEnumerable<Person> dataSet = new List<Person>
{
new Person{ ID = "1", PrimaryName = "Prim", SecondaryName = "Sec"},
new Person{ ID = "2", PrimaryName = "test", SecondaryName = "Sec2"},
new Person{ ID = "3", PrimaryName = "test", SecondaryName = "Sec3"}
};
string attribute = "LastName";
OperatorValue queryOperator = OperatorValue.Equal;
string value = "test";
Func<Person, string> getter = GetFuncForProperty(attribute);
dataSet = Filter(dataSet, getter, queryOperator, value);
foreach (Person person in dataSet)
Console.WriteLine(person.ID);
}
public static IEnumerable<Person> Filter(IEnumerable<Person> source, Func<Person, string> getter, OperatorValue operatorValue, string searchValue)
{
switch (operatorValue)
{
case OperatorValue.Equal:
return source.Where(p => getter.Invoke(p).Equals(searchValue));
case OperatorValue.NotEquals:
return source.Where(p => !getter.Invoke(p).Equals(searchValue));
case OperatorValue.StartsWith:
return source.Where(p => getter.Invoke(p).StartsWith(searchValue));
}
throw new ArgumentException(operatorValue.ToString() + " is not supported");
}
public static Func<Person, string> GetFuncForProperty(string propertyName)
{
switch (propertyName)
{
case "ID":
return (Person person) => person.ID;
case "FirstName":
return (Person person) => person.SecondaryName;
case "LastName":
return (Person person) => person.PrimaryName;
}
throw new ArgumentException(propertyName + " is not supported");
}
public enum OperatorValue
{
Equal,
NotEquals,
StartsWith
}
public class Person
{
public string ID { get; set; }
public string SecondaryName { get; set; }
public string PrimaryName { get; set; }
}
See it running at: https://dotnetfiddle.net/MAqvZZ
Is it possible to build part of query based on Expression?
public List<SelectListItem> GetSelectedListFromEntity<T>(Expression<Func<T, object>> property) where T : BaseEntity<int>
{
var result = _repository.Query<T>().Select(p => new SelectListItem()
{
Text = property, //? (in simple case it looks like: p.Name + p.Category)
Value = p.Id.ToString(),
).ToList();
return result;
}
For:
var result = GetSelectedListFromEntity<Product>(p => p.Name + p.Category);
If you want it to work with a db provider, something giving you an IQueryable you can do something with expression trees like this.
Given these classes:
public class BaseEntity
{
public int Id { get; set;}
}
public class Product : BaseEntity
{
public string Name { get; set; }
public string Category { get; set; }
}
Now you need a function to make the expression:
public Expression<Func<T, SelectListItem>> CreateExpression<T>(Expression<Func<T, string>> textExp) where T : BaseEntity
{
var arg = textExp.Parameters.First();
var param = new ParameterExpression[] { arg };
var body = Expression.MemberInit(
Expression.New(typeof(SelectListItem)),
Expression.Bind(typeof(SelectListItem).GetMember("Text").First(), textExp.Body),
Expression.Bind(typeof(SelectListItem).GetMember("Value").First(),
Expression.Call(
Expression.PropertyOrField(arg, "Id"),
"ToString",
new Type[0]
)));
var exp = Expression.Lambda(body, param);
return (Expression<Func<T, SelectListItem>>)exp;
}
In C# 6 it's better practice to replace those magic strings with nameof().
Then finally you can call it with something like this:
var items = new List<Product> { new Product { Id = 0, Name = "Test", Category = "Cat" } };
var result = items.AsQueryable().Select(CreateExpression<Product>(p => p.Name + p.Category)).ToList();
Now as long as you linq provider can cope with making SelectListItems you should be fine.
i am having some difficulties creating a proper select.
i have my custom class:
internal class classA{
internal string FieldName { get; set; }
internal string FieldValue { get; set; }
internal bool IncludeInChecksum { get; set; }
}
what i am trying to do is to build and concatinate a querystring using the class list from above.
for the following object:
List<classA> requestParams = ...
the query string should look like:
a=1&b=2&c=3
ordered by FieldName ascending
where IncludeInChecksum = true
FieldName = FieldValue
preMD5= requestParams
.Where(x=>x.IncludeInChecksum)
.OrderBy(y=>y.FieldName)
.Aggregate((i,j)=>(i.FieldName+ "=" +j.FieldValue));
this is where i am stuck.
thanks in advance
I will try to get all name=value strings with the help of the Select() method. And then convert the result to array. After that just use String.Join() method to get the desired result.
Join(String, String[]) concatenates all the elements of a string array, using the specified separator between each element.
var preMD5= requestParams
.Where(x => x.IncludeInChecksum)
.OrderBy(y => y.FieldName)
.Select(z => string.Format("{0}={1}", z.FieldName, z.FieldValue))
.ToArray();
preMD5 = string.Join("&", preMD5);
Aggregate aggregates values from different rows. You need to combine values from different fields. For this you use Select:
requestParms.Where(...).OrderBy(...).Select(f=>f.FieldName+"="+f.FieldValue)
This will return an IEnumerable of name=value strings. You can use string.Join to combine them into one string.
I know it's been answered,
but still I wanted to provide my solution.
I used a dedicated method to build the query string parameter,
and an extension method to concat it all.
Hope this helps.
public class classA
{
internal string FieldName { get; set; }
internal string FieldValue { get; set; }
internal bool IncludeInChecksum { get; set; }
public classA(string fieldName, string fieldValue, bool includeInChecksum)
{
this.FieldName = fieldName;
this.FieldValue = fieldValue;
this.IncludeInChecksum = includeInChecksum;
}
public string ToQueryString()
{
return string.Format("{0}={1}",
this.FieldName,
this.FieldValue);
}
public void Test()
{
var list = new List<classA> {
new classA("A", "1", true) ,
new classA("D", "4", true) ,
new classA("B", "2", false) ,
new classA("C", "3", true)
};
var result = list.
Where(o => o.IncludeInChecksum).
OrderBy(o => o.FieldName).
Select(o => o.ToQueryString()).
ToStringEx("&");
}
}
public static class ExtensionMethods
{
public static string ToStringEx<T>(this IEnumerable<T> items, string separetor = ",")
{
if (items == null)
{
return "null";
}
return string.Join(separetor, items.Select(o => o != null ? o.ToString() : "[null]").ToArray());
}
}
Does anyone have any idea how I call a lambda expression from within a lambda expression?
If I have:
public class CourseViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public static Expression<Func<Course, CourseViewModel>> AsViewModel = x =>
new CourseViewModel
{
Id = x.Id,
Name = x.Name,
}
}
public class StudentViewModel
{
public int Id { get; set; }
public string Name{ get; set; }
public string PreferredCheese { get; set; }
public IEnumerable<CourseViewModel> Courses { get; set; }
public static Expression<Func<Student, StudentViewModel>> AsViewModel = x =>
new StudentViewModel
{
Id = x.Id,
Name = x.Name,
PreferredCheese = x.PreferredCheese,
Courses = ???I'd like to call the CourseViewModel.AsViewModel here
}
}
In the code above rather than writing the AsViewModel expression within the StudentViewModel as:
Courses = new CourseViewModel
{
Id = x.Id,
Name = x.Name,
}
I'd like to call CourseViewModel.AsViewModel to allow code re-use and to keep the code converting a Course to a CourseViewModel in the CourseViewModel class. Is this possible?
You can just use x.Courses.Select(c => CourseViewModel.AsViewModel(c)) So your whole expression would be:
public static Expression<Func<Student, StudentViewModel>> AsViewModel = x =>
new StudentViewModel
{
Id = x.Id,
Name = x.Name,
PreferredCheese = x.PreferredCheese,
Courses = x.Courses.Select(c => CourseViewModel.AsViewModel(c))
}
If you want to preserve CourseViewModel.AsViewModel as a nested expression (which you probably do if you're using a LINQ query provider), this gets tricky; you effectively have to build up the AsViewModel expression in StudentViewModel by yourself:
using E = System.Linq.Expressions.Expression; // for brevity
// ...
static StudentViewModel()
{
var s = E.Parameter(typeof(Student), "s");
//
// Quick hack to resolve the generic `Enumerable.Select()` extension method
// with the correct type arguments.
//
var selectMethod = (Expression<Func<Student, IEnumerable<CourseViewModel>>>)
(_ => _.Courses.Select(c => default(CourseViewModel)));
var lambda = E.Lambda<Func<Student, StudentViewModel>>(
E.MemberInit(
E.New(typeof(StudentViewModel)),
E.Bind(
typeof(StudentViewModel).GetProperty("Id"),
E.Property(s, "Id")),
E.Bind(
typeof(StudentViewModel).GetProperty("Name"),
E.Property(s, "Name")),
E.Bind(
typeof(StudentViewModel).GetProperty("PreferredCheese"),
E.Property(s, "PreferredCheese")), // LOL?
E.Bind(
typeof(StudentViewModel).GetProperty("Courses"),
E.Call(
((MethodCallExpression)selectMethod.Body).Method,
E.Property(s, "Courses"),
CourseViewModel.AsViewModel))
),
s);
AsViewModel = lambda;
}
The resulting expression tree is equivalent to:
s => new StudentViewModel {
Id = s.Id,
Name = s.Name,
PreferredCheese = s.PreferredCheese,
Courses = s.Courses.Select(x => new CourseViewModel { Id = x.Id, Name = x.Name })
}
Consider this class:
public class Column<T>
{
public string Header { get; set; }
public Func<T, string> ValueExpression { get; set; }
}
used like this:
var columns = new List<Column<Employee>>
{
new Column<Employee> {Header = "Employee Id", ValueExpression = e => e.EmployeeID.ToString()},
new Column<Employee> {Header = "Name", ValueExpression = e => e.FirstName + " " + e.LastName},
new Column<Employee> {Header = "Employee Birthday Year", ValueExpression = e => e.BirthDate.HasValue ? e.BirthDate.Value.Year.ToString() : ""},
new Column<Employee> { Header = "test", ValueExpression = e => e.Address}
}
I would like to do a .Select() on an IQueryable to make it only retrieve the needed fields from the database.
So I want to do something like this:
var expressions = columns.Select(c => c.ValueExpression).Combine();
IQueryable<Employee> employees = EmployeeRepository.GetEmployees();
employees = employees.Select(expressions);
Only "Combine()" obviously doesn't exist.. :-)
public static Func<T, U[]> Combine<T, U>(this Func<T, U>[] functions) {
return t => functions.Select(fun => fun(t)).ToArray();
}
I'd declare that for generic IEnumerable<Func<T, U>> instead of array:
public static Func<T, IEnumerable<U>> Combine<T, U>(this IEnumerable<Func<T, U>> functions)
{
return t => functions.Select(fun => fun(t));
}
As mentioned in comments, this is not likely to work directly with LINQ to SQL. However, you could grab LINQ to SQL results by doing a .AsEnumerable() and process the rest on client side.