Using string methods in EF LINQ queries - c#

I have a situation wherein two urls need to be compared, one coming in to the method and the other in the db. The one in the db may or may not be Url decoded and will have %20 for space and so on.
Here's the linq query in the method:
var Result = (from s in context.301Redirects
where s.IsDeleted == false && s.Status == true && HttpUtility.UrlDecode(s.OldURL).ToUpper() == HttpUtility.UrlDecode(OldURLPath).ToUpper()
select s.NewURL).FirstOrDefault();
return Result;
Inside the method I can UrlDecode the incoming param but what about the one in the db?
This one fails because EF will not recognize UrlDecode method and throw and exception saying so.
Is there a workaround?

You can bring the collection in memory, then perform the UrlDecode at that point by evaluating the query. Try:
var Result = (from s in context.301Redirects
where s.IsDeleted == false && s.Status == true)
.AsEnumerable()
.FirstOrDefault(HttpUtility.UrlDecode(s.OldURL).ToUpper() == HttpUtility.UrlDecode(OldURLPath).ToUpper();
return Result;
If your database table is huge though, I'd consider creating some sort of TRIGGER/update script to either URL encode or decode all records, that way you don't have to check at all, you just encode/decode the parameter passed in.

Do your adjustments before the query so that you have absolute values for comparisons in the query that align with the particular formats, and always check against the ones in the database, rather than transform them - so, this potentially includes using OR in your query, instead of pulling the whole thing into memory a la ToList.

Related

My ForEach loop with a LINQ expression cannot be translated?

I need to go through all my chemicals and send out a warning to users if the expiration date is either 2 days away or 15 days away.
I have it set like that so the warning is sent out twice. Once 2 weeks before it expires, and once 2 days before it expires.
So I have this loop:
foreach (var chemical in _context.Chemical.Where(c => (c.DateOfExpiration - DateTime.Now).TotalDays == 15 || (c.DateOfExpiration - DateTime.Now).TotalDays == 2))
{
// send out notices
var base = _context.Base.Find(chemical.BaseId);
var catalyst = _context.Catalyst.Find(chemical.CatalystId);
_warningService.SendSafetyWarning(chemical, base, catalyst.Name);
}
But whenever I run it, I get this error:
System.InvalidOperationException: 'The LINQ expression 'DbSet<Chemical>
.Where(p => (p.DateOfExpiration - DateTime.Now).TotalDays == 15 || (p.DateOfExpiration - DateTime.Now).TotalDays == 2)'
could not be translated.
I'm not sure why it's giving me this error when I run it.
Is there a better way of doing what I'm trying to do?
Thanks!
DateTime.TotalDays returns a double.
You might have more luck changing your code to:
foreach (var chemical in _context.Chemical.Where(c => ((int)(c.DateOfExpiration - DateTime.Now).TotalDays == 15) || ((int)(c.DateOfExpiration - DateTime.Now).TotalDays == 2)))
{
// send out notices
var base = _context.Base.Find(chemical.BaseId);
var catalyst = _context.Catalyst.Find(chemical.CatalystId);
_warningService.SendSafetyWarning(chemical, base, catalyst.Name);
}
There are two solutions to this problem.
I'll first explain why it is throwing an error. EF is converting your code to an SQL Expression. When working with Expression you cannot parse in any C# code and expect it to work. EF needs to be able to translate it to SQL. That is why you are getting the exception saying that it can't translate your code.
When working directly in the Where clause on the DbSet or the DbContext you are actualy talking to the Where method for an IQueryable. Which has a parameter of Expression<Func<bool, T>> where T is your DbSet<T> type.
You either need to simplify your query so it can be translated to SQL. Or you can convert your IQueryable to an IEnumerable.
When you want to run your query on the SQL server, you need to use the first approach. But when you can run your query on the client you can use the second approach.
The first approached as mentioned by vivek nuna is using EntityFunctions. It might not be enough for your use case, because it has limited functionality.
The statement made that DateTime.Now is different on each iteratoion is not true. Because the query is converted once, it isn't running the query on every iteration. It simply doesn't know how to translate your query to SQL.
https://learn.microsoft.com/en-us/dotnet/api/system.data.objects.entityfunctions?view=netframework-4.8
The second approach means adding AsEnumerable after your DbSet. This will query all your data from your SQL server, and evaluate in C#. This is usually not recommended if you have a large data set. Example:
var chemicals = _context.Chemical.AsEnumerable(); // this will get the complete collection
chemicals.Where(i => i.Value == true); // everything will work now
And answer to a "better" approach. It's a bit cleaner code:
If you have navigation properties for your Base and Catalyst you can also include this in your query.
Note that you can still query server side before pulling everything client side using a Where on the _context.Chemical and then including and calling AsEnumerable.
var chemicals = _context.Chemical.Include(i => i.Base).Include(i => i.Catalyst).AsEnumerable();
foreach (var chemical in chemicals.Where(i => true)) // set correct where clause
{
_warningService.SendSafetyWarning(chemical, chemical.Base, chemical.Catalyst);
}
You can declare the variable before and then pass it to your expression. Like DateOfExpiration == nextFifteenDays …..
var nextFifteenDays = DateTime.Now.Adddays(15);
var nextTwoDays = DateTime.Now.AddDays(2);
Or you can use EntityFunctions.Diffdays
Reason for the error is well explained by #neil that DateTime.Now will keep on changing, so can’t be used.

Linq skip query if string is Null

I am trying to implement a search function, but am running into problems when some of the fields are not filled in by a user.
string country = searchCountry.Text.ToLower();
string state = searchState.Text.ToLower();
var searchLocation= (from h in db.Locations where (!string.IsNullOrWhiteSpace(country) ? h.Country.ToLower().Contains(country):false)
&& (!string.IsNullOrWhiteSpace(state) ? h.State.ToLower().Contains(state) : false)
select h);
The problem is that when one of the strings is empty the searchLocation returns nothing and only works when both fields are filled in. I Have tried replacing the && with || but then it will get results, even if one of the search terms is not in the db.
Is there a way to do this, besides Filtering out null values in a linq search
This will return any locations where either the country is empty or it matches, and either the state is empty or it matches.
var searchLocation= (from h in db.Locations
where (string.IsNullOrWhiteSpace(country) || h.Country.ToLower().Contains(country))
&& (string.IsNullOrWhiteSpace(state) || h.State.ToLower().Contains(state))
select h);
It would help to have a bit more description of what you'd like to put in and get out, but this seems logical to me.
Either field would be optional, but it would filter results to include anything that matched all (one or two) filled in fields.
Of course, if you run this without any filters, it will return all locations. So keep that in mind if you're making requests to the database. If that's desired behavior, it might make sense to just pull all your data into a list beforehand, rather than querying every time anything is typed.
I believe you're overthinking this. Just validate the fields before searching:
string country = searchCountry.Text.ToLower();
string state = searchState.Text.ToLower();
if(string.IsNullOrWhitespace(state) || string.IsNullOrWhitespace(country))
{
//MessageBox.Show...
return;
}
var searchLocation= //query with validated fields
It's a very good idea to validate your input before trying to perform actions against it. And it makes your code more readable than combining the two.

Get First Single matched element or First if there's no match?

Is that possible in LINQ to write a nice one-liner to get a first matched element or if there's no match than get first element in the collection?
E.g. you have a collection of parrots and you want yellow parrot but if there's no yellow parrots - then any will do, something like this:
Parrots.MatchedOrFirst(x => x.Yellow == true)
I'm trying to avoid double-go to SQL Server and the ORM we use in this particular case is Dapper.
What about:
var matchedOrFirst = Parrots.FirstOrDefault(x => x.Yellow == true)
?? Parrots.FirstOrDefault();
Edit
For structs, this should work:
var matchedOrFirst = Parrots.Any(x => x.Yellow == true)
? Parrots.First(x => x.Yellow == true)
: Parrots.FirstOrDefault();
Edit: It was a linq to SQL solution
First building a handy extension
public static T MatchedOrFirstOrDefault<T>(this IQueryable<T> collection, System.Linq.Expressions.Expression<Func<T, Boolean>> predicate)
{
return (from item in collection.Where(predicate) select item)
.Concat((from item in collection select item).Take(1))
.ToList() // Convert to query result
.FirstOrDefault();
}
Using the code
var matchedOrFirst = Parrots.MatchedOrFirstOrDefault(x => x.Yellow);
If you want to avoid a 2nd SQL call and since requires branching logic, its unlikely that Dapper will know how to convert a LINQ query you come up with into appropriate SQL IIF, CASE, or whatever other SQL-specific functions you end up using.
I recommend you write a simple stored procedure to do that and call it from Dapper.
Depending on its usage though, if this page only has one or two queries on it already, and is located reasonably close (latency wise) to the server, a 2nd simple SELECT won't hurt the overall application that much. Unless it is in a loop or something, or your example is trivial compared to the actual query regarding the cost of the first SELECT.

The linq query taking too much time. Need to reduce the Time

Here i am using the below query and its taking lots of time around 14 to 15 seconds for retrieving the large amount of data.
In below Query the CreatedDate is of DateTimeOffset data type.
var naId = UnitOfWork.SalesPhases.FirstOrDefault(p => p.PhaseName =="NA").SalesPhaseId;
var rejectedId = UnitOfWork.SalesPhases.FirstOrDefault(p => p.PhaseName =="Rejected").SalesPhaseId;
var data = UnitOfWork.Leads.Query().AsEnumerable()
.Where(p =>(p.SalesPhaseId == naId || p.SalesPhaseId == rejectedId) &&
p.CreatedDate.Date >= fromDate && p.CreatedDate.Date <= toDate).Select(m =>
new
{
m.LeadId,
m.LeadOwnerId,
m.SalesPhaseId,
m.LeadActivities,
m.Employee,
m.SalesPhase,
m.CompanyName,
m.CreatedDate,
m.LeadHistories,
m.LeadAddresses
}).ToList();
I tried using the AsQueryable instead of the AsEnumerable but it gives the below error:
"The specified type member 'Date' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported."
Can you help me out to reduce the execution time of the query?
Your use of AsEnumerable is forcing the filtering to be done locally. It's pulling in all the data, then filtering it in your app. That's clearly very inefficient. Now, it seems that part of your query can't be directly expressed in LINQ to SQL. I see two options here.
Firstly you could do most of your filtering in SQL, but then do the date filtering locally:
var data = UnitOfWork.Leads.Query()
// Do this part of the query in SQL
.Where(p => p.SalesPhaseId == naId ||
p.SalesPhaseId == rejectedId)
.AsEnumerable()
// Do the rest of the query in-process
.Where(p => p.CreatedDate.Date >= fromDate &&
p.CreatedDate.Date <= toDate)
.Select(...)
That's suitable if the first part will filter it down massively, and then you only need to do local processing of a small set of data.
Alternatively, you could work out what your date filtering means in terms of DateTime. It looks like you could do:
// This may not be required, depending on the source.
fromDate = fromDate.Date;
// This will be, although you may be able to get rid of the ".Date" part.
toDate = toDate.Date.AddDays(1);
var data = UnitOfWork.Leads.Query()
// Do this part of the query in SQL
.Where(p => (p.SalesPhaseId == naId ||
p.SalesPhaseId == rejectedId) &&
p.CreatedDate >= fromDate &&
p.CreatedDate < toDate)
.Select(...)
That's created an equivalent query, but without using the Date property in the query itself.
Everything after AsEnumerable() is executed locally rather than on the server. See also
https://stackoverflow.com/a/2013876/141172
This means that all rows in the table are returned from the database, and then filtered in your C# code.
Remove that call so that the filtering happens server-side.
EDIT
Noticed Jon's comment and it reminded me that he reimplemented LINQ to Objects as a learning exercise. His comments about the AsEnumerable() reimplementation are worth reading
I can describe its behaviour pretty easily: it returns source.
That's all it does. There's no argument validation, it doesn't create another iterator. It just returns source.
You may well be wondering what the point is... and it's all about changing the compile-time type of the expression. I'm going to take about IQueryable in another post (although probably not implement anything related to it) but hopefully you're aware that it's usually used for "out of process" queries - most commonly in databases.
Now it's not entirely uncommon to want to perform some aspects of the query in the database, and then a bit more manipulation in .NET - particularly if there are aspects you basically can't implement in LINQ to SQL (or whatever provider you're using). For example, you may want to build a particular in-memory representation which isn't really amenable to the provider's model.
https://msmvps.com/blogs/jon_skeet/archive/2011/01/14/reimplementing-linq-to-objects-part-36-asenumerable.aspx
Your code should like this..
var naId = UnitOfWork.SalesPhases.FirstOrDefault(p => p.PhaseName =="NA").SalesPhaseId;
var rejectedId = UnitOfWork.SalesPhases.FirstOrDefault(p => p.PhaseName =="Rejected").SalesPhaseId;
var data = UnitOfWork.Leads.Query().AsQueryable()
.Where(p =>(p.SalesPhaseId == naId || p.SalesPhaseId == rejectedId) &&
p.CreatedDate>= fromDate.Date && p.CreatedDate <= toDate.Date).Select(m =>
new
{
m.LeadId,
m.LeadOwnerId,
m.SalesPhaseId,
m.LeadActivities,
m.Employee,
m.SalesPhase,
m.CompanyName,
m.CreatedDate,
m.LeadHistories,
m.LeadAddresses
}).ToList();
Firstly, You need to use .ToQueryable instead of .ToIEnumerable().
Secondly, you cannot use .Date to datetime properties inside a entity framework linq query. That only works for in-memory collections like list and arrays.

Check for items inside a Entity Framework linq result

I am trying to check to see if any results were returned from an entity framework query before acting upon it, I'm trying to use the code :-
var shoppingCartSessions = from c in context.ShoppingCartSessions where c.UserId == gUserID select c;
if (shoppingCartSessions.First() != null)
{
}
However I get the error
Sequence contains no elements
I have checked around stack and found that I can replace .First with .FirstOrDefault however I wanted to check if this is the correct way to be checking for existence of elements. Is there a better way rather than trying to fetch the item and then checking it?
Use Any():
var shoppingCartSessions = from c in context.ShoppingCartSessions
where c.UserId == gUserID
select c;
if (shoppingCartSessions.Any())
{
//not empty
}
Have you tried checking .Count() > 0 ?
EDIT:
As stated by Mahmoud Gamal, using Any() should render slightly better performance, since it will execute an EXISTS query, rather than a COUNT() on the DB, and you ultimately don't care about the exact amount.

Categories