LINQ search query doesn't work - c#

I'm trying to write a search query in LINQ. Below is the where condition.
where (!string.IsNullOrEmpty(nameWithInitials)
&& tb.NameWithInitials.Contains(nameWithInitials))
&& (!string.IsNullOrEmpty(studentRegNo)
&& tbSR.StudentRegistrationNo.Contains(studentRegNo))
&& (!string.IsNullOrEmpty(NIC) && tb.NIC.Contains(NIC))
&& (!string.IsNullOrEmpty(fullName) && tbi.Name.Contains(fullName))
It doesn't return any values if I pass a single parameter. For example if I pass 'Chamara' as fullname it doesn't return any result but if I pass all the parameters at the once then it returns the matching records.
I need to get this to work even when I pass several parameters dynamically

You are using AND (&&) everywhere, so if at least one of these conditions is false, your where condition will be false. Try using OR conditions instead:
where (string.IsNullOrEmpty(nameWithInitials) || tb.NameWithInitials.Contains(nameWithInitials))
&& (string.IsNullOrEmpty(studentRegNo) || tbSR.StudentRegistrationNo.Contains(studentRegNo))
&& (string.IsNullOrEmpty(NIC) || tb.NIC.Contains(NIC))
&& (string.IsNullOrEmpty(fullName) || tbi.Name.Contains(fullName))
In this case in any of these conditions if you have empty parameter, only the first part of condition will be evaluated, otherwise the second condition will be evaluated.
One potential issue is that Entity Framework might not be able to translate this to actual SQL. In this case, you can use such approach:
var query = // your original query without where condition
// Check if the condition is valid and only then add where condition
if(!string.IsNullOrEmpty(nameWithInitials))
{
query = query.Where(tb => tb.NameWithInitials.Contains(nameWithInitials));
}
// repeat this for all other conditions

What you're asking is semi-confusing but i think you want to search for every string if exists, which translates to
where ((string.IsNullOrEmpty(nameWithInitials)
|| tb.NameWithInitials.Contains(nameWithInitials))
&& (string.IsNullOrEmpty(studentRegNo)
|| tbSR.StudentRegistrationNo.Contains(studentRegNo))
&& (string.IsNullOrEmpty(NIC) || tb.NIC.Contains(NIC))
&& (string.IsNullOrEmpty(fullName) || tbi.Name.Contains(fullName))

Related

Linq Multiple where based on different conditions

In search page I have some options based on them search query must be different . I have wrote this :
int userId = Convert.ToInt32(HttpContext.User.Identity.GetUserId());
var followings = (from f in _context.Followers
where f.FollowersFollowerId == userId && f.FollowersIsAccept == true
select f.FollowersUserId).ToList();
int value;
if (spto.Page == 0)
{
var post = _context.Posts.AsNoTracking().Where(p => (followings.Contains(p.PostsUserId) || p.PostsUser.UserIsPublic == true || p.PostsUserId == userId) && p.PostIsAccept == true).Select(p => p).AsEnumerable();
if(spto.MinCost != null)
{
post = post.Where(p => int.TryParse(p.PostCost, out value) && Convert.ToInt32(p.PostCost) >= spto.MinCost).Select(p => p);
}
if (spto.MaxCost != null)
{
post = post.Where(p => int.TryParse(p.PostCost, out value) && Convert.ToInt32(p.PostCost) <= spto.MaxCost).Select(p => p);
}
if (spto.TypeId != null)
{
post = post.Where(p => p.PostTypeId == spto.TypeId).Select(p => p);
}
if (spto.CityId != null)
{
post = post.Where(p => p.PostCityId == spto.CityId).Select(p => p);
}
if (spto.IsImmidiate != null)
{
post = post.Where(p => p.PostIsImmediate == true).Select(p => p);
}
var posts = post.Select(p => new
{
p.Id,
Image = p.PostsImages.Select(i => i.PostImagesImage.ImageAddress).FirstOrDefault(),
p.PostCity.CityName,
p.PostType.TypeName
}).AsEnumerable().Take(15).Select(p => p).ToList();
if (posts.Count != 0)
return Ok(posts);
return NotFound();
In this case I have 6 Query that take time and the performance is low and code is too long . Is there any better way for writing better code ?
Short answer: if you don't do the ToList and AsEnumerable until the end, then you will only execute one query on your dbContext.
So keep everything IQueryable<...>, until you create List<...> posts:
var posts = post.Select(p => new
{
p.Id,
Image = p.PostsImages
.Select(i => i.PostImagesImage.ImageAddress)
.FirstOrDefault(),
p.PostCity.CityName,
p.PostType.TypeName,
})
.Take(15)
.ToList();
IQueryable and IEnumerable
For the reason why skipping all the ToList / AsEnumerable would help to improve performance, you need to be aware about the difference between an IEnumerable<...> and an IQueryable<...>.
IEnumerable
A object of a class that implements IEnumerable<...> represents the potential to enumerate over a sequence that the object can produce.
The object holds everything to produce the sequence. Once you ask for the sequence, it is your local process that will execute the code to produce the sequence.
At low level, you produce the sequence by using GetEnumerator and repeatedly call MoveNext. As long as MoveNext returns true, there is a next element in the sequence. You can access this next element using property Current.
Enumerating the sequence is done like this:
IEnumerable<Customer> customers = ...
using (IEnumarator<Customer> customerEnumerator = customers.GetEnumerator())
{
while (customerEnumerator.MoveNext())
{
// there is still a Customer in the sequence, fetch it and process it
Customer customer = customerEnumerator.Current;
ProcessCustomer(customer);
}
}
Well this is a lot of code, so the creators of C# invented foreach, which will do most of the code:
foreach (Customer customer in customers)
ProcessCustomer(customer);
Now that you know what code is behind the foreach, you might understand what happens in the first line of the foreach.
It is important to remember, that an IEnumerable<...> is meant to be processed by your local process. The IEnumerable<...> can call every method that your local process can call.
IQueryable
An object of a class that implements IQueryable<...> seems very much like an IEnumerable<...>, it also represents the potential to produce an enumerable sequence of similar object. The difference however, is that another process is supposed to provide the data.
For this, the IQueryable<...> object holds an Expression and a Provider. The Expression represents a formula to what data must be fetched in some generic format; the Provider knows who must provide the data (usually a database management system), and what language is used to communicate with this DBMS (usually SQL).
As long as you concatenate LINQ methods, or your own methods that only return IQueryable<...>, only the Expression is changed. No query is executed, the database is not contacted. Concatenating such statements is a fast method.
Only when you start enumerating, either at its lowest level using GetEnumerator / MoveNext / Current, or higher level using foreach, the Expression is sent to the Provider, who will translate it to SQL and fetch the data from the database. The returned data is represented as an enumerable sequence to the caller.
Be aware, that there are LINQ methods, that don't return IQueryable<TResult>, but a List<TResult>, TResult, a bool, or int, etc: ToList / FirstOrDefault / Any / Count / etc. Those methods will deep inside call GetEnumerator / MoveNext / Current`; so those are the methods that will fetch data from the database.
Back to your question
Database management systems are extremely optimized for handling data: fetching, ordering, filtering, etc. One of the slower parts of a database query is the transfer of the fetched data to your local process.
Hence it is wise to let the DBMS do as much database handling as possible, and only transfer the data to your local process that you actually plan to use.
So, try to avoid ToList, if your local process doesn't use the fetched data. In your case: you transfer the followings to your local process, only to transfer it back to the database in the IQueryable.Contains method.
Furthermore, (it depends a bit on the framework you are using), the AsEnumerable transfers data to your local process, so your local process has to do the filtering with the Where and the Contains.
Alas you forgot to give us a description of your requirements ("From all Posts, give me only those Posts that ..."), and it is a bit too much for me to analyze all your queries, but you gain most efficiency if you try to keep everything IQueryable<...> as long as possible.
There might be some problems with the Int.TryParse(...). Your provider probably will not know how to translate this into SQL. There are several solutions possible:
Apparently PostCost represents a number. Consider to store it as a number. If it is an amount (price or something, something with a limited number of decimals), consider to store it as a decimal.
If you really can't convince your project leaders that numbers should be stored as decimals, either search for a job where they make proper databases, or consider to create a stored procedure that converts the string that is in PostCost to a decimal / int.
if you will only use fifteen elements, use the IQueryable.Take(15), not the IEnumerable.Take(15).
Further optimizations:
int userId =
var followerUserIds = _context.Followers
.Where(follower => follower.FollowersFollowerId == userId
&& follower.FollowersIsAccept)
.Select(follower => follower.FollowersUserId);
In words: make the following IQueryable, but don't execute it yet: "From all Followers, keep only those Followers that are Accepted and have a FollowersFollowerId equal to userId. From the remaining Followers, take the FollowersUserId".
It seems that you only plan to use it if page is zero. Why create this query also if page not zero?
By the way, never use statements like where a == true, or even worse: if (a == true) then b == true else b == false, this gives readers the impression that you have difficulty to grasp the idea of Booleans, just use: where a and b = a.
Next you decide to create a query that zero or more Posts, and thought it would be a good idea to give it a singular noun as identifier: post.
var post = _context.Posts
.Where(post => (followings.Contains(post.PostsUserId)
|| post.PostsUser.UserIsPublic
|| post.PostsUserId == userId)
&& post.PostIsAccept);
Contains will cause a Join with the Followers table. It will probably be more efficient if you only join Accepted posts with the followers table. So first check on PostIsAccept and the other predicates before you decide to join:
.Where(post => post.PostIsAccept
&& (post.PostsUser.UserIsPublic || post.PostsUserId == userId
|| followings.Contains(post.PostsUserId));
All non-accepted Posts won't have to be joined with the Followings; depending on whether your Provider is smart enough: it won't join all public users, or the one with userId, because it knows that it will already pass the filter.
Consider to use a Contains, instead of Any
It seems to me that you want the following:
I have a UserId; Give me all Accepted Posts, that are either from this user, or that are from a public user, or that have an accepted follower
var posts = dbContext.Posts
.Were(post => post.IsAccepted
&& (post.PostsUser.UserIsPublic || post.PostsUserId == userId
|| dbContext.Followers
.Where(followers => ... // filter the followers as above)
.Any());
Be aware: I still haven't executed the query, I only have changed the Expression!
AFter this first definition of posts, you filter the posts further, depending on various values of spto. You could consider to make this one big query, but I think that won't speed up the process. It will only make it more unreadable.
Finally: why use:
.Select(post => post)
This doesn't do anything to your sequence, it will only make it slower.
Some observations:
.AsEnumerable()
This is meant to hide where operators if you use a custom collection. It should not be needed in this case.
.Select(p => p)
I fail to see any purpose for this, remove it.
int.TryParse(p.PostCost, out value) && Convert.ToInt32(p.PostCost) >= spto.MinCost
Parsing can be expensive, so you want to do as little as possible, and this does it twice, of four times if you have both min and max. replace with direct compares with value, i.e. int.TryParse(p.PostCost, out value) && value >= spo.MinCost. I would also suggest have an explicit case when there is both a min and max cost to avoid parsing twice.
followings.Contains(p.PostsUserId)
Followings is a list, so it will search thru all items. Use a HashSet to speed up performance. I.e. Replace .ToList() with ToHashSet() when creating the followings list. HashSet uses a hash table to make Contains() a constant time operation rather than a linear operation.
Query order
You would want to order the checks to eliminate as many items as early as possible, and make simple, fast, checks before slower checks.
Merge where operators
A single where operator is in general faster than multiple calls.
Use plain loop
If you really need as high performance as possible it might be better to use regular loops. Linq is great for writing compact code, but performance is usually better with plain loops.
Profile
Whenever you talk about performance it is important to point out the importance of profiling. The comments above are reasonable places to start, but there might be some completely different things that takes time. The only way to know is to profile. That should also give a good indication about the improvements.
I have solved my problem with ternary operator :
var post = _context.Posts.AsNoTracking().Where(p =>
(followings.Contains(p.PostsUserId) || p.PostsUser.UserIsPublic == true || p.PostsUserId == userId) && p.PostIsAccept == true
&& (spto.MinCost != null ? int.TryParse(p.PostCost, out value) && Convert.ToInt32(p.PostCost) >= spto.MinCost : 1 == 1)
&& (spto.MaxCost != null ? int.TryParse(p.PostCost, out value) && Convert.ToInt32(p.PostCost) <= spto.MaxCost : 1 == 1)
&& (spto.TypeId != null ? p.PostTypeId == spto.TypeId : 1 == 1)
&& (spto.CityId != null ? p.PostCityId == spto.CityId : 1 == 1)
&& (spto.IsImmidiate != null && spto.IsImmidiate == true ? p.PostIsImmediate == true : 1 == 1)).Select(p => new
{
p.Id,
Image = p.PostsImages.Select(i => i.PostImagesImage.ImageAddress).FirstOrDefault(),
p.PostCity.CityName,
p.PostType.TypeName
}).Skip(spto.Page * 15).Take(15).ToList();
EDIT (Better Code) :
Thanx to #ZoharPeled , #HaraldCoppoolse , #JonasH I have Changed the Code like this :
int value;
var post = _context.Posts.AsNoTracking().Where(p =>
(followings.Contains(p.PostsUserId) || p.PostsUser.UserIsPublic == true || p.PostsUserId == userId) && p.PostIsAccept == true
&& (spto.MinCost == null || (int.TryParse(p.PostCost, out value) && Convert.ToInt32(p.PostCost) >= spto.MinCost))
&& (spto.MaxCost == null || (int.TryParse(p.PostCost, out value) && Convert.ToInt32(p.PostCost) <= spto.MaxCost))
&& (spto.TypeId == null || p.PostTypeId == spto.TypeId)
&& (spto.CityId == null || p.PostCityId == spto.CityId)
&& (spto.IsImmidiate == null || p.PostIsImmediate == true)).Select(p => new
{
p.Id,
Image = p.PostsImages.Select(i => i.PostImagesImage.ImageAddress).FirstOrDefault(),
p.PostCity.CityName,
p.PostType.TypeName
}).Skip(spto.Page * 15).Take(15).ToList();
Edit (Best Code) :
int userId = Convert.ToInt32(HttpContext.User.Identity.GetUserId());
var followings = _context.Followers
.Where(follower => follower.FollowersFollowerId == userId
&& follower.FollowersIsAccept)
.Select(follower => follower.FollowersUserId);
int value;
var post = _context.Posts.AsNoTracking().Where(p => p.PostIsAccept
&& (p.PostsUser.UserIsPublic || p.PostsUserId == userId
|| _context.Followers.Where(f => f.FollowersFollowerId == userId
&& f.FollowersIsAccept).Select(f => f.FollowersUserId).Any()));
if (spto.MinCost != null)
post = post.Where(p => int.TryParse(p.PostCost, out value) && Convert.ToInt32(p.PostCost) >= spto.MinCost);
if (spto.MaxCost != null)
post = post.Where(p => int.TryParse(p.PostCost, out value) && Convert.ToInt32(p.PostCost) <= spto.MaxCost);
if (spto.TypeId != null)
post = post.Where(p => p.PostTypeId == spto.TypeId);
if (spto.CityId != null)
post = post.Where(p => p.PostCityId == spto.CityId);
if (spto.IsImmidiate != null)
post = post.Where(p => p.PostIsImmediate == true);
var posts = post.Select(p => new
{
p.Id,
Image = p.PostsImages.Select(i => i.PostImagesImage.ImageAddress).FirstOrDefault(),
p.PostCity.CityName,
p.PostType.TypeName
}).Skip(spto.Page).Take(15).ToList();
if (posts.Count != 0)
return Ok(posts);

Where condition equal true and Nullable object must have a value

I've been looking for an answer but I couldn't find anything to help me. I get this error
Nullable object must have a value.
My request is:
from e in dc.tblElements
where
e.IsUnique &&
(e.TypeID == 2) &&
(categoryId != null ? e.CategoryId.Value == categoryId.Value : true) &&
((e.Name.Contains(keyword)) ||
(e.Keywords.Contains(keyword)))
select e
The third line of the where condition is the problem (categoryId). If categoryId has a value, it works but not when it is null. However, I replaced this line with true and it works as well. I can't understand what is the problem here.
in my table CategoryId can be null so I tried:
(categoryId.HasValue && e.CategoryId.HasValue ? e.CategoryId.Value == categoryId.Value : true)
What I want to do: I want to select all the elements of this table depending on the where condition. categoryId comes from a drop down so if the default value is still selected when the user does the request, I want to display all the elements no matter what the category.
You should be good with just comparing your two variables:
e.CategoryId == categoryId
If you want special treatment of one being NULL, maybe because you want that to be a special case where NULL matches everything instead of just another NULL, you can add that:
e.CategoryId == categoryId || !e.CategoryId.HasValue || !categoryId.HasValue
Your problem with your statement is that you access .Value. Yes, if you would run the code with Linq-To-Objects in memory, it would work because the compiler will only run the code of one branch of your if-statement (ternary operator, I know, but you get what I mean). But for a database, there needs to be a statement prepared. That statement needs to be there in full, it does not use any short-circuiting. So the statement builder will access both your branches to build that statement for the database and one of those branches will fail because it accesses .Value although there is none.
Make CategoryId as nullable type and try.
Nullable<int> CategoryId = null;
Looks like you are trying to implement a "catch-all" categoryId parameter. That's an anti-pattern in SQL and a strong smell that can lead to bad performance.
In LINQ, it's not necessary since you can add .Where() conditions just by adding another .Where() call to your query, eg :
var query = from e in dc.tblElements
where
e.IsUnique &&
e.TypeID == 2 &&
( e.Name.Contains(keyword) ||
e.Keywords.Contains(keyword) )
select e;
if (categoryId.HasValue)
{
query=query.Where(e.CategoryId == categoryId);
}
You can use this to add multiple conditions at runtime
Try this:
from e in dc.tblElements
where
e.IsUnique &&
(e.TypeID == 2) &&
(categoryId.HasValue && e.CategoryId.Value == categoryId.Value) &&
((e.Name.Contains(keyword)) ||
(e.Keywords.Contains(keyword)))
select e

Using multiple clauses in where

What is the correct way to include multiple wheres in a LINQ call for OR
List<Pos> posList = DbContext.PosList
.Where<Pos>(p => p.Pos == "51000785" ||
p => p.Pos == "123")
.ToList<Pos>();
The Linq where clause takes one expression and returns one bool value. Yours is taking two expressions each with their own return value. You would need to combine these two into one lambda expression that returns one value rather than the two separate ones in your example.
List<Pos> posList = DbContext.PosList
.Where<Pos>(p => p.Pos == "51000785" || p.Pos == "123")
.ToList<Pos>();

C# Linq contains, group by and order by

In my program I have a search method.
I have two versions of it:
1st version where I search for drawings with a drawing number in my database like normal.
2nd version is the normal search plus the function to find only the newest objects with this drawingnumber in the database.
For both options I´m using the CONTAINS method.
For the normal search it works well but on the newest search, where it is combined with GROUP BY and ORDER BY I feel like there are many objects missing.
I checked when I use the whole drawingnumber, so that the CONTAINS doesn´t need to work.
So for example when I just write 100 for the drawingnumber and using the normal search it finds all objects with the 100 - Thats fine.
However, when I search for the newest objects with only the 100 it finds only a few.
When I use the whole number for the search it finds the newest objects for the right number. So is it bad anyways to use CONTAINS with all the sort- and order stuff or I´m just missing something there?
For better understanding, the drawingnumber there can be different extensions and doktypes. The Dok_Count is the count for the newest document where I´m looking for.
var query =
from z in context.zeichnungs
where (zeichnungsnummer == "" || z.Zeichnungsnummer.Contains(zeichnungsnummer)) &&
(index == "" || index == z.Index) &&
(artikelbezeichnung == "" || artikelbezeichnung == z.Artikelbezeichnung) &&
(status == "" || status == z.Status) && (mmsSachmerkmal == "" || mmsSachmerkmal == z.MMS_Sachmerkmal) &&
(doktyp == "" || doktyp == z.Dokumententyp) && (dateiendung == "" || dateiendung == z.Dateiendung) &&
(z.Datum >= startDate.Date && z.Datum <= endDate.Date) && (status == "" || status == z.Status)
select z;
var sortQuery = query.GroupBy(x => new { x.Dokumententyp, x.Dateiendung }).Select(g => g.OrderByDescending(record => record.Dok_Count).FirstOrDefault());
you are trying to do database normal work with linq this is not it pourpose, use views, thanks for the vote.
I think the data server do this with better performance than in .net classes. Now, I think, you can do a sql profile (or trace) to catch what is the command that the process ask to the database, you will see that the contains its a where in method, in my opinion if you have a alternative way to do this, like join in database, would be better. In other hand, if the number of the records that return this query never will be under 1000, it's no matter, this is a good way.
Sorry for my English :D

LINQ query, ignoring results with certain decimal points

I need to perform a LINQ query on a large database in C#. One of the columns I need to use in the query is a double. I need to omit results that have more than 4 decimal places in this column. The database can't be changed as other programs need to use it and make use of what I don't want. The results are then added to a list to use later. I thought that this would work.
where fun.Units != '*.?????*'
However it returns the error that too many characters are in the character literal.
The whole query looks like this so far
var clientQuery1 = from cli in main1.Clients
from pol in main1.Policies
from fun in main1.FundHoldings
from uni in main1.UnitPrices
where cli.AccountNumber == accNum
&& pol.ClientRef == cli.ClientRef
&& fun.FKeyRef == pol.PolicyRef
&& uni.UnitPriceRef == fun.UnitPriceRef
&& fun.Units != '*.?????*'
select uni.UnitName;
Can you please try with this below query and let me know.
var clientQuery1 = from cli in main1.Clients
from pol in main1.Policies
from fun in main1.FundHoldings
from uni in main1.UnitPrices
where cli.AccountNumber == accNum
&& pol.ClientRef == cli.ClientRef
&& fun.FKeyRef == pol.PolicyRef
&& uni.UnitPriceRef == fun.UnitPriceRef
&& fun.Units == Math.Round(Convert.ToDouble(fun.Units),4)
select uni.UnitName;
Well you can solve that particular error using:
&& fun.Units != "*.?????*"
Note the change from single quotes to double quotes. However, that's not going to help you overall. What's the type of fun.Units in LINQ? If it's decimal, you might be able to use:
&& decimal.Round(fun.Units, 4) == fun.Units
... but it's not clear to me what that will do in the generated SQL. It's worth a try, but even if it works you should see what the SQL looks like.

Categories