Lambda Expressions and searching - c#

Lets say i have a form which have the following :
Name:TextBox
Email:TextBox
Age:TextBox
now i want to Get customers Collection based on this filter textboxs
so i want to to use something like :
List<customer> customers = getCustomerswhere(c=>c.name == txtName.Text && Email == txtEmail.Text);
now of course i dont know which he will fill and which he wont so
if (txtName.Text.trim() != "")
//something like c=>c.Name == txtName.text;
if (txtEmail.Text.trim() != "")
//something like and c=>c.Email == txtEmail.text;
how do i do this ! i cant concatenate lambda expressions , i know i can use dynamic expressions but i think there is easier way ? any idea how to implement this ?
ok i tried this:
Func<Customer,bool > a = (bb) => bb.fullName == "asdfsd";
Func<Customer, bool> b = c => c.lastName == "sdas";
Func<Customer, bool> cc = c => a(c) && b(c);
now comes another problem
the method im passing CC to is expecting Expression<Func<T, bool>> expression
so it doesnt work gives me compile time error cant convert between types!

you can create some expressions like:
var a = c => c.name == txtName.Text;
var b = c => c.name == txtName.Text;
and then concatenate them like this:
var result = c => a(c) && b(c);

Like this:
Func<Customer, bool> predicate = c => true;
if (txtName.Text.Trim() != "")
predicate = Concatenate(predicate, c => c.Name == txtName.text);
if (txtEmail.Text.Trim() != "")
predicate = Concatenate(predicate, c => c.Email == txtEmail.text);
static Func<T, bool> Concatenate(Func<T, bool> a, Func<T, bool> b) {
return t => a(t) && b(t);
}
The Concatenate method must be a separate method because lambda expressions capture variables by reference.
The line
predicate = c => predicate(c) && c.Name == txtName.text;
will result in a stack overflow because the predicate variable will always refer to the latest immutable delegate instance that you assign to it.

return Customers.Where(c => (txtName.Text.Trim() == "" || c.Name == txtName.Text)
&& (txtEmail.Text.Trim() == "" || c.Email == txtEmail.Text));
In other words, only impose the 'name' condition if the 'name' box is filled out, and only impose the 'email' condition if the 'email' box is filled out.
Note that you can use the String.IsNullOrWhiteSpace method in .NET 4.0 instead of the Trim technique you have used.

Here is how i Implemented it:
public class LambdaCriteries<T> : List<Expression<Func<T, bool>>>
{
public Expression<Func<T, bool>> GetFinalLambdaExpression()
{
var par = Expression.Parameter(typeof(T));
var intial = Expression.Invoke(this.First(),par);
var sec = Expression.Invoke(this.Skip(1).First(),par);
BinaryExpression binaryExpression = Expression.And(intial, sec);
if (this.Count> 2)
{
foreach (var ex in this.ToList().Skip(2))
{
binaryExpression = Expression.And(binaryExpression, Expression.Invoke(ex, par));
}
return Expression.Lambda<Func<T, bool>>(binaryExpression,par);
}
else
{
return Expression.Lambda<Func<T, bool>>(binaryExpression,par);
}
}
}
and to use it :
if(txtId.text != "")
criteries.Add(v => v.Id == int.Parse(txtId.text));
if(txtName.text != "")
criteries.Add(v => v.Name == txtId.text);
and final expression :
var finalexp = criteries.GetFinalLambdaExpression();

Related

Searching in MVC5 using linq

Using this Linq code I get any values corresponding to the search input i.e if I search country = Italy and gender = female I get both employees from Italy and employees who are female but I need it to be more specific.
i.e if I search Country = Italy and Gender = female I need to get female employees from Italy. Please suggest me a Linq code for the same
Also, I have five search inputs (First Name, Last Name, Designation, Country, Gender) so just (&&) only doesn't do the work here!
Here's the code:
List<Employee> Elist = userdb.Employees
.Where(i => i.FirstName == Fn ||
i.LastName == Ln ||
i.Designation == desig ||
i.Country == country ||
i.Gender == gender)
.ToList();
This is a situation where the nature of IQueryable comes in very useful. You can add Where clauses to your query without actually executing anything against the database. The SQL would only be executed when you materialise the data, for example using ToList(). This is called deferred query execution.
So you can write your code like this:
IQueryable<Employee> query = userdb.Employees;
if(!string.IsNullOrEmpty(Fn))
{
query = query.Where(e => e.FirstName == Fn);
}
if(!string.IsNullOrEmpty(Ln))
{
query = query.Where(e => e.LastName == Ln);
}
// etc. etc.
List<Employee> Elist = query.ToList();
Most likely, you are not wanting to include criteria that is not filled in. You would only want to filter by a value if the value exists (or is not null). Use an IQueryable to build your search and then assign it to Elist.
IQueryable<Employee> Query = userdb.Employees;
if (Fn != null) {
Query = Query.Where(i => i.FirstName.Equals(Fn));
}
if (Ln != null) {
Query = Query.Where(i => i.LastName.Equals(Ln));
}
if (desig != null) {
Query = Query.Where(i => i.Designation.Equals(desig));
}
if (country != null) {
Query = Query.Where(i => i.Country.Equals(country));
}
if (gender != null) {
Query = Query.Where(i => i.Gender.Equals(gender));
}
List<Employee> Elist = Query.ToList();
Personally I would use a PredicateBuilder here. A small example, let's say you just have 2 queries:
Expression<Func<Person, bool>> hasFirstName = p1 => p1.FirstName == Fn;
Expression<Func<Person, bool>> hasLastName= p2 => p2.LastName == Ln";
You could build this into a predicate builder like so and keep on expanding using any sort of logic:
var predicate = PredicateBuilder.False<Employee>();
if (!string.IsNullOrEmpty(Fn))
{
predicate = predicate.And(e => e.FirstName == Fn);
}
if (!string.IsNullOrEmpty(Ln))
{
predicate = predicate.And(e => e.FirstName == Ln);
}
var result = userdb.Employees.Where(predicate);
Try this
List<Employee> Elist = userdb.Employees
.Where(i => (Fn == null || i.FirstName == Fn ) &&
(Ln == null || i.LastName == Ln ) &&
(desig == null || i.Designation == desig) &&
(country == null || i.Country == country) &&
(gender == null || i.Gender == gender)
.ToList();

Expression tree for groupby with where clause and than select

From UI dynamic column are coming as parameter in API and based on the parameter I have to fetch data from database.
Example : In the below code, based on the column if condition linq query is being executed. Now I want to make it generic so that it serve if new column condition come in future.
public List<string> GetFilteredTypeAhead(string searchText,string searchForRole,int fiscalyear,int fiscalPeriod)
{
if (searchForRole == "column1")
{
var accounts = (from a in _context.Account
where a.column1.StartsWith(searchText) && a.FiscalPeriod == fiscalPeriod && a.FiscalYear ==fiscalyear
group a.column1 by a.column2 into g
select g.Key).ToList();
return accounts;
}
else if(searchForRole == "column2")
{
var accounts = (from a in _context.Account
where a.column2.StartsWith(searchText) && a.FiscalPeriod == fiscalPeriod && a.FiscalYear == fiscalyear
group a.column2 by a.column2 into g
select g.Key).ToList();
return accounts;
}
else if (searchForRole == "column3")
{
var accounts = (from a in _context.Account
where a.column3.StartsWith(searchText) && a.FiscalPeriod == fiscalPeriod && a.FiscalYear == fiscalyear
group a.column3 by a.column3 into g
select g.Key).ToList();
return accounts;
}
else if (searchForRole == "column4")
{
var accounts = (from a in _context.Account
where a.column4.StartsWith(searchText) && a.FiscalPeriod.Equals(fiscalPeriod) && a.FiscalYear.Equals(fiscalyear)
group a.column4 by a.column4 into g
select g.Key).ToList();
return accounts;
}
else
{
return new List<string>();
}
}
To convert it to generic. I created a expression tree.
static IQueryable<T> ConvertToExpression<T>(IQueryable<T> query, string propertyValue, PropertyInfo propertyInfo, int fiscalyear, int fiscalPeriod)
{
ParameterExpression e = Expression.Parameter(typeof(T), "e");
MemberExpression m = Expression.MakeMemberAccess(e, propertyInfo);
ConstantExpression c = Expression.Constant(propertyValue, typeof(string));
MethodInfo mi = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) });
Expression call = Expression.Call(m, mi, c);
PropertyInfo propertyInfoFiscalPeriod = typeof(T).GetProperty("FiscalPeriod");
MemberExpression memberPropertyFiscalPeriod = Expression.Property(e, propertyInfoFiscalPeriod);
ConstantExpression right = Expression.Constant(fiscalPeriod);
Expression equalsFiscalPeriod = Expression.Equal(memberPropertyFiscalPeriod, Expression.Convert(right, typeof(Int16)));
PropertyInfo propertyInfoFiscalYear = typeof(T).GetProperty("FiscalYear");
MemberExpression memberPropertyFiscalYear = Expression.Property(e, propertyInfoFiscalYear);
right = Expression.Constant(fiscalyear);
Expression equalsFiscalYear = Expression.Equal(memberPropertyFiscalYear, Expression.Convert(right, typeof(Int16)));
Expression combineExpression = Expression.And(equalsFiscalPeriod, equalsFiscalYear);
Expression predicateBody = Expression.And(call, combineExpression);
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(predicateBody, e);
return query.Where(lambda);
}
And To call it I used code like below
"searchForRole" comes as parameter in as "column1","column2" etc
PropertyInfo propertyInfo = typeof(Account).GetProperty(searchForRole);
IQueryable<Account> query = _context.Account;
query = ConvertToExpression(query, searchText, propertyInfo,fiscalyear,fiscalPeriod);
var list = query.ToList();
Now this is working fine but the result having duplicate records. I wanted to have some distinct or group by on passed parameter column. In Simple words I wanted to remove if condition and make my search method generic. Please help.
It's possible, but IMHO it's better to keep the dynamic parts at minimum and use the C# compile time safety as much as possible.
The sample query in question
var accounts = (from a in _context.Account
where a.column1.StartsWith(searchText) && a.FiscalPeriod == fiscalPeriod && a.FiscalYear ==fiscalyear
group a.column1 by a.column1 into g
select g.Key).ToList();
can be rewritten as follows
var accounts = _context.Account
.Where(a => a.FiscalPeriod == fiscalPeriod && a.FiscalYear == fiscalyear)
.Select(a => a.column1)
.Where(c => c.StartsWith(searchText))
.Distinct()
.ToList();
As you can see, the only dynamic part is a => a.column1 of type Expression<Func<Account, string>>. So all you need is a method like this:
static Expression<Func<T, M>> MemberSelector<T>(string name)
{
var parameter = Expression.Parameter(typeof(T), "e");
var body = Expression.PropertyOrField(name);
return Expression.Lambda<Func<T, M>>(body, parameter);
}
and to replace
.Select(a => a.column1)
with
.Select(MemberSelector<Account, string>(searchForRole))

Entity, Contains or intersect, is this query possible?

I have a list of string retreived this way :
List<string> keyWords = db.MotCleRecherche.Select(t => t.MotClé).ToList();
I also have a query that takes many parameters to be executed :
object = db.DAapp.Where(t => t.CODE_ART.StartsWith(s) && t.DATE_CREAT >= debut && t.DATE_CREAT < fin).ToList()
now... I want to add this kind of condition :
db.DAapp.Where(t => t.DESC_ART.ToLower().Contains(keywords.ToLower()))
or
db.DAapp.Where(t => t.DESC_ART.ToLower().Intersect(keywords.ToLower()))
I guess you could see it comming... I can't figure how to really make this work... all i know is considering a list X filed and Y list filled:
X.Intersect(Y).Any()
will return true if there is something equal... but DESC_ART is just ONE long string and i want to know if some of my keywords are in there
I agree with Stephen that you should cast the keyWords to lower first before comparing. But if you really need to do this with linq you can do something like this.
var result = db.DAapp.Where(t => keywords.Any(keyword=> string.Equals(keyword,t.DESC_ART, StringComparison.InvariantCultureIgnoreCase )));
This will cause a to lower to get called on each string every iteration of your linq loop so its expensive.
First add this to your project (for example to your controller):
static Expression<Func<T, bool>> AnyOf<T>(
params Expression<Func<T, bool>>[] expressions)
{
if (expressions == null || expressions.Length == 0) return x => false;
if (expressions.Length == 1) return expressions[0];
var body = expressions[0].Body;
var param = expressions[0].Parameters.Single();
for (int i = 1; i < expressions.Length; i++)
{
var expr = expressions[i];
var swappedParam = new SwapVisitor(expr.Parameters.Single(), param)
.Visit(expr.Body);
body = Expression.OrElse(body, swappedParam);
}
return Expression.Lambda<Func<T, bool>>(body, param);
}
class SwapVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public SwapVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
I find this from stackoverflow. now you can create desired query as below :
var filters = new List<Expression<Func<Models.DAapp, bool>>>();
foreach (var st in keyWords)
filters.Add(d => d.DESC_ART.ToLower().Contains(st.ToLower()));
var lambda = AnyOf(filters.ToArray());
var q = db.DAapp.Where(t =>
t.CODE_ART.StartsWith(s)
&& t.DATE_CREAT >= debut
&& t.DATE_CREAT < fin
);
q = q.Where(lambda);
var res = q.ToList();
Please be noticed that, this solution creates only one select query with multiple where expressions. which is more efficient that other solutions like below that contains multiple select queries inside where clause :
var q = db.DAapp.Where(t =>
t.CODE_ART.StartsWith(s)
&& t.DATE_CREAT >= debut
&& t.DATE_CREAT < fin
&& keyWords.Any(k => t.DESC_ART.ToLower().Contains(k.ToLower()))
);

Using Func<> in Entity Framework Query

I have the following Entity Framework query:
Func<Company, bool> filter;
if (officeId != 0)
filter = company => !company.IsDeleted && company.OfficeCompanies.Any(c => c.OfficeId == officeId);
else
filter = company => !company.IsDeleted;
var companies = from c in Repository.Query<Company>()
where filter(c) &&
(relationshipTypes.Count() == 0 || relationshipTypes.Any(r => r == c.TypeEnumIndex)) &&
c.Description.Contains(term)
orderby c.Description
select new JqueryUiAutoCompleteItem
{
label = c.Description,
value = SqlFunctions.StringConvert((double)c.Id)
};
It gives me the error:
The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.
If I remove the reference to filter() in the main body of the query, there is no error.
I understand the meaning of this error: I am using code that cannot be converted into SQL. But what is there about my filter() that cannot be converted to SQL?
You need to switch the Func to an Expression, then pass that expression to a Where directly in LINQ fluent syntax. I don't think there's a way to use the expression in query syntax.
Expression<Func<Company, bool>> filter; //<-- changed type
if (officeId != 0)
filter = company => !company.IsDeleted && company.OfficeCompanies.Any(c => c.OfficeId == officeId);
else
filter = company => !company.IsDeleted;
var companies = from c in Repository.Query<Company>().Where(filter) // <-- changed syntax
where (relationshipTypes.Count() == 0 || relationshipTypes.Any(r => r == c.TypeEnumIndex)) &&
c.Description.Contains(term)
orderby c.Description
select new JqueryUiAutoCompleteItem
{
label = c.Description,
value = SqlFunctions.StringConvert((double)c.Id)
};

how to create dynamic linq query based on search criterias

I have a search form which i want to use to search a database for data. The searchbox has 4 checkboxes and 1 textfield. The problem is how do i build the linq query considering i dont know beforehand what textboxes the user will check for filtering the search. What i have so far is:
[HttpPost]
public ActionResult search(string ulv, string bjorn, string jerv, string gaupe)
{
var query = (from o in db.observasjonene select o);
if (ulv != null)
{
query = query.Where(o => o.art == ulv);
}
if (bjorn != null)
{
query = query.Where(o => o.art == bjorn);
}
if (jerv != null)
{
query = query.Where(o => o.art == jerv);
}
if (gaupe != null)
{
query = query.Where(o => o.art == gaupe);
}
IEnumerable ls = query.ToList();
return Json(ls, JsonRequestBehavior.AllowGet);
}
The problem with the "where" clause is that if a condition is true, it overwrites the results from the earlier condition. I guess i need an "or" statement or something..
If I have understood your question correctly, you want to check if art equals to any of provided values. You can combine those values into collection and check if collection contains art value:
var values = new [] { ulv, bjorn, jerv, game }.Where(v => v != null);
var query = from o in db.observasjonene
where values.Contains(o.art)
select o;
EF translates Contains into SQL IN operator.
I'm using two approaches in this case:
Build dynamic query:
var q = DB.Invoices.AsQueryable();
if (isPresented != null)
q = q.Where(iv => iv.IsPresented == isPresented);
if (ID != null)
q = q.Where(iv => iv.ID == ID.Value);
...........................
return from iv in q
orderby iv.DueDate descending
select iv;
Use Union to combine search results:
var q1 = db.FeeInvoice.Where(fi => [QUERY1]));
if (isPresented != null)
{
var q2 = db.FeeInvoice.Where(fi =>[QUERY2]));
q1.Union(q2);
}
if (ID != null)
{
var q3 = db.FeeInvoice.Where(fi =>[QUERY3]);
q1.Union(q3);
}
...........................
You are comparing all the parameters value to single column in the query ie. art (see you have written same column name in each where condition) . I'm not sure why are you doing so? you can simply take single parameter which compare the value like this
public ActionResult search(string value)
{
query = query.Where(o => o.art == value);
}
or if it is by mistake and you want to apply where condition along with multiple column then you can try something like this
query=query.Where(o => (o.art == ulv || ulv == string.Empty) && (o => o.bjorn == bjorn || bjorn=string.empty) && (o.jerv == jerv || jerv == string.Empty) && (o.gaupe == gaupe || gaupe == string.Empty));
Note: I assume your column name as your parameters name.

Categories

Resources