LINQ to Entities does not recognize the method Boolean ContainsAny - c#

Error
LINQ to Entities does not recognize the method 'Boolean
ContainsAny(System.String, System.String[])' method, and this method
cannot be translated into a store expression.
Code
var predicate = FilterClients(clientRequest);
clients = db.Clients.Where(predicate).Include(x => x.Organization)
.Include(x => x.Department).ToList();
private Expression<Func<Client, bool>> FilterClients(ClientRequest clientRequest)
{
var predicate = PredicateBuilder.True<Client>();
if (clientRequest.IsBySearchPatternFullName)
{
var searchPatternFullName = clientRequest.SearchPatternFullName.Trim();
var matches = Regex.Matches(searchPatternFullName, #"\w+[^\s]*\w+|\w");
var words = new List<string>();
foreach (Match match in matches)
{
var word = match.Value;
words.Add(word);
}
var wordArray = words.ToArray();
predicate = predicate.And(x => x.LastName.ContainsAny(wordArray) || x.FirstName.ContainsAny(wordArray) || x.MiddleName.ContainsAny(wordArray));
}
return predicate;
}
There is similar question C# LINQ to Entities does not recognize the method 'Boolean'
But I would like to optimize this part somehow
predicate = predicate.And(x => x.LastName.ContainsAny(wordArray) || x.FirstName.ContainsAny(wordArray) || x.MiddleName.ContainsAny(wordArray));
Any clue people?

Oh! I just did it!
foreach (Match match in matches)
{
var word = match.Value;
predicate = predicate.And(x => x.LastName.Contains(word) || x.FirstName.Contains(word) || x.MiddleName.Contains(word));
}

You can query against list of word in following way :
predicate = predicate.And(x => wordArray.Contains(x.LastName) || wordArray.Contains(x.FirstName) || wordArray.Contains(x.MiddleName));

Related

Converting LINQ expression to expression tree queryable.Where(c => c.Tags != null && searchValues.All(s => c.Tags.Contains(s)));

I have a list of strings string[] searchValues and a LINQ expression
queryable.Where(c => c.Tags != null && searchValues.All(s => c.Tags.Contains(s)));
where .Tags is a List<string>
I want to rewrite it as an expression (filters and sorting orders are coming from UI as strings, and I wanted to create a generic method to convert it to expressions)
So far I've written this, but it's incorrect.
private static Expression GetFilterExpressionIListString2(MemberExpression memberExp, string[] values)
{
var anyMethod = typeof(Enumerable).GetMethods().First(m => m.Name == "Any" && m.GetParameters().Length == 2);
var specificAnyMethod = anyMethod.MakeGenericMethod(typeof(string));
Expression<Func<List<string>, bool>> lambda = s => values.All(v => s.Contains(v));
var anyExp = Expression.Call(specificAnyMethod, memberExp, lambda);
var notNullExp = Expression.NotEqual(memberExp, Expression.Constant(null));
var andExp = Expression.AndAlso(notNullExp, anyExp);
return andExp;
}

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()))
);

Dynamically filtering a column/property in to an EF 4.1 query using C#

I'm trying to create a generic "search engine" in C# using linq. I have a simple search engine that functions and look like the following.
var query = "joh smi";
var searchTerms = query.Split(new char[] { ' ' });
var numberOfTerms = searchTerms.Length;
var matches = from p in this.context.People
from t in searchTerms
where p.FirstName.Contains(t) ||
p.LastName.Contains(t)
group p by p into g
where g.Count() == numberOfTerms
select g.Key;
I want it to be more generic so I can call it like this:
var matches = Search<Person>(dataset, query, p => p.FirstName, p => p.LastName);
I've gotten as far as the following, but it fails with a "The LINQ expression node type 'Invoke' is not supported in LINQ to Entities." System.NotSupportedException.
static IEnumerable<T> Find<T>(IQueryable<T> items, string query,
params Func<T, string>[] properties)
{
var terms = query.Split(' ');
var numberOfParts = terms.Length;
foreach (var prop in properties)
{
var transformed = items.SelectMany(item => terms,
(item, term) => new { term, item });
// crashes due to this method call
var filtered = transformed.Where(p => prop(p.item).Contains(p.term));
items = filtered.Select(p => p.item);
}
return from i in items
group i by i into g
where g.Count() == numberOfParts
select g.Key;
}
I'm certain it's doable, there just has to be a way to compile i => i.FirstName to an Expression<Func<T, bool>>, but that's where my LINQ expertise ends. Does anyone have any ideas?
You should use a Predicate Builder to construct your Or query, something like:
var predicate = PredicateBuilder.False<T>();
foreach (var prop in properties)
{
Func<T, string> currentProp = prop;
predicate = predicate.Or (p => currentProp(p.item).Contains(p.term));
}
var result = items.Where(predicate );
Look into using a Specification Pattern. Check out this blog. Specifically, look at the spec pattern he developed. This is a similar thought to #Variant where you can build a dynamic specification and pass it to your context or repository.
It turns out the content of the queries just needed to be 'Expanded'. I used a library I found here to expand the expressions. I think that allows Linq to Entities to translate it in to sql. You'll notice Expand gets called over and over again; I think all of them are necessary. It works, anyway. Code to follow:
using System.Linq.Expressions;
public static class SearchEngine<T>
{
class IandT<T>
{
public string Term { get; set; }
public T Item { get; set; }
}
public static IEnumerable<T> Find(
IQueryable<T> items,
string query,
params Expression<Func<T, string>>[] properties)
{
var terms = query.Split(new char[] { ' ' },
StringSplitOptions.RemoveEmptyEntries);
var numberOfParts = terms.Length;
Expression<Func<IandT<T>, bool>> falseCond = a => false;
Func<Expression<Func<IandT<T>, bool>>,
Expression<Func<IandT<T>, bool>>,
Expression<Func<IandT<T>, bool>>> combineOr =
(f, g) => (b) => f.Expand(b) || g.Expand(b);
var criteria = falseCond;
foreach (var prop in properties)
{
var currentprop = prop;
Expression<Func<IandT<T>, bool>> current = c =>
currentprop.Expand(c.Item).IndexOf(c.Term) != -1;
criteria = combineOr(criteria.Expand(), current.Expand());
}
return from p in items.ToExpandable()
from t in terms
where criteria.Expand(new IandT<T> { Item = p, Term = t })
group p by p into g
where g.Count() == numberOfParts
select g.Key;
}
}
It can be called via the following code:
var matches = Search<Person>(dataset, query, p => p.FirstName, p => p.LastName);

Lambda Expressions and searching

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();

Categories

Resources