LINQ to SQL Decimal Parameter - c#

I have a very simple linq to sql query in C#:
int acctNum = 12345;
var query = from p in db.table
where p.ACCT_NO == acctNum
select p;
This generates the following SQL:
exec sp_executesql N'SELECT [t0].field1, [t0].field2, [t0].ACCT_NO
FROM [dbo].[table] AS [t0]
WHERE [t0].[ACCT_NO] = #p0', N'#p0 decimal(29,0)', #p0 = 12345
For some reason, this is taking an incredibly long time to run (several minutes). If I run an equivalent query in management studio (select * from table where acct_no = 12345), it takes less than a second on a very large table (~7MM rows). After some digging with the SQL profiler, I found that linq is passing the acctNum parameter as a Decimal(29,0) while the field is stored in the database as a Numeric(18,0). If I take the generated SQL and just change the parameter type from decimal to numeric, it runs in less than a second. In the profiler, I can see that the linq version uses almost 2 million reads versus about 400 for the numeric parameter query. How can I force linq to pass this parameter as numeric instead of decimal?

Most likely the problem lies with the type of p.ACCT_NO (in other words it was probably generated as a floating-point numeric type). Make sure that this property is also typed as an int and it should work.

Related

Alter EF Generated query parameters

I'm using EF 6.4.4 to query a SQL view. Now the view is not really performing optimal, but i don't control it.
I'm executing the following code with a WHERE statement on a string/nvarchar property
_context.ViewObject
.Where(x => x.Name == nameFilter)
.ToList();
Similarly, i have the same SQL statement executed in SMSS
SELECT [Id]
, [Name]
, ...
FROM [View]
WHERE [Name] = '<nameFilter>'
My problem is that the EF variant is way slower than the direct SQL query.
When checking out the SQL query generated by EF i see the following:
SELECT [Id]
, [Name]
, ...
FROM [View]
WHERE [Name] = #p__linq__0
with parameter #p__linq__0 is of type NVARCHAR(4000) NULL
This even though that my input variable is not NULL and has a lenght of maximum 6 characters.
When i execute the same sql query with this parameter, it is slow in SMSS as well.
Apparently, this has somethign
So what i want to do is alter the SQL query parameter that EF is using to generate this query. This to make sure that my parameter is more accurately represented in the query and that i can get the same performance as directly in SMSS.
Is there a way to do this?
Whats going on: parameter sniffing
Execute the following in SSMS and you will propably see the same performance.
EXECUTE sp_executesql N'SELECT [Id]
, [Name]
, ...
FROM [View]
WHERE [Name] = #nameFilter'
,N'#nameFilter nvarchar(4000)'
,#nameFilter = '<namefilter>';
sp_executeSql is used by EF to execute queries against a database and thus, when you write .Where(x => x.Name == nameFilter) this is translated to the above statement.
Making you suffer from parameter sniffing.
You could fix this by adding recompile to your queries like described here But be aware that adding recompile to all queries might have negative impact on the other queries.
You can execute the following queries with actual execution plan to see the difference:
Query with WHERE Name = #NameFilter
Query with WHERE Name = '<NameFilter>'
Query with WHERE Name = #NameFilter OPTION(RECOMPILE)
If it's not parameter sniffing, it might be implicit conversions, but I'm guessing both types are NVARCHAR so this shouldn't matter.
99% of the time it's parameter sniffing.

How to fix: linq query returns #p_linq_0 instead of variable

My linq query returns #p_linq_0.
public IEnumerable<CheckListItem> GetByCheckListGroup(int id)
{
int integer = id;
var connection = context.CheckListItems
.Where(p => p.fkCheckListGroupID == integer)
.AsEnumerable();
return connection;
}
An id of 7 comes through but the query replaces it with #p_linq_0
WHERE [Extent1].[fkCheckListGroupID] = #p__linq__0
without the where clause the query is perfect.
even if i change it to
.Where(p => p.fkCheckListGroupID.Equals(integer)) the same outcome.
If i hard code it, it works.
i expect WHERE [Extent1].[fkCheckListGroupID] = 7 instead of #p_linq_0
query returns
SELECT
[Extent1].[pkSOCheckListItemID] AS [pkSOCheckListItemID],
[Extent1].[fkCheckListGroupID] AS [fkCheckListGroupID],
[Extent1].[pfCode] AS [pfCode],
[Extent1].[pfDescription] AS [pfDescription],
[Extent1].[pfState] AS [pfState],
[Extent1].[pfDefinition] AS [pfDefinition],
[Extent1].[pfExplanation] AS [pfExplanation],
[Extent1].[afName] AS [afName],
[Extent1].[afOrder] AS [afOrder],
[Extent1].[sfCreatedDate] AS [sfCreatedDate],
[Extent1].[sfLastModifiedDate] AS [sfLastModifiedDate],
[Extent1].[sfCreatedBy] AS [sfCreatedBy],
[Extent1].[sfLastModifiedBy] AS [sfLastModifiedBy]
FROM [dbo].[CheckListItem] AS [Extent1]
WHERE [Extent1].[fkCheckListGroupID] = #p__linq__0
SSMS message: Must declare the scalar variable "#p_linq_0"
That's just standard Entity Framework behaviour. Queries are parameterised, so what you'll see in the object connection is the parameterised query. The values for parameters will also be in the object. When the query executes the parameter values are used, but the query string you'll see, if for instance you use SQL Server Profiler, will show the parameter names in places like where clauses and have the parameter values at the end of the query string.
What is executed is a built in stored procedure, sp_executesql. That stored procedure is passed the query string, which contains the parameters, and arguments for each parameter. The parameter arguments are further query strings for defining the parameters and giving them values.
If you're seeing no results in when the query is executed, e.g. by calling ToList on the connection object, then there aren't any matching rows in the table being queried.
There are a couple of ways to check the query that is executed. One is to run SQL Server Profiler and then run the code. Find the execution of the query in the profiler results. You'll be able to copy the query that was executed and run that. However, since you have the query, albeit without parameters, you could take what you have and define the parameters yourself. Or even construct the call to sp_executesql yourself. It should look something like this:
exec sp_executesql(N'<your parameterised query string here>', N'#p_linq_0 int', N'#p_linq_0 = 7')

EF: Parameter postfix in SQL translated from single linq keeps on incrementing

We are facing a production issue with oracle db where the numeric postfix of command parameter in sql issued by EF keeps on increasing when LINQ is being used. For example, when run multiple times, the linq query
var collection = from obj in Query where obj.Val1==val1 select obj;
gets translated as (not exact, but sufficient to describe the idea)
select * from Table1 where Column1=#p__linq__0
select * from Table1 where Column1=#p__linq__1
select * from Table1 where Column1=#p__linq__2
...
select * from Table1 where Column1=#p__linq__1003
...
As a result, the oracle shared pool cache runs out of memory.
Does anyone have any idea of it?

where statement doesn't work correctly in c#

I am using MYSQL in my c# app.
There are some string values in mysql table and there is a textbox that client can write in that and then I use this statement to show the results:
"SELECT ID,METER FROM DB.TABLE1 WHERE METER >= '" +TEXT1.text.tostring+"'";
But for Example, if the client write 400 in that textbox, the results are like this:
50,400,500,600,50,500
But we know that 50 is not bigger than 400!!!
And then I used this code:
"SELECT ID,METER FROM DB.TABLE1 WHERE METER <= '" +TEXT1.text.tostring+"'";
If the client write 400 in that textbox, the results are like this:
300,150,100,250;
50 is not shown!!!
Can you please help me what should I do???
50 is "bigger" than 400 when you treat them as strings, so I suspect that's what you're doing.
First: never, never, never build SQL like this. It's susceptible to SQL injection attacks, it leads to error-prone conversions, and it makes your code harder to read.
Instead, use parameterized SQL. In this case you'll want SQL of something like:
SELECT ID,METER FROM DB.TABLE1 WHERE METER >= #Parameter
and then set the #Parameter parameter in the parameter collection - parsing it as an integer (or whatever type is suitable for the values you're trying to represent) first.
Next: check the type of METER in your schema. If it's varchar or some similar text type, you need to fix that. It should be a suitable numeric type if you want to treat the values as numbers.
< and > operator will work perfeclty on numeric values as you have added single quotes to the values you have passed, it may be causing the issue. try this
"SELECT ID,METER FROM DB.TABLE1 WHERE METER >= "+TEXT1.text.tostring;
Convert user input to Integer and then use the integer value in your query.
SELECT ID,METER FROM DB.TABLE1 WHERE METER >= Convert.ToInteger(TEXT1.text.ToString()) ;
Text comparison isn't numeric comparison. To avoid this misleading behavior and sql injection risk you must use parametrized queries.
You can cast a string to a number in MySQL:
var sql = #"
SELECT ID, METER
FROM DB.TABLE1
WHERE CAST(METER AS UNSIGNED) >= "
+ Convert.ToInteger(TEXT1.text.ToString());
There are all kinds of oddities to consider when dealing with numbers as strings in MySQL.
I suggest reading: https://dev.mysql.com/doc/refman/5.0/en/type-conversion.html
As Jon has suggested, a better solution would be to ALTER the table schema to use a numeric column datatype and use SQL parameters in your code, especially as you seem to be dealing with user input.

sp_executesql runs in milliseconds in SSMS but takes 3 seconds from ado.net [duplicate]

This question already has an answer here:
Stored Proc slower from application than Management Studio
(1 answer)
Closed 9 years ago.
This is my dynamic query used on search form which runs in milliseconds in SSMS roughly between 300 to 400 ms:
exec sp_executesql N'set arithabort off;
set transaction isolation level read uncommitted;
With cte as
(Select ROW_NUMBER() OVER
(Order By Case When d.OldInstrumentID IS NULL
THEN d.LastStatusChangedDateTime Else d.RecordingDateTime End
desc) peta_rn,
d.DocumentID
From Documents d
Inner Join Users u on d.UserID = u.UserID
Inner Join IGroupes ig on ig.IGroupID = d.IGroupID
Inner Join ITypes it on it.ITypeID = d.ITypeID
Where 1=1
And (CreatedByAccountID = #0 Or DocumentStatusID = #1 Or DocumentStatusID = #2 )
And (d.JurisdictionID = #3 Or DocumentStatusID = #4 Or DocumentStatusID = #5)
AND ( d.DocumentStatusID = 9 )
)
Select d.DocumentID, d.IsReEfiled, d.IGroupID, d.ITypeID, d.RecordingDateTime,
d.CreatedByAccountID, d.JurisdictionID,
Case When d.OldInstrumentID IS NULL THEN d.LastStatusChangedDateTime
Else d.RecordingDateTime End as LastStatusChangedDateTime,
dbo.FnCanChangeDocumentStatus(d.DocumentStatusID,d.DocumentID) as CanChangeStatus,
d.IDate, d.InstrumentID, d.DocumentStatusID,ig.Abbreviation as IGroupAbbreviation,
u.Username, j.JDAbbreviation, inf.DocumentName,
it.Abbreviation as ITypeAbbreviation, d.DocumentDate,
ds.Abbreviation as DocumentStatusAbbreviation,
Upper(dbo.GetFlatDocumentName(d.DocumentID)) as FlatDocumentName
From Documents d
Left Join IGroupes ig On d.IGroupID = ig.IGroupID
Left Join ITypes it On d.ITypeID = it.ITypeID
Left Join Users u On u.UserID = d.UserID
Left Join DocumentStatuses ds On d.DocumentStatusID = ds.DocumentStatusID
Left Join InstrumentFiles inf On d.DocumentID = inf.DocumentID
Left Join Jurisdictions j on j.JurisdictionID = d.JurisdictionID
Inner Join cte on cte.DocumentID = d.DocumentID
Where 1=1
And peta_rn>=#6 AND peta_rn<=#7
Order by peta_rn',
N'#0 int,#1 int,#2 int,#3 int,#4 int,#5 int,#6 bigint,#7 bigint',
#0=44,#1=5,#2=9,#3=1,#4=5,#5=9,#6=94200,#7=94250
This sql is formed in C# code and the where clauses are added dynamically based on the value the user has searched in search form. It takes roughly 3 seconds to move from one page to 2nd. I already have necessary indexes on most of the columns where I search.
Any idea why would my Ado.Net code be slow?
Update: Not sure if execution plans would help but here they are:
It is possible that SQL server has created inappropriate query plan for ADO.NET connections. We have seen similar issues with ADO, usual solution is to clear any query plans and run slow query again - this may create better plan.
To clear query plans most general solution is to update statistics for involved tables. Like next for you:
update statistics documents with fullscan
Do same for other tables involved and then run your slow query from ADO.NET (do not run SSMS before).
Note that such timing inconsistencies may hint of bad query or database design - at least for us that is usually so :)
If you run a query repeatedly in SSMS, the database may re-use a previously created execution plan, and the required data may already be cached in memory.
There are a couple of things I notice in your query:
the CTE joins Users, IGroupes and ITypes, but the joined records are not used in the SELECT
the CTE performs an ORDER BY on a calculated expression (notice the 85% cost in (unindexed) Sort)
probably replacing the CASE expression with a computed persisted column which can be indexed speeds up execution.
note that the ORDER BY is executed on data resulting from joining 4 tables
the WHERE condition of the CTE states AND d.DocumentStatusID = 9, but AND's other DocumentStatusIDs
paging is performed on the result of 8 JOINed tables.
most likely creating an intermediate CTE which filters the first CTE based on peta_rn improves performance
.net by default uses UTF strings, which equates to NVARCHAR as opposed to VARCHAR.
When you are doing a WHERE ID = #foo in dot net, you are likely to be implicitly doing
WHERE CONVERT(ID, NVARCHAR) = #foo
The result is that this where clause can't be indexed, and must be table scanned. The solution is to actually pass each parameter into the SqlCommand as a DbParameter with the DbType set to VARCHAR (in the case of string).
A similar situation could of course occur with Int types if the .net parameter is "wider" than the SQL column equivalent.
PS The easiest way to "prove" this issue is to run your query in SSMS with the following above
DECLARE #p0 INT = 123
DECLARE #p1 NVARCHAR = "foobar" //etc etc
and compare with
DECLARE #p0 INT = 123
DECLARE #p1 VARCHAR = "foobar" //etc etc

Categories