This is the pseudo-SQL I want to generate:
SELECT * FROM Table WHERE Column1 = #Value1 OR Column2 = #Value2
The problem is, sometimes the second one should not be included. I was hoping to chain together .Where() clauses like so:
var query = context.TableName;
query = query.Where(t => t.Column1 == value1);
if (NeedsToBeIncluded(value2))
query = query.Where(t => t.Column2 == value2);
Unfortunately, this doesn't work. .Where() will emit an AND if you chain them together by default. Is there a way to get it to emit an OR?
I'm looking for something along the lines of:
var query = context.TableName;
query = query.Where(t => t.Column1 == value1);
if (NeedsToBeIncluded(value2))
query = query.OrWhere(t => t.Column2 == value2);
UPDATE
Ok, so my example listed above is too simple. It was merely supposed to be an example that outlines the problem space. Out "in the wild" so to speak, Column1 and Column2 could actually be "CarType" and "OwnerName", maybe there's more, maybe there's less. I just used a simple example to outline the problem because I'm looking to solve a range of domain problems with this chaining-.Where()s together.
One way is to use LINQKit's PredicateBuilder.
Another way is to use a list:
var values = new List<string> { value1 };
if (NeedsToBeIncluded(value2)) values.Add(value2);
query = context.TableName.Where(t => values.Contains(t));
PB is more flexible, but the list will solve the problem in your question. Note that you need EF 4 for Contains.
I gave an example how to dynamically build a condition yesterday - see here. For your case it would be something like that.
var parameter = Expression.Parameter(typeof(TableName), "t");
Expression condition = Expression.Equal(
Expression.Property(parameter, "Column1"),
Expression.Constant(value1)));
if (NeedsToBeIncluded(value2))
{
condition = Expression.OrElse(
condition,
Expression.Equal(
Expression.Property(parameter, "Column2"),
Expression.Constant(value2)));
}
var expression = Expression.Lambda<Func<TableName, Boolean>>(condition, parameter);
var query = context.TableName.Where(expression);
Related
Say you have the following line of code:
var filter = Builders<BsonDocument>.Filter.Where(t => t.id == myId);
var result = collection.Find(filter).ToList();
Does the filter builder translate the lambda to a literal $where clause or does it optimize it to use the $eq clause? $where has slow performance so I don't want to use it if there's no translation.
This is a simple example but we have other queries in our code that are lengthier but would easily translate to non-$where queries.
Thanks to David Osborne's comments above, I was able to do a little more research and come up with an answer. It turns out simple LINQ queries do in fact translate directly to native Mongo queries and not to $where queries.
var filter = Builders<BsonDocument>.Filter.Where(x => x["status"] != "ready");
var findFluent = collection.Find(filter);
Console.WriteLine(findFluent);
filter = Builders<BsonDocument>.Filter.Ne("status", "ready");
var findFluent = collection.Find(filter);
Console.WriteLine(findFluent);
Both print out the identical queries. This holds for simple combinations of equality and logical operators.
However, some operators appear to be inaccessible through LINQ. I attempted to write an $in query
var success = new List<string> { "imported", "processed" };
var filter = Builders<BsonDocument>.Filter.Where(x => success.Contains(x["status"].ToString()));
and got a runtime error. Similarly with a regex match
var regex = new Regex("^*0000");
var filter = Builders<BsonDocument>.Filter.Where(x => regex.IsMatch(x["orderId"].ToString()));
Not surprising but good to know.
I have one code of employee which is A-B-C-D- . Now I want to search all records which are starting with A- till it rich A-B-C-D-. I have tried below code:
var result = db.Employee.Where(x=> x.EmployeeCode.StartsWith("A-B-C-D-"));
Above code gives me only one record. But I want all records which starts with A- then A-B- then A-B-C- and then equals to A-B-C-D-.
Any hint or idea is appreciated.
Have you tried this?
var result = db.Employee
.Where(x=> x.EmployeeCode.StartsWith("A-")
|| x.EmployeeCode.StartsWith("A-B-")
|| x.EmployeeCode.StartsWith("A-B-C-")
|| x.EmployeeCode.StartsWith("A-B-C-D-");
As you say in the comment that it must be dynamic, then do something like this:
string code = "A-B-C-D-";
var predicates = new List<Expression<Func<Customer,bool>>>();
for (int i = 0; i < code.Length; i++)
{
if (code[i] == '-')
{
var prefix = code.Substring(0, i + 1);
predicates.Add(x => x.EmployeeCode.StartsWith(prefix));
}
}
var oredPredicates = ...; // Keep reading!
...
var result = db.Employee.Where(oredPredicate);
Now, you have a lis of predicates, and have to combine them with || (or). To do so it's a bit messy, but there are solutions, for example like in this SO Q&A's:
Combining two expressions (Expression>)
Combine two Linq lambda expressions
How can I combine two lambda expressions without using Invoke method?
C# how to combine two expressions into a new one?
Once you have all the predicates combined, use it as parameter for your .Where() function.
Unfortunately the most complicated part of combining the expressions is unavoidable, and it's the toughest part to solve this problem, but it works. Please, read the 3 Q&A's, to get insight in what you're doing and get the code that best suits you (beware that using the simple Expression.OrAlso would not directly work, because the x param in each lambda is different for each created expression)
I'm using Entity Framework 6 and Waseem Sabjee's solution for appending expressions at runtime which works really well.
However, what I can't work out is how to separate parts of the expression with what in SQL would be parentheses.
For example:
SELECT * FROM SOMETABLE WHERE Field1 = 'somevalue' AND (FIELD2 = 1 OR FIELD2 = 2 OR FIELD2 = 3)
instead of
SELECT * FROM SOMETABLE WHERE Field1 = 'somevalue' AND FIELD2 = 1 OR FIELD2 = 2 OR FIELD2 = 3
which will output different results and would be wrong in the second
example for what I actually want.
(although in SQL I would do the "OR" parts with an "IN" clause)
Normally I'd just put parentheses around the relevant part of the filter but in my case at runtime I don't know how many OR statements I need as I pass in a List of values, the number of which is only known at runtime i.e:
public Search(IList<int> vals, string filter){
Expression<Func<Event, bool>> filter = x => x.Field1 == filter;
bool first = true;
for(int i in vals){
if (first){
filter = filter.And(x -> x.Field2 == i);
first = false;
}
else{
filter = filter.Or(x -> x.Field2 == i);
}
}
}
Does anyone know how this can be achieved with expressions?
You should create or statements before and statement:
public Search(IList<int> vals, string filter){
Expression<Func<Event, bool>> andFilter = x => x.Field1 == filter;
var firstVal = vals.First();
Expression<Func<Event, bool>> orFilter = x=>x.Field2 == firstVal;
foreach(int i in vals.Skip(1)){
orFilter = orFilter.Or(x -> x.Field2 == i);
}
andFilter = andFilter.And(orFilter);
}
OK so I had to think a bit laterally to solve this as I could not find a way to do this with an expression builder (there may still be a way!)
Instead, I turned to Roslyn that I found referenced here.
With Rosyln you can essentially compile strings into C# code and return output to be further used at runtime.
So instead of appending expressions I now create the expression as a piece of code in a string, compile it with Rosyln, and return the new expression I used to filter my data.
This is how it works:
ScriptEngine scriptEngine = new ScriptEngine();
string exp = "public System.Linq.Expressions.Expression<System.Func<my.namespace.object,bool>> CreateFilter(){System.Linq.Expressions.Expression<System.Func<my.namespace.object,bool>> filter = e => e.Field1.Contains(\"a\");return filter;}CreateFilter();";
This first part sets up the new method as a string.
Roslyn.Scripting.Session s = scriptEngine.CreateSession();
s.AddReference(typeof(System.Linq.Expressions.Expression).Assembly);
s.AddReference(typeof(my.namespace.object).Assembly);
You need to add references to any non-system assemblies your code requires just like any other project.
Expression<Func<Event, bool>> filter = (Expression<Func<Event, bool>>)s.Execute(exp);
Finally, you just execute the code which returns your new expression.
With this technique you can easily add any parentheses you like, where ever you like, and build up the expression using loops and other conditional statements.
I found Roslyn on NuGet.
Sorry for my late reply (power issues this side).
Here is my solution (code only)
// at this point query will be: SELECT * FROM SOMTABLE WHERE Field1 = 'Cat'
Expression<Func<Event, bool>> filterExpression = x => x.Field1 == filter;
if (vals.Count > 0)
{
// at this point query will be: SELECT * FROM SOMTABLE WHERE Field1 = 'Cat' AND Field2 = 1
filterExpression = filterExpression.And(x => x.Field2 == vals.First());
}
if (vals.Count > 1)
{
// at this point query will be: SELECT * FROM SOMTABLE WHERE Field1 = 'Cat' AND (Field2 = 1 OR Field2 = 3 OR Field2 = 4)
filterExpression = filterExpression.Or(x => vals.Skip(1).Contains(x.Field2));
}
I've posted a link to a vs solution here: http://www.waseem-sabjee.com/code/LinqExpressionFilterIssue.zip
I have a scenario where I have to change the order by field based on some condition.
from summaryRows in _summaryTable.AsEnumerable()
where summaryRows.Field<string>("AirlineDisplayName")
.Equals(airlineName_, StringComparison.OrdinalIgnoreCase)
orderby summaryRows.Field<decimal>("FareAdult")
select new
{
summaryTableRow = summaryRows
};
Based on the condition, I have to change the order by field to orderby summaryRows.Field<double>("BasePricePlusTaxAndFees")
Here, both the field data type is different. How can I do it in one query?
I think this will be the most readable using fluent Linq syntax and introducing an if-statement while building the query.. Since you do not explain your condition, I assume that you have a boolean variable called condition with the appropriate value:
var query = _summaryTable.AsEnumerable()
.Where(
summaryRows => summaryRows.Field<string>("AirlineDisplayName")
.Equals(airlineName_, StringComparison.OrdinalIgnoreCase));
if (condition)
query = query.OrderBy(summaryRows => summaryRows.Field<decimal>("FareAdult"));
else
query = query.OrderBy(summaryRows => summaryRows.Field<double>("BasePricePlusTaxAndFees"));
var resultQuery = query.Select(summaryRows => new
{
summaryTableRow = summaryRows
});
Disclaimer: I have not tested it, but good luck.
What about this:
orderby conditionIsTrue ? (IComparable)summaryRows.Field<double>("BasePricePlusTaxAndFees") : (IComparable)summaryRows.Field<decimal>("FareAdult")
Has anyone had problems gettting associations to load using LINQ to SQL when your child record was loaded via a lambda query? For example:
var orderLine = db.OrderLines.
Where(ol => ol.ID == orderLineID select ol).
First();
// navigate to order via the association
var order = orderLine.GetOrder();
What I get basically is a null result from GetOrder().
But if I do this instead:
var orderLine = (from ol in db.OrderLines where ol.ID == orderLineID).First();
var order = orderLine.GetOrder();
Works fine.
What can cause this? Is this a bug?
EDIT: Here's the actual code that WORKS with the Lambda expression commented out that DOESN'T WORK
var msg = db.Messages.Where(m => m.ID == msgID).First();
if (msg.SentTS.HasValue) return;
// Get the message recipients
// I don't get it.. why doesn't lambda expressions work here? returns 0 results!
// var testtos = msg.GetMessageTos.Where(mt => mt.Active);
var tos = from mt in db.MessagesTos
where mt.Active && mt.MessageID == msgID
select mt;
You can also try this, I think it's a little cleaner.
var orderLine = db.OrderLines.Single( ol => ol.ID == orderLineID );
var order = orderLine.GetOrder();
I beileive in your non-working example you want to use .First() instead of .Single().
It seems to me that the problem has more to do with the association than lambda expressions.
In your scenario, this should work:
var tos = db.MessagesTos.Where(mt=> mt.Active && mt.MessageID);
while this won't:
var tos = from mt in msg.SentTS
where mt.Active
select mt;
As to why it doesn't work, I suggest taking a look at the association in the designer and checking its matching the db model correctly (matching the correct columns). I also suggest to confirm that msg.SentTS is effectively coming empty, regardless of any further query you run on it.
See my EDIT for the code that works. I guess sometimes the "Answer" is to do what works, not necessarily what you understand.