How to build the correct LINQ query (generate OR instead of AND) - sql

I need some help in building the correct query. I have an Employees table. I need to get a list of all employees, that EENO (Employee ID) contains a string from a supplied array of partial Employee IDs.
When I use this code
// IEnumerable<string> employeeIds is a collection of partial Employee IDs
IQueryable<Employee> query = Employees;
foreach (string id in employeeIds)
{
query = query.Where(e => e.EENO.Contains(id));
}
return query;
I will get something like:
SELECT *
FROM Employees
WHERE EENO LIKE '%1111111%'
AND EENO LIKE '%2222222%'
AND EENO LIKE '%3333333%'
AND EENO LIKE '%4444444%'
Which doesn't make sense.
I need "OR" instead of "AND" in resulting SQL.
Thank you!
UPDATE
This code I wrote using PredicateBuilder works perfectly when I need to include these employees.
var predicate = PredicateBuilder.False<Employee>();
foreach (string id in employeeIds)
{
var temp = id;
predicate = predicate.Or(e => e.EENO.Contains(temp));
}
var query = Employees.Where(predicate);
Now, I need to write an opposite code, to exclude these employees,
here it is but it is not working: the generated SQL is totally weird.
var predicate = PredicateBuilder.False<Employee>();
foreach (string id in employeeIds)
{
var temp = id;
predicate = predicate.And(e => !e.EENO.Contains(temp)); // changed to "And" and "!"
}
var query = Employees.Where(predicate);
return query;
It's supposed to generate SQL Where clause like this one:
WHERE EENO NOT LIKE '%11111%'
AND NOT LIKE '%22222%'
AND NOT LIKE '%33333%'
But it's not happening
The SQL generated is this: http://i.imgur.com/9MDP7.png
Any help is appreciated. Thanks.

Instead of the foreach, just build the IQueryable once:
query = query.Where(e => employeeIds.Contains(e.EENO));

I'd take a look at http://www.albahari.com/nutshell/predicatebuilder.aspx. This has a great way of building Or queries, and is written by the guy that wrote LinqPad. The above link also has examples of usage.

I believe you can use Any():
var query = Employees.Where(emp => employeeIds.Any(id => id.Contains(emp.EENO)));

If you don't want to use a predicate builder, then the only other option is to UNION each of the collections together on an intermediate query:
// IEnumerable<string> employeeIds is a collection of partial Employee IDs
IQueryable<Employee> query = Enumerable.Empty<Employee>().AsQueryable();
foreach (string id in employeeIds)
{
string tempID = id;
query = query.Union(Employees.Where(e => e.EENO.Contains(tempID));
}
return query;
Also keep in mind that closure rules are going to break your predicate and only end up filtering on your last criteria. That's why I have the tempID variable inside the foreach loop.
EDIT: So here's the compendium of all the issues you've run across:
Generate ORs instead of ANDS
Done, using PredicateBuilder.
Only last predicate is being applied
Addressed by assigning a temp variable in your inner loop (due to closure rules)
Exclusion predicates not working
You need to start with the correct base case. When you use ORs, you need to make sure you start with the false case first, that way you only include records where AT LEAST ONE predicate matches (otherwise doesn't return anything). The reason for this is that the base case should just get ignored for purposes of evaluation. In other words false || predicate1 || predicate2 || ... really is just predicate1 || predicate2 || ... because you're looking for at least one true in your list of predicates (and you just need a base to build on). The opposite applies to the AND case. You start with true so that it gets "ignored" for purposes of evaluation, but you still need a base case. In other words: true && predicate1 && ... is the same as predicate1 && .... Hope that addresses your last issue.

Related

How can I use a method to add a filter on a property in Entity Framework Core?

I need to conditionally add a filter to particular dates in a query. There are common preconditions and the filter will be the same. Therefore I would like the common code to be in a method which can perform these checks and then have the consumer pass in the property which the filter should be applied to (could be applied to multiple).
Here is a simplified version of my code.
var query = dbContext.Documents.AsQueryable();
query = FilterDocumentsByDate(query, x => x.CreatedDate);
query = FilterDocumentsByDate(query, x => x.SubmittedDate);
private IQueryable<Document> FilterDocumentsByDate(IQueryable<Document> query, Func<Document, DateTime> propertyToSearch)
{
query = query.Where(x => propertyToSearch(x).Year > 2000);
return query;
}
When I look at the query in SQL profiler, I can see that the query is missing the WHERE clause (so all documents are being retrieved and the filter is being done in memory). If I copy/paste the code inline for both dates (instead of calling the method twice) then the WHERE clause for the both dates are included in the query.
Is there no way to add a WHERE condition to an IQueryable by passing a property in a Func which can be properly translated to SQL by Entity Framework?
EF is unable to understand your query, so it breaks and executes WHERE clause in memory.
The solution is creating dynamic expressions.
var query = dbContext.Documents.AsQueryable();
query = FilterDocumentsByDate(query, x => x.CreatedDate.Year);
query = FilterDocumentsByDate(query, x => x.SubmittedDate.Year);
private IQueryable<Document> FilterDocumentsByDate(IQueryable<Document> query, Expression<Func<Document, int>> expression)
{
var parameter = expression.Parameters.FirstOrDefault();
Expression comparisonExpression = Expression.Equal(expression.Body, Expression.Constant(2000));
Expression<Func<Document, bool>> exp = Expression.Lambda<Func<Document, bool>>(comparisonExpression, parameter);
query = query.Where(exp);
return query;
}
I am sorry, I haven't run this myself, but this should create WHERE statement. Let me know how it goes.

Entity Framework Query using Contains with mulitple options

Using entity framework to return a list of people where the forename contains text in a string array.
Let's say:
string[] search = new string[] { "bert", "rob" };
and query
dataContext.People.Where(w => search.Any(a => w.Forename.Contains(a)));
This compiles and works BUT the process is actually calling all records from the database and then performing my where clause on the returned data. This makes sense.
Is there a way to rewrite the query so the where clause is generated in SQL?
I assume that dataContext.People is an IQueryable from the DbSet and that there is no materialization instruction involved such as ToList() or AsEnumerable().
the answer is here: http://www.albahari.com/nutshell/predicatebuilder.aspx
in your case:
var predicate = PredicateBuilder.False<People>();
foreach (string keyword in keywords)
{
string temp = keyword;
predicate = predicate.Or (p => p.Forename.Contains (temp));
}
dataContext.People.Where (predicate);
One way to do this is to use SqlQuery and perform and actual SQL query.
dataContext.Database.SqlQuery<People>("SELECT Forename, Lastname FROM myTable WHERE Forename LIKE '%bert%' or Forename LIKE '%rob%'");

LinqToSql: ExecuteQuery an ExecuteQuery-result?

I use this code to get all customers from my database...
Dim customerResult = db.ExecuteQuery(Of VIEW_customers)("SELECT * FROM TOPL_Customers").ToList
Now that I have all customers fetched from the database, I want to run a "filter" query against customerResult - how do I do that?
Was hoping for something like this...
Dim filterResult = customerResult.ExecuteQuery(Of VIEW_customers)("SELECT * WHERE active=1").ToList
Any suggestions? I don't want to query the database twice.
I need to use a string as a search query because it's dynamic.
Thanks
Try this:
var filteredResult = from a in customerResult
//Add suitable filter condition based on column values
where a.Active == 1
select a;
I'm assuming here that VIEW_customers represents your model for the result set.
You can use Expressions to build up a series of filters and then join them together to create a single, dynamic where clause to run against your result set.
Expression<Func<VIEW_customers, bool>> predicate1 = x => x.someField == 'something';
Expression<Func<VIEW_customers, bool>> predicate2 = x => x.otherField == 'something else';
Then you can join them together with .And or .Or as appropriate:
Expression<Func<VIEW_customers, bool>> combinedPredicate = predicate1.And(predicate2);
You can chain any number of these together; it's boolean logic (basically think of each additional clause as being wrapped by a set of parenthesis). When you're ready to consume the predicate, compile it and run it like a normal .Where clause.
Func<VIEW_customers, bool> compiledPredicate = combinedPredicate.Compile();
var results = customerResult.Where(compiledPredicate);

checking if all the items in list occur in another list using linq

I am stuck with a problem here. I am trying to compare items in a list to another list with much more items using linq.
For example:
list 1: 10,15,20
list 2: 10,13,14,15,20,30,45,54,67,87
I should get TRUE if all the items in list 1 occur in list 2. So the example above should return TRUE
Like you can see I can't use sequenceEquals
Any ideas?
EDIT:
list2 is actually not a list it is a column in sql thas has following values:
<id>673</id><id>698</id><id>735</id><id>1118</id><id>1120</id><id>25353</id>.
in linq I did the following queries thanks to Jon Skeets help:
var query = from e in db
where e.taxonomy_parent_id == 722
select e.taxonomy_item_id;
query is IQueryable of longs at this moment
var query2 = from e in db
where query.Contains(e.taxonomy_item_id)
where !lsTaxIDstring.Except(e.taxonomy_ids.Replace("<id>", "")
.Replace("</id>", "")
.Split(',').ToList())
.Any()
select e.taxonomy_item_id;
But now I am getting the error Local sequence cannot be used in LINQ to SQL implementation of query operators except the Contains() operator.
How about:
if (!list1.Except(list2).Any())
That's about the simplest approach I can think of. You could explicitly create sets etc if you want:
HashSet<int> set2 = new HashSet<int>(list2);
if (!list1.Any(x => set2.Contains(x)))
but I'd expect that to pretty much be the implementation of Except anyway.
This should be what you want:
!list1.Except(list2).Any()
var result = list1.All(i => list2.Any(i2 => i2 == i));

Fetch Single Item Using DataContext

I'm doing the following:
public MyItem FetchSingleItem(int id)
{
string query = "SELECT Something FROM Somewhere WHERE MyField = {0}";
IEnumerable<MyItem> collection = this.ExecuteQuery<MyItem>(query, id);
List<MyItem> list = collection.ToList<MyItem>();
return list.Last<MyItem>();
}
It's not very elegant really and I was hoping there's something a little better to get a single item out using DataContext. I'm extending from DataContext in my repository. There's a valid reason why before you ask, but that's not the point in this question ;)
So, any better ways of doing this?
Cheers
If it is SQL Server, change your SQL to:
SELECT TOP 1 Something FROM Somewhere ...
Or alternatavely, change these lines
List<MyItem> list = collection.ToList<MyItem>();
return list.Last<MyItem>();
into this one:
return collection.First();
myDataContext.MyItem.Where(item => item.MyField == id)
.Select(item => item.Something)
.FirstOrDefault();
The record returned is undefined, since you have no ORDER BY. So it's hard to do an exact translation. In general, though, reverse the order and take the First():
var q = from s in this.Somewhere
where s.MyField == id
orderby s.Something desc
select s.Something;
return q.First();
Relational tables are unordered. So if you don't specify the record you want precisely, you must consider the returned record as randomly selected.