Weird behavior in Entity Framework Linq in string EndsWith method - c#

Background
I have a table that contains only one column: Name.
There are only four rows in it, say
| Name |
| test1.com |
| test2.com |
| test3.com |
| test4.com |
Problem
If I query
var email = "a#test2.com";
Table.Where(x => email.EndsWith(x.Name));
I'll get an empty list. but If I query all rows first and calculate Where in memory like this
var email = "a#test2.com";
Table.ToList().Where(x => email.EndsWith(x.Name));
I'll get a list contains only test2.com which is correct.
The generated SQL for the first query is
SELECT "Extent1"."Name" AS "Name"
FROM "USER"."Table" "Extent1"
WHERE (( NVL(INSTR(REVERSE(:p__linq__0), REVERSE("Extent1"."Name")), 0) ) = 1)
I've tried replacing :p__linq__0 with 'a#test2.com' and running the query in the SQLDeveloper, the result is correct.
More Information
If I change EndsWith() to Contains(), the problem will be gone. Here is the generated SQL for Contains()
SELECT "Extent1"."Name" AS "Name"
FROM "USER"."Table" "Extent1"
WHERE (( NVL(INSTR(:p__linq__0, "Extent1"."Name"), 0) ) > 0)
Do you have any idea what's wrong with EndsWith or REVERSE method?
Environment
EF5.0
.NET4.5
Oracle11g
ODP.NET11.2 Release 3

This line concerns me and is a common pitfall with people using EF:
Table.ToList().Where(x => email.EndsWith(x.Name));
The part Table.ToList() is the worst part because this will actually materialise the entire table into memory and then perform the EndsWith in C#.
This line:
Table.Where(x => email.EndsWith(x.Name));
I would caution this approach just on general principle as it will be horrendously slow when the table grows to reasonable size. You can do the heavy lifting before the query hits the database by splitting out the domain from the the email as you construct the query:
var email = "a#test2.com";
/* You should null check this of course and not just assume a match was found */
var domain = Regex.Match(email , "#(.*)").Groups[1].Value;
/* Note: ToList() materialisation happens at the end */
var result = Table.Where(x => x.Name == domain).ToList();
Furthermore, if you need to match on the domain names of a column storing emails, then my preferred approach would be to split the email and store the domain name in a separate column that you index and just match on, this will scale and be a heck of a lot easier to manage. Remember that these days data is cheap... especially compared to non-indexable table scans.
Also remember (for both scenarios) that your database is set to CI (case insensitive)

Related

OrderByDescending - specific data 1st and then rest in order

We have data as follows
Created On
Supplier
01-Nov-21
ABC
02-Nov-21
XYZ
15-Nov-21
ABC
20-Nov-21
MNO
25-Nov-21
ABC
So we want to record in descending order of creation but 1st want all "ABC" records on the top.
I tried with this one, but didn't worked.
object.OrderByDescending(m => m.name== "ABC").ThenBy(x => x.CreatedOn)
So instead of getting records as follows
01-Nov-21 | ABC
15-Nov-21 | ABC
25-Nov-21 | ABC
02-Nov-21 | XYZ
20-Nov-21 | MNO
It was coming in same order as above i.e. based on createdOn field
Am i missing anything here?
in descending order of creation but 1st want all "ABC" records on the top
You need to use ThenByDescending - using OrderByDescending initially doesn't then set the sorting of the date up to be descending too
I don't personally think you should sort a bool in your first clause, because it's not obvious to everyone how booleans sort. Rather than it be something one has to remember/look up, I would recommend that you use the bool to generate an int for sorting because everyone can easily understand your intent (everyone can put 0 and 1 into ascending order):
foreach(var x in list.OrderBy(m => m.name == "ABC" ? 0 : 1).ThenByDescending(x => x.CreatedOn))
//process your objects in turn here
To achieve a "date descending but all ABC on top" you first sort on whether something is ABC or not (and bear in mind that that's an exact, case sensitive match, unless this query is being translated to SQL and send to a DB like SQL server, where case sensitivity depends on the collation of the column being sorted/where trailing spaces may be ignored), then you want to sort the date descending with ThenByDescending.

Get single row using Entity Framework without getting all data [duplicate]

This question already has answers here:
Entity Framework 4: Selecting Single Record
(3 answers)
Closed 7 years ago.
I'm trying to understand whether it possible to get a single row from a database using entity framework without returning all the data. Maybe I'm misunderstanding how EF work but I believe its similar to the following:
TBL1
Id | Name | Place
1 | Teressa Green | UK
2 | Robin Banks | Germany
3 | Liam Neeson | Canada
If I want Robin Banks Id do something similar to
context.tbl1.where(obj => obj.name = "Robin Banks")
However from what I've understood this is getting all data from the table and then filtering down to the one row. Is there a way to return just the one row back to the logic without initially returning all the data?
To put the my issue in one sentence. I'm trying to avoid loading back all rows when I just want 1.
I think you need to use here SingleOrDefault
var result= db.yourtable
.SingleOrDefault(c=>c.Name== "Some Name");
Whenever you use SingleOrDefault, you clearly state that the query should result in at most a single result
This line will not actually execute anything on the database:
context.tbl1.Where(obj => obj.name == "Robin Banks")
It will return an IEnumerable<tbl1> which is going to be lazily evaluated when you come to use it. To execute an actual query on the database you need to perform an enumeration on the IEnumerable<tbl1> (e.g. a foreach, .ToList() or .SingleOrDefault()). At this point EF will convert your Where() clause into actual SQL and execute it on the database, returning the specified data. So, it will get all data that matches your predicate obj.name="Robin Banks". It will not get all the data in tbl1 using a SQL statement and then filter the results in .NET - that's not how it works.
However, you can do this (if you need to, but not recommended almost 100% of the time) by first enumerating with .ToList():
context.tbl1.Where(obj => <some SQL evaluated expression>).ToList()
And then adding an additional predicate on the end:
context.tbl1.Where(obj => <some SQL evaluated expression>).ToList().Where(obj => <some .NET evaluated expression>).ToList()
You can log the actual SQL being generated by EF by doing the following with your context:
context.Database.Log = Console.WriteLine;
And see for yourself what's going on under the hood.
you are not sure if an item with a given key exists --> FirstOrDefault
Entity Framework 4 Single() vs First() vs FirstOrDefault()

How can I do case insensitive and concatenated field searches using nhibernate / linq?

I have an asp.net-mvc website that uses Fluent Nhibernate Linq / SQL Server. I have a textbox where someone can enter a name and that generate the following query that I am using now to search my Person table:
return Session.Query<Person>()
.Where(r => (r.LastName.Contains(s) || r.FirstName.Contains(s)));
This works as expected in terms of translating to a "SQL like query"
Select * from Person where FirstName like '%%' or LastName like '%%'
but I have 2 new requirements that I am not sure that nhibernate linq supports.
In some cases people are entering the name in upper or lower case so I want to be able to do a case insensitive search.
Since it's a single textbox, in some cases people type in both the first and last name (something like "Smith, Joe" and that fails to find a result given that overall string doesn't exist in either the first or last name fields. Besides breaking the UI up into separate fields (which I can't do for some other reasons) is there any suggestion on how I could support the query to include the following combination of user's search string
[First] [Last]
[Last], [First]
in the above search code.
To solve issue with mixed upper/lower, we can just convert both sides into .ToLower()
return Session.Query<Person>()
.Where(r => (r.LastName.ToLower().Contains(s.ToLower())
|| r.FirstName.ToLower().Contains(s.ToLower())));
Check this link for more details how the NHibernate native InsensitiveLikeExpression.cs is working (for almost every dialect it is doing the same) :
NHibernate IsInsensitiveLike treats strings as case sensitive
The second part, here is some super simple algorithm (if it is one at all)
var first = searched.Split(' ')[0].ToLower();
var last = searched.Split(' ')[1].ToLower();
var emps = session.Query<Person>()
.Where(e =>
(e.FirstName.ToLower().StartsWith(first)
&& e.LastName.ToLower().StartsWith(last))
||
(e.FirstName.ToLower().StartsWith(last)
&& e.LastName.ToLower().StartsWith(first))
)
Very similar solution could be used (and I do) for combobox searching... Where "Ra Ko" will also find Radim Kohler...
Your first point-
1). In some cases people are entering the name in upper or lower case
so I want to be able to do a case insensitive search.
Answer is already given by Radim Köhler - that convert both sides into .ToLower()
Now for your second point, below might be useful.
return session.Query<Person>()
.Where(e =>
((e.FirstName.ToLower() + " " + e.LastName.ToLower()).Contains(s))
||
((e.LastName.ToLower() + ", " + e.FirstName.ToLower()).Contains(s))
)
There might be any syntax error as i have not tested in VS.

Dyamic LINQ library, give name to columns that contain space

I need to create a query that returns data with column name (without square brackets) as indicated in the code below. How can I do that?
var query = db.Customers
.Where("City = #0 and Orders.Count >= #1", "London", 10)
.OrderBy("CompanyName")
.Select("new(CompanyName as [Company Name])");
When you need to make LINQ queries dynamic you may need to drop down the Expression Tree layer. The reason for this is that virtually everything there is dynamic... It allows you to build queries on the fly but.... it takes a while to learn and it is tedious in my opinon.
This is what was done here:
https://gist.github.com/400553/6562ebb3cf2767d6c1ad9474d6f04691ab6ca412

SQL Port to LINQ with Left Outer Join with aggregation and bitwise filtering

I have the following query:
;WITH valRules AS
( SELECT vr.valRuleID, Count(*) AS totalRows, Sum(vt.test) AS validRows
FROM (SELECT NULL AS x) AS x
JOIN #itemMap AS IM
ON IM.lngitemID = 1
JOIN tblValidationRule AS vr
ON IM.RuleID = vr.valRuleID
JOIN tblValidationRuleDetl AS vrd
ON vr.valRuleID = vrd.valRuleID
LEFT JOIN #ValTest AS vt
ON vrd.type = vt.type
AND vrd.typeSequence = vt.typeSequence
AND vrd.valRule & vt.Response > 0
OR (vrd.valrule = 0 AND vt.response = 0 )
GROUP BY vr.valRuleID
)
SELECT Count(*)
FROM valrules
WHERE totalrows = validRows
Note the CTE, and the Bitwise Operator in the Left Join Condition. How this is currently used is in a stored procedure that takes values from a C# application in the form of an XML variable. The XML Variable is placed into table #valTest. All columns are of datatype INT. If vt.Response is valid for vaRule, the result of & will be greater than zero. (i.e. 31 & 8 = 8 but 12 & 2 = 0). vt.Test column contains the number 1 for each row, so that it may be summed up (nulls are automatically excluded) to get a count of the validations that pass by rule. Each rule has a number of attributes that must pass validation for success. If the number of attributes is equal to those that passed, we have success.
In an effort to reduce calls to the database, the goal is to cache ALL the rules in the ASP.NET cache and handle validation localy. The developers are asking for a de-normalized version of the validation data with the claim that the SQL Set based operation is not a simple task in C# with Linq. From what I have looked into, I would agree. At this point my investigation shows the bitwise comparison in the join condition is particularly problematic.
The main question is how is can this be converted to something that uses Linq on the C# side? Or, are there more efficient ways to deal with this on the client side and Linq is not one of them (i.e. just give them flat data)?
thanks
LINQ-to-SQL isn't going to do anything quite as bespoke as that query. Which isn't a criticism of either LINQ-to-SQL or the query: simply, there are limits.
There are two ways I would approach that:
1: as a parameterized TSQL query via ExecuteQuery<T> - i.e.
var result = db.ExecuteQuery<YourType>(#"your query here with {0}, {1} etc",
arg0, arg1, ...);
2: write that TSQL a udf mapped into the data-context:
var result = db.YourUdf(arg0, ...);
Both are valid and will work with LINQ-to-SQL; personally I prefer the first approach, but the UDF approach allows greater re-use within the DB layer, at the expense of having more complex deployment (i.e. app tier and db tier all at the same time).

Categories