Dynamic where clause for joined System.Data.DataTable in Linq C# - linq

I'd like to create a dynamic where clause for variable amount of columns. Can this be done?
I think I've looked at all the posts related to this topic but still cannot find the answer.
var results = from table1 in dt1.AsEnumerable()
join table2 in dt2.AsEnumerable() on table1.Field<int>("ID") equals table2.Field<int>("ID")
where table1["ColumnA"].ToString() != table2["ColumnA"].ToString() ||
table1["ColumnB"].ToString() != table2["ColumnB"].ToString() ||
table1["ColumnC"].ToString() != table2["ColumnC"].ToString()
select table1;

You can logically add where clauses. Here's an extension method that does it
(it's not mine, I saw it somewhere here on StackOverflow, a long time ago. I'll reference it when I find it)
Func<T, bool> And(this Func<T, bool> a, Func<T, bool> b)
{
return (T x) => a(x) && b(x);
}
With it, you can iterate over the columns and add them to the where clause:
Func<string, bool> whereClause = x => true;
foreach(string columnName in dt.Columns.Select(x => x.ColumnName)
{
string first = dt.Columns[columnName].ToString();
string second = dt2.Columns[columnName].ToString();
whereClause = whereClause.And(x => first.Equals(second));
}
var result = (from foo in bar select fooBar).Where(whereClause);

You could use either System.Linq.Dynamic(ScottGu's blog article and nuget) in case of dynamic where clause:
var results = (from fruit in fruits
join car in cars on fruit.Id equals car.Id
select new { fruit, car })
.AsQueryable()
.Where("fruit.ColA != car.ColA")
.Where("fruit.ColB == car.ColB");
Or dynamicaly build expressions this using extensions from PredicateBuilder writen by #joe-albahari. For example:
var predicate =
PredicateBuilder
.True<Tuple<Product, Product>>()
.And(t => t.Item1.ColA != t.Item2.ColA)
.And(t => t.Item1.ColB == t.Item2.ColB)
.Compile();
(from fruit in fruits
join car in cars on fruit.Id equals car.Id
select Tuple.Create(fruit, car))
.Where(predicate)
.Dump();
ps: full code available at gisthub

Related

How to create programmatically single LINQ query w/ OR between Where() clauses (.Where(fn) OR .Where(fn) OR .Where(fn)) programatically

I'd like to know it is it possible to create programmatically single LINQ query (for EntityFramework 6) with N .Where() clauses, but with OR between these .Where() clauses.
Imagine IQueryable object defined like:
var query = dbContext.MyTable.Where(mt => mt.TimeStamp >= DateBegin);
What I need else is add N (unknown number) of Where clauses, but with OR condition between them.
Image list of some object:
List<MyObject> myObj =
new List<MyObject>({new MyObject {val = "a" }, new MyObject { val = "b"}}); //In real code there is more than 1 property.
then I'd like to add Where() clauses to query like:
myObj.ForEach(mo =>{
// THIS CREATES -AND- BETWEEN WHERE CLAUSES, BUT I NEED -OR-
query.Where(q=>q.MyValue == mo.val); // In real code there is more than 1 property to compare
});
I was thinking about .Union() beteween queries, but It could generate union between separated queries and it's not optimal I think.
Thanks!
Here's the solution: linq-to-entities-combining-predicates
Or course is necessary to use "latest" answer:
Copy/Paste class ParameterRebinder
Copy/Paste static class Utility
Usage:
Expression<Func<Car, bool>> theCarIsRed = c => c.Color == "Red";
Expression<Func<Car, bool>> theCarIsCheap = c => c.Price < 10.0;
Expression<Func<Car, bool>> theCarIsRedOrCheap = theCarIsRed.Or(theCarIsCheap);
var query = carQuery.Where(theCarIsRedOrCheap);
Because in my solution is N of expressions, I take first expression and then append other expressions in ForEach cycle.
var firstExpression = expressionList.First();
expressionList.Skip(1).ToList().ForEach(ex => { firstExpression = firstExpression.Or(ex); });

linq query with dynamic predicates in where clause joined by OR

you can easily create dynamic queries in c# if you add more restrictions to the current query.
var list = new List<Item>();
var q = list.AsQueryable();
q = q.Where(x => x.Size == 3);
q = q.Where(x => x.Color == "blue");
In this case, every new predicate is added performing an AND operation with the previous. The previous result is equivalent to:
q = list.Where(x => x.Size == 3 && x.Color == "blue");
Is it possible to achieve the same result but with OR instead of AND?
q = list.Where(x => x.Size == 3 || x.Color == "blue");
The idea is to have a variable number of expressions that are joined with OR operator.
Expected result would need to be written in some how similar to the following pseudo code:
var conditions = new List<Func<Item, bool>>();
And later iterate conditions to build something like:
foreach(var condition in conditions)
finalExpression += finalExpression || condition;
Another possible solution to this, especially when someone doesn't want to use an external library is using expression trees.
Add a following expression extension:
public static Expression<Func<T, bool>> Or<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var invokedExpr = Expression.Invoke(expr2, expr1.Parameters);
return Expression.Lambda<Func<T, bool>>(
Expression.OrElse(expr1.Body, invokedExpr), expr1.Parameters);
}
As an example imagine you have a Container entity, which has InnerContainer nested entity with two properties Name and Id. Then you can use it in the following way:
Expression<Func<Container, bool>> whereQuery = c => c.InnerContainer.Name == "Name1";
whereQuery = whereQuery.Or(c => c.InnerContainer.Name == "Name2");
whereQuery = whereQuery.Or(c => c.InnerContainer.Id == 0);
var result = query
.Where(whereQuery)
.ToList();
Materializing such query will result in the following SQL:
SELECT [x].[Id], [x].[InnerContainerId]
FROM [Containers] AS [x]
LEFT JOIN [InnerContainer] AS [x.InnerContainer] ON [x].[InnerContainerId] = [x.InnerContainer].[Id]
WHERE [x.InnerContainer].[Name] IN (N'Name1', N'Name2') OR ([x].[InnerContainerId] = 0)
This way you can hold lambdas in a collection and loop through them.
Thanks to Raphaƫl Althaus that gave the following link:
http://www.albahari.com/nutshell/predicatebuilder.aspx
Predicate builder is the solution. You can use it installing LinqKit from Nuget. In that url you can find also the implementation of this class.
Note: in order to make this work with LinqToSql or LinqToEntities the IQueriable Object must be transformed using "AsExpandable()" method, for memory objects it's not required

Linq query select count into same entity

I got two tables: comments and commentLikes
in the same query i count the likes users have given on a comment.
I got the following (simplified) query:
var res = (from c in db.Comments
where c.Topic.ID == topicID
select new
{
comment = c,
count = c.CommentLikes.Count()
}).ToList();
But, rather than mapping the likecount into the comment entity again, I'd like to get a list of Comments only with a field LikeCount in it, preferably with an efficient query. Something like this:
var res = (from c in db.Comments
where c.Topic.ID == topicID
select new
{
comment = c,
c.LikeCount = c.CommentLikes.Count()
}).ToList();
This query doesn't compile.
How to do this in linq?
You can't do that. EF does not support to project (= select) data into an entity. You must fill the LikeCount property in memory after the query has been executed. You can write it in a compact way, but it's basically just a foreach loop over the materialized anonymous objects:
IEnumerable<Comment> res =
(from c in db.Comments
where c.Topic.ID == topicID
select new
{
comment = c,
count = c.CommentLikes.Count()
})
.ToList() // DB query runs here, the rest in memory
.Select(a => {
a.comment.LikeCount = a.count;
return a.comment;
});

LINQ BuildContainsExpression With OR conditions

I'm trying to get the following SQL query to work in LINQ:
Select id from table1 where id in (1,2) or canceledId in (1,2)
I'm using BuildContainsExpression to achieve the "IN" condition, but I can't figure out how to implement the "or" condition.
My shot in the dark is as follows:
var identifiers = new List<int> {1,2};
var query = (from t in Context.Table1
select t);
var query =
query.Where(BuildContainsExpression<Table1, int>(t => t.Id, identifiers));
if (showCanceled)
{
var expression = query.Where(BuildContainsExpression<Table1, int>(t => t.CanceledId.Value, identifiers)).Expression;
Expression.Or(expression, transactionsQuery.Expression);
}
But I get the following exception:
The binary operator Or is not defined for the types 'System.Linq.IQueryable1[Table1]' and 'System.Linq.IQueryable1[Table1]'..
Any ideas? -Am I in the right direction?
Thanks,
Nir.
You are appending your OR in the wrong place. What you are doing now is effectively something like this:
(from t in Context.Table1
where identifiers.Contains(t.Id)
select t)
OR
(where identifiers.Contains(t.CanceledId))
The second problem is that the BuildContainsExpression method you use, returns a lambda expression, something that looks like this:
t => t.Id == 1 || t.Id == 2 || ...
You can't change this expression once it's generated. However, that's what you want because you'd like to have something like this:
t => t.Id == 1 || t.Id == 2 || ... || t.CanceledId == 1 || t.CanceledId == 2 || ...
You can't simply take the body of this lambda expression and or it together with another expression because it depends on the parameter t.
So what you can do is the following:
// Overload of BuildContainsExpression.
private static Expression<Func<T, bool>> BuildOtherContainsExpression<T>(
ParameterExpression p, Expression field1, Expression field2, int[] values)
{
var eq1 = values.Select(v => Expression.Equal(field1, Expression.Constant(v)));
var eq2 = values.Select(v => Expression.Equal(field2, Expression.Constant(v)));
var body = eq1.Aggregate((acc, equal) => Expression.Or(acc, equal));
body = eq2.Aggregate(body, (acc, equal) => Expression.Or(acc, equal));
return Expression.Lambda<Func<T, bool>>(body, p);
}
// Create a parameter expression that represents something of type Table1.
var parameter = Expression.Parameter(typeof(Table1), "t");
// Create two field expressions that refer to a field of the parameter.
var idField = Expression.Property(parameter, "Id");
var canceledIdField = Expression.Property(parameter, "CanceledId");
// And finally the call to this method.
query.Where(BuildContainsExpression<Table1>(
parameter, idField, canceledIdField, identifiers));
Your if statement would now look like this:
if (!showCanceled)
{
// Use original version of BuildContainsExpression.
}
else
{
// Create some expressions and use overloaded version of BuildContainsExpression.
}
I know I'm a bit late to the party here - but I think the original code in the original poster's question was 99% right.
The only wrong was that instead of
Expression.Or
it should have been
Expression.OrElse

Conditional Multiple Fields Searching and Filtering in LINQ

Assuming that we have the following table:
Person:
PersonID,
Name,
Age,
Gender
And we are providing a search function that allows users to search the table according to the name and/or the age.
The tricky part in writing the SQL ( or LINQ) query is that the users can choose to search for both field, or any one field, or no field. If he wants to search for all then he would just have to leave the textbox blank.
The logic to do this can be written as follows:
var p;
if(Name_TextBox=='')
{
p=from row in person
select row ;
}
else
{
p= from row in person
where row.Name=Name_TextBox
select row ;
}
// repeat the same for age
Now after a while the code gets very long and messy... How can I compress the above into a single query with no if-else?
Try code like this
string personName = txtPersonName.Text;
int personAge = Convert.ToInt32(txtAge.Text);
var opportunites = from p in this.DataContext.Persons
select new
{
p.PersonID,
p.Name,
p.Age,
p.Gender
};
if (personsID != 0)
opportunites = opportunites.Where(p => p.PersonID == personID);
if (personName != string.Empty)
opportunites = opportunites.Where(p => p.Name.StartsWith(personName));
if (personAge != 0)
opportunites = opportunites.Where(p => p.Age == personAge);
This will work fine. If personName is not given it will be not add to where, and if given then it will added.
One alternative which I have used in SQL which could be implemented in Linq too is
var p = from p in Person
where p.Name == Name_TextBox || Name_TextBox == String.Empty
select p;
(Note that your 'linq' is using SQL syntax, which won't compile. Also you can't declare a var as you are doing without directly assigning a value)
why not use the null coalescing operator? eg.
var products = from a in context.products
where a.ID == (productID ?? a.ID)
select a;
This works really well on my system

Resources