C# Predicate Builder with "NOT IN" functionality - c#

With PredicateBuilder how do I get functionality similar to the SQL IN or NOT IN query?
For example I have a list of IDs and I want to select all of the People whose IDs either Match or do not match the IDs.
The people match functionality is fairly straightforward (although there may be a better way to do it)
var predicate = PredicateBuilder.False<Person>()
foreach (int i in personIDs)
{
int temp = i;
predicate = predicate.Or(e=>e.PersonID == temp);
}
return persons.Where(predicate);
So how do I get the opposite? I want all persons whose IDs are not in the personIDs list.

Ask De Morgan:
NOT (P OR Q) = (NOT P) AND (NOT Q)
To have your code generate the equivalent of a NOT IN condition, rewrite as
var predicate = PredicateBuilder.True<Person>()
and
predicate = predicate.And(e=>e.PersonID != temp);

Do you use Entity Framework?
Then you can build the query without PredicateBuilder:
var personIds = new List<int>() { 8,9,10 };
var query = persons.Where(it => !personIds.Contains(it.PersonId));
From this LINQ statement a SQL NOT IN query is created.

Is this what you want?
var predicate = PredicateBuilder.True<Person>()
foreach (int i in personIDs)
{
int temp = i;
predicate = predicate.And(e => e.PersonID != temp);
}
return persons.Where(predicate);

Without looking at the api....
var predicate = PredicateBuilder.True<Person>()
foreach (int i in personIDs)
{
int temp = i;
predicate = predicate.And(e=>e.PersonID != temp);
}
return persons.Where(predicate);

Related

Getting all Data from SQL using LINQ with comma-separated IDs

I do have a string of Empids separated by comma like:
EMpID:"2007,2008,2002,1992,1000,2108,1085
and I need to retrieve the records of all those specified employees using LINQ query.
I tried it with looping but I need to get that in efficient and faster way.
Here goes what i did using looping.
string[] EMpID_str = LeaveDictionary["EMpID"].ToString().Split(',');
for (int i = 0; i < EMpID_str.Length; i++)
{
EMpID = Convert.ToInt32(EMpID_str[i]);
//Linq to get data for each Empid goes here
}
But What I need is to use single LINQ or Lambda query to retrieve the same.Without looping
First convert your ,(comma) separated empId to string array like below:
var empArr = EmpId.split(',');
var employeesResult = emplyeeList.Where(x => empArr.contains(x.EmpId.ToString()));
I hope, it will help someone.
If the Ids that you want to fetch are numbers, not strings, then you should not convert the string to an array of strings, but to a sequence of numbers:
IEnumerable<int> employeeIdsToFetch = LeaveDictionary["EMpID"].ToString()
.Split(',')
.Select(splitText => Int32.Parse(splitText));
To fetch all employees with thees Ids:
var fetchedEmployees = dbContext.Employees
.Where(employee => employeeIdsToFetch.Contains(employee.Id))
.Select(employee => new
{
// Select only the employee properties that you plan to use:
Id = employee.Id,
Name = employee.Name,
...
});
You can use the Expression class to build a Func<int, bool> from your string and use it with the Where methode:
var str = "2,5,8,9,4,6,7";
var para = Expression.Parameter(typeof(int));
var body = str.Split(",")
.Select(s => int.Parse(s))
.Select(i => Expression.Constant(i))
.Select(c => Expression.Equal(para, c))
.Aggregate((a, b) => Expression.Or(a, b));
Func<int, bool> func = Expression.Lambda<Func<int, bool>>(body, para).Compile();
and if you this solution to work with linq to SQL just dont compile the expression at the end and let the linq to SQL engine compile it to an efficent SQL expression.
Instead of the Aggregate Method (which will produce an expression with linear complexity) one could use an divide and conquer approach to fold the values into one value.
For example with this class:
public static class Helper
{
public static T EfficientFold<T>(this List<T> list, Func<T, T, T> func)
{
return EfficientFold(list, 0, list.Count, func);
}
private static T EfficientFold<T>(List<T> list, int lowerbound, int upperbound, Func<T, T, T> func)
{
int diff = upperbound - lowerbound;
var mid = lowerbound + diff / 2;
if (diff < 1)
{
throw new Exception();
}
else if (diff == 1)
{
return list[lowerbound];
}
else
{
var left = EfficientFold(list, lowerbound, mid, func);
var right = EfficientFold(list, mid, upperbound, func);
return func(left, right);
}
}
}
and then we can do
var body = str.Split(",")
.Select(s => int.Parse(s))
.Select(i => Expression.Constant(i))
.Select(c => Expression.Equal(para, c))
.ToList()
.EfficientFold((a, b) => Expression.Or(a, b));
which gives the evaluation a complexity of log(n).

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

Question with PredicateBuilder multiple AND OR

I've a question about the PredicateBuilder and I really hope you can give me some advice on how to solve this. I'll try to explain this.
I have the case where people can search for products based on keywords. Each keyword belongs to a keywordgroup, so some real data would be:
KeywordGroup / Keyword
Type - Chain/
Type - Bracelet/
Color - Purple/
Color - Green
Now I want to have the following results:
Between each different KeywordGroup there should be an OR.
Between each different Keyword inside a KeywordGroup there should be an AND.
So e.g., a user want's to search for only Bracelets with the colors Purlple or Green.
Is this possible with this PredicateBuilder?
This is what I have so far:
================================
/// <summary>
/// Search for products
/// </summary>
/// <param name="itemsPerPage"></param>
/// <returns></returns>
public List<Product> SearchProducts(int from, int max, string sorting, List<Keyword> filter, out int totalitems) {
try {
var predicate = PredicateBuilder.True<Product>();
KeywordGroup previousKeywordGroup = null;
foreach (Keyword k in filter.OrderBy(g=>g.KeywordGroup.SortOrder)) {
if (previousKeywordGroup != k.KeywordGroup) {
previousKeywordGroup = k.KeywordGroup;
predicate = predicate.And(p => p.Keywords.Contains(k));
}
else
predicate = predicate.Or(p => p.Keywords.Contains(k));
}
var products = context.Products.AsExpandable().Where(predicate);
//var products = from p in context.Products
// from k in p.Keywords
// where filter.Contains(k)
// select p;
totalitems = products.Distinct().Count();
if (sorting == "asc")
return products.Where(x => x.Visible == true).Distinct().Skip(from).Take(max).OrderBy(o => o.SellingPrice).ToList();
else
return products.Where(x => x.Visible == true).Distinct().Skip(from).Take(max).OrderByDescending(o => o.SellingPrice).ToList();
}
catch (Exception ex) {
throw ex;
}
}
================================
It doesn't work, though.
Can you help me out?
Thanks!
Daniel
You need to use a temporary variable in the loop for each keyword. From the Predicate Builder page:
The temporary variable in the loop is
required to avoid the outer variable
trap, where the same variable is
captured for each iteration of the
foreach loop.
Try this instead:
foreach (Keyword k in filter.OrderBy(g=>g.KeywordGroup.SortOrder)) {
Keyword temp = k;
if (previousKeywordGroup != k.KeywordGroup) {
previousKeywordGroup = k.KeywordGroup;
predicate = predicate.And(p => p.Keywords.Contains(temp));
}
else
predicate = predicate.Or(p => p.Keywords.Contains(temp));
}
Notice the use of temp in each line where predicate And and Or are used.
This is just making a big list of And ands Or statements, you need to group them together.
Something like this..
var grouped = filter.GroupBy(item => item.KeyWordGroup, item => item.KeyWords);
foreach (var item in grouped)
{
var innerPredicate = PredicateBuilder.True<Product>();
foreach (var inner in item)
{
innerPredicate = innerPredicate.Or(p => item.Contains(k));
}
predicate = predicate.And(innerPredicate); //not sure this is correct as dont have IDE..
}

If I'm projecting with linq and not using a range variable what is the proper syntax?

I have a query that sums and aggregates alot of data something like this:
var anonType = from x in collection
let Cars = collection.Where(c=>c.Code == "Cars")
let Trucks = collection.Where(c=>c.Code == "Trucks")
select new {
Total = collection.Sum(v=>v.Amount),
CarValue = Cars.Sum(v=>v.Amout),
TruckValue = Trucks.Sum(v=>v.Amount),
CarCount = Cars.Count(),
TruckCount = Trucks.Count()
};
I find it really weird that I have to declare the range variable x, especially if I'm not using it. So, am I doing something wrong or is there a different format I should be following?
I could be wrong, but from your usage, I don't think you want to do a traditional query expression syntax query with your collection anyway, as it appears you are only looking for aggregates. The way you have it written, you would be pulling multiple copies of the aggregated data because you're doing it for each of the items in the collection. If you wished, you could split your query like this (sample properties thrown in)
var values = collection.Where(c => c.Code == "A");
var anonType = new
{
Sum = values.Sum(v => v.Amount),
MinimumStartDate = values.Min(v => v.StartDate),
Count = values.Count()
};
You declare a range variable no matter the looping construct:
foreach(var x in collection)
or
for(var index = 0; index < collection.Count; index++)
or
var index = 0;
while(index < collection.Count)
{
//...
index++;
}
Queries are no different. Just don't use the variable, it doesn't hurt anything.
So, am I doing something wrong?
Your query is not good. For each element in the collection, you are enumerating the collection 5 times (cost = 5*n^2).
Is there a different format I should be following?
You could get away with enumerating the collection 5 times (cost = 5n).
IEnumerable<X> cars = collection.Where(c => c.Code == "Cars");
IEnumerable<X> trucks = collection.Where(c => c.Code == "Trucks");
var myTotals = new
{
Total = collection.Sum(v => v.Amount),
CarValue = cars.Sum(v => v.Amount),
TruckValue = trucks.Sum(v => v.Amount,
CarCount = cars.Count(),
TruckCount = trucks.Count()
};

Categories

Resources