Linq query to db with dynamic where parameters - c#

I have a table with data about cities:
In a search input field the user types an arbitrary number of chars then presses "search" button.
There is also another field where the user can enter the state via a dropdownlist.
Pressing the search button triggers an ajax call to a server controller which receives both the starting chars of the name and the two chars of the state.
The controller should return the list of cities with name starting with the supplied chars.
If the state is passed, the list should contain only those cities whose name starts with the supplied chars AND that are situated in the state.
If the state is not passed all matching cities are returned regardless of the state.
I cannot find a way to write a single Linq statement, because I do not know how to write the part of the query for the state:
Currently I do this:
public ActionResult selectCity(string searchString, string stateCode)
{
List<CityViewModel> mylist = new List<CityViewModel>();
IQueryable<City> mycities = null;
if (stateCode == "")
{
mylist = (from c in db.Cities
where c.name.StartsWith(searchString)
select c);
}
else
{
mylist = (from c in db.Cities
where ((c.name.StartsWith(searchString)) &&
(c.stateCode == stateCode))
select c);
}
<snip>
.....
.....
</snip>
return PartialView("_selComune",elenco);
}
I can't believe that there isn't a way to do this with a single query.

Yes, there is.
You want to say: if stateCode is empty or it matches City.stateCode then include it in result moreover City.Name must always begin with searchString. In code:
var mylist =
from c in db.Cities
where
c.name.StartsWith(searchString)
&& (stateCode == "" || c.stateCode == stateCode)
select c;
Roughly equivalent to this SQL (assuming parameters with proper syntax are provided):
SELECT * FROM Cities
WHERE name LIKE #searchString AND (#stateCode = '' OR stateCode = #stateCode)
SQL Server will optimize second comparison away if first condition is always satisfied (just check Query Execution Plan).
I think it's a typo writing code example but myList isn't List<City> unless you also add ToList() to your LINQ query.

Adriano Repetti's answer allows you to write a single query to handle "nullable" parameters but sometimes it has performance drawbacks, translated in SQL the kind of query may prevent indexes to work.
Please note that the following will also work and build the exact needed query on sql side, with only a bit of more code on LINQ side :
mylist = (from c in db.Cities
where c.name.StartsWith(searchString)
select c);
if (stateCode != "")
{
mylist = mylist.Where(c.stateCode == stateCode);
}
This kind of construct won't replace previous "where" content, it will add any new terms combining them with a "and" operator.
IQeryable is made to allow you to built it in many different lines, not a single one like you did in your code sample.

Related

How to use Linq as an IF - Condition

I got a little linq query searching for a customer in a database:
var query =
from c in database.customer
where c.ID == input
select c;
Every customer has an ID, which in this case is set by an user-"input"
All customers in database are from "Germany" but some shall be copied into the database with a different value for country ("England" instead of "Germany")
Now, before adding them to the database directly, I want to check if there is already an "english version" of this customer in the database.
if (query.Where(c => c.country == "England"))
//do nothing
else
//add the customer with c.country = "England"
The problem is that this is not a regular if-statement.
Is there a possible way to achieve what I want to express in this if-condition?
Thanks
just change condition to
if (query.Any(c => c.country.Equals("England")))
//do nothing
else
//add the customer with c.country = "England"
Linq method Where returns IEnumerable value wchich can't be automatically converted into bool value.
Methods like Any or All returns true or false based on whether condition is true for at least one element in collection or all of them respectively.

Linq to get data from a table but not if in another table?

Because of a poor design on our database I have to write a complex query to get my data.
I need to get all valid data from a table_1. In other works I need to get each valid row of my table_1. I don't have a simple valid or invalid column in my table_1. This information is stored in a table_2. Table_2 contains all invalid row with the error message.
Let say the data I need to retrieve are orders. For my example notice that OrderNo is the number of an order. So I can have multiple line, version, of this order in table_1 and I can also have multiple line of error on this order in table_2. So I will also have to use a version number.
I already tried this:
table_1.Where(y => (y.OrderNo == "1234"));
table_2.Where(y => (y.OrderNo == "1234")).Select(y => y.Version).Distinct();
And I think I need to do something like this:
var errorList = table_2.Where(y => (y.OrderNo == "1234")).Select(y => y.Version).Distinct();
table_1.Where(y => (y.OrderNo == "1234" && y.Version.NOT_IN(erriList)));
Could you help me?
I suppose you are searching for Contains function with ! symbol (logical negation operator). Like this:
var errorList = table_2.Where(y => y.OrderNo == "1234")
.Select(y => y.Version);
var res = table_1.Where(y => y.OrderNo == "1234"
//here you get only rows that doesn't represent in errorList
&& !errorList.Contains(y.Version));
to get data from a table but not if in another table
This is called antijoin. While you can use Contains and Any based approaches presented in the other answers, usually you'll get the best performance by using the classic SQL approach - LEFT OUTER JOIN combined with checking the right side for NULL.
Which in LINQ looks like this:
var query =
from t1 in table_1
//where t1.OrderNo == "1234"
join t2 in table_2 on t1.OrderNo equals t2.OrderNo into t2group
from t2 in t2group.DefaultIfEmpty()
where t2 == null
select t1;
Actually when you use OrderNo filter, most probably there will not be a noticeable speed difference between this and other queries. The main benefit of the above would be if you remove that filter, although many nowadays SQL query optimizers are able to derive one and the same execution plan regardless of whether the query uses JOIN / IN / EXISTS constructs.
How about this:
var validRows = table1
.Where(t1 => !table2
.Any(t2 => t1.OrderNo == t2.OrderNo &&
t1.Version == t2.Version));
Note that this is far more efficient in SQL unless you're using something fancy that translates the expression to SQL.

C# LINQ .Contains returns empty?

For a school project I need to filter students who have signed up for multiple courses at the same timeblock. Instead of querying the DB via procedures/views I want to use LINQ to filter it in memory for learning purposes.
Everything seems alright according to the debugger however the result of my linq query is 0 and I can't figure out how.
Here's the code:
foreach (Timeblock tb in ctx.Timeblocks)
{
List<Student> doublestudents = new List<Student>();
//Get the schedules matching the timeblock.
Schedule[] schedules = (from sched in ctx.Schedules
where sched.Timeblock.Id == tb.Id
select sched).ToArray();
/\/\/\Gives me 2 schedules matching that timeblock.
if (schedules.Count() > 1)
{
doublestudents = (from s in ctx.Students
where s.Courses.Contains(schedules[0].Course) && s.Courses.Contains(schedules[1].Course)
select s).ToList();
Console.WriteLine(doublestudents.Count); <<< count results in 0 students.
}
}
While debugging it seems everything should work alright.
Each student has a List and each Course hsa a List
schedules[0].Course has Id 1
schedules[0].Course has Id 6
The student with Id 14 has both these courses in it's list.
Still the linq query does not return this student. Can this be because it's not the same reference of course it wont find a match at the .Contains()?
It's driving me totally crazy since every way I try this it wont return any results while there are matches...
You are comparing on Course which is a reference type. This means the objects are pointing to locations in memory rather than the actual values of the Course object itself, so you will never get a match because the courses of the student and the courses from the timeblock query are all held in different areas of memory.
You should instead use a value type for the comparison, like the course ID. Value types are the actual data itself so using something like int (for integer) will let the actual numerical values be compared. Two different int variables set to the same number will result in an equality.
You can also revise the comparison to accept any number of courses instead of just two so that it's much more flexible to use.
if (schedules.Count() > 1)
{
var scheduleCourseIds = schedules.Select(sch => sch.Course.Id).ToList();
doublestudents = (from s in ctx.Students
let studentCourseIds = s.Courses.Select(c => c.Id)
where !scheduleCourseIds.Except(studentCourseIds).Any()
select s).ToList();
Console.WriteLine(doublestudents.Count);
}
Some notes:
Compare the Course IDs (assuming these are unique and what you use to match them in the database) so that you're comparing value types and will get a match.
Use the let keyword in Linq to create temporary variables you can use in the query and make everything more readable.
Use the logic for one set containing all the elements of another set (found here) so you can have any number of duplicated courses to match against.
The problem is that your schedule[0].Course object and the s.Courses, from the new query, are completely different.
you may use the element's key to evaluate your equality condition/expression, as:
if (schedules.Count() > 1)
{
doublestudents = (from s in ctx.Students
where s.Courses.Any(x=> x.Key == schedules[0].Course.Key) && s.Courses.Any(x=> x.Key == schedules[1].Course.Key)
select s).ToList();
Console.WriteLine(doublestudents.Count); <<< count results in 0 students.
}
}
In order to achieve this you will need to include
using System.Linq
As you have guessed, this is probably related to reference equality. Here is a quick fix:
doublestudents =
(from s in ctx.Students
where s.Courses.Any(c => c.Id == schedules[0].Course.Id) &&
s.Courses.Any(c => c.Id == schedules[1].Course.Id)
select s).ToList();
Please note that I am assuming that the Course class has a property called Id which is the primary key. Replace it as needed.
Please note that this code assumes that there are two schedules. You need to work on the code to make it work for any number of schedules.
Another approach is to override the Equals and GetHashCode methods on the Course class so that objects of this type are compared based on their values (the values of their properties, possibly the ID property alone?).

What is the way to join these two list?

I have a IList<User> that contains objects with a pair of value: Name and Surname.
On the database I have a table that contains rows with Name and Surname field. I want on codebehind to return the list of the rows that match my List, so let say have Name and Surname (respectively) equals.
My actual code is:
utenti = (from User utente in db.User.AsEnumerable()
join amico in amiciParsed
on new { utente.Nome, utente.Cognome } equals
new { Nome = amico.first_name, Cognome = amico.last_name }
select utente).OrderBy(p => p.Nome)
.OrderBy(p => p.Cognome)
.OrderBy(p => p.Nickname)
.ToList();
but this it is not good for two reasons:
It will download the whole records of the DB on the client;
I can't match Name and Surname as case sensitive (example Marco cordi != Marco Cordi); and on DB I have every kind of up/down chars.
As suggested on a previously question, seems that this answer can't help me, since I have to do a join (and also because the first problem it is not related).
What's the way to resolve this problem?
I don't know if this will work in your situation, but you might give it a try.
First, create a new list of strings:
List<string> amici = aimiciParsed.Select(x => x.first_name + "|" + x.last_name).ToList();
Then, select the users from DB, based on this list
var utenti = db.User.AsEnumerable().Where(utente =>
amici.Contains(utente.Nome + "|" + utente.Cognome)).ToList();
It sends the list of strings to the DB as a list of parameters and translates it into a query like
SELECT * FROM User WHERE User.Nome + "|" + User.Cognome IN (#p1, #p2, #p3 ...)
Unfortunately, there is no way to call Contains with something like StringComparison.OrdinalIgnoreCase, so you might have to change the collation of your columns.
This could be done with PredicateBuilder:
using LinqKit;
var predicate = PredicateBuilder.False<User>();
foreach(var amico in amiciParsed)
{
var a1 = amico; // Prevent modified closure (pre .Net 4.5)
predicate = predicate.Or(user => user.Nome == a1.first_name
&& user.Cognome == a1.last_name);
}
var query = db.User.Where(predicate.Expand())
.OrderBy(p => p.Nome)
...
The advantage is that indexes on Nome and Cognome can be used (which is impossible if you search on a concatenated value). On the other hand, the number of OR clauses can get very large, which may hit certain limits in SQL Server (https://stackoverflow.com/a/1869810/861716). You'll have to stress-test this (although the same goes for IN clauses).
When asking a question here on SO, you may want to translate it to English - don't expect people to know what "uente", "amico" or "Cognome" are.
One question: Why do you use ..in db.User.AsEnumerable() and not just ..in db.User?
Let everything in your query stay IQueryable (instead of IEnumerable). This lets Linq2Sql create SQLs that are as optimized as possible, instead of downloading all the records and joining the records client-side. This may also be the reason your search turns case-sensitive. Client-side in-memory string comparison will always be case-sensitive, while string comparison in SQL depends on the database's configuration.
Try ditching the .AsEnumerable() and see if you get better results:
utenti = (from User foo in db.User
join bar in amiciParsed
...

C# Linq Select Rows Where ID Equals ID in CSV

What I have is a string of comma separated IDs that I'm receiving from a query string (e.g. 23,51,6,87,29). Alternately, that string could just say "all".
In my Linq query I need a way to say (in pseudo code):
from l in List<>
where l.Id = all_of_the_ids_in_csv
&& other conditions
select new {...}
I'm just not sure how to go about doing that. I'm not even sure what to google to get me going in the right direction. Any pointing in the right direction would be extremely helpful.
I would suggest to split your query in 2 - first part will select by ID, and the select one will select other conditions.
First of all: check if query string contains numbers, or is just all:
var IEnumerable<ListItemType> query = sourceList;
if(queryStringValue != "All")
{
var ids = queryStringValue.Split(new[] { ',' })
.Select(x => int.Parse(x)) // remove that line id item.Id is a string
.ToArray();
query = query.Where(item => ids.Contains(item.Id));
}
from l in query
// other conditions
select new {...}
Because LINQ queries have deffered execution you can build queries like that without performance drawback. Query won't be executed until you ask for results (by ToList call or enumeration).
If you really want it with just one LINQ query:
var idArray = all_of_the_ids_in_csv.Split(',');
from l in List<>
where (all_of_the_ids_in_csv == "All" || idArray.Contains(l.Id))
&& other conditions
select new {...}
The trick is using string.Split
var ids = string.split(rawIdString, ",").ToList();
var objects = ids.Where(id=> /*filter id here */).Select(id=>new { /* id will be the single id from the csv */ });
// at this point objects will be an IEnumerable<T> where T is whatever type you created in the new statement above

Categories

Resources