SqlCommand timeout even though in SQL Studio same query is fast - c#

I have what I thought was a simple query that I execute from my little log-processing application. The aim of this method is to simply get the highest date value out of the log table:
private DateTime GetLastEntryDate(string serverName, string siteName)
{
DateTime dt = DateTime.MinValue;
using (var con = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["LogParserDB"].ConnectionString))
{
con.Open();
using (var cmd = new SqlCommand("SELECT MAX(date) FROM iislogs WHERE host=#Host AND site=#Site", con))
{
cmd.CommandTimeout = 120;
cmd.Parameters.AddWithValue("Host", serverName);
cmd.Parameters.AddWithValue("Site", siteName);
var result = cmd.ExecuteScalar();
if (result != DBNull.Value)
{
dt = (DateTime)result;
}
}
}
return dt;
}
There are some indexes on the table, but I'm not sure if that's relevant, because the problem I'm getting is that when I run this code, it throws a timeout after 2 minutes on the ExecuteScalar line.
If I copy and paste that query into SSMS with the same parameters, it completes in 00:00:04, or even 00:00:00 (if I've just updated stats).
SELECT MAX(date) FROM iislogs WHERE host='servername' AND site='W3SVC1'
I have checked for blocking, and I can't see anything like that - that database is only being accessed by this one app, and it's not multi-threaded or anything like that.
Update:
Interestingly, when I run the exact query as captured by Profiler, it also takes a long time in SSMS:
exec sp_executesql N'SELECT MAX(date) FROM iislogs WHERE host=#Host AND site=#Site',N'#Host nvarchar(13),#Site nvarchar(6)',#Host=N'servername',#Site=N'W3SVC1'
The actual columns are varchar(50) and varchar(10) respectively.

The query you execute in SSMS
SELECT MAX(date)
FROM iislogs
WHERE host = 'servername'
AND site = 'W3SVC1'
Is not the same as the one executed by your application.
EXEC sp_executesql
N'SELECT MAX(date) FROM iislogs WHERE host=#Host AND site=#Site',
N'#Host nvarchar(13),#Site nvarchar(6)',
#Host=N'servername',
#Site=N'W3SVC1'
The first one has varchar string literals. The second one has nvarchar parameters. Your column datatypes are in fact varchar.
nvarchar has higher datatype precedence than varchar so you are forcing an implicit cast of the column. This will render any indexes on the column useless.
Change the parameter datatypes in the application to varchar

Related

Query from C# code outputting differently from SQL Server query

Working with:
ASP.Net web-forms application
C# code not vb.net
SQL Server with hard coded test data
Note: this issue doesn't cause any errors or cause any disruption in the code, however it outputs differently from expected.
What I am trying to do is populate a Gridview using code behind file, which can be updated by the user on button click.
Code to populate:
protected void PopulateReport()
{
// create connection and add commands
SqlConnection con = new SqlConnection(GetConnectionString());
con.Open();
if(RP_SelectEmp.Text == "ALL")
{
string query1 = "SELECT RequestID, empName, RequestType, RequestDesc, RequestStartDate FROM TOR WHERE (RequestStartDate > #StartDate)" +
" AND (RequestEndDate < #EndDate) AND (granted = #State)";
SqlCommand cmd = new SqlCommand(query1, con);
// needed conversions
DateTime startD = Convert.ToDateTime(RP_FromDateSelect.Text);
DateTime endD = Convert.ToDateTime(RP_EndDateSelect.Text);
Boolean state = Convert.ToBoolean("True");
// needed parameters
cmd.Parameters.AddWithValue("#State", state);
cmd.Parameters.AddWithValue("#StartDate", startD);
cmd.Parameters.AddWithValue("#EndDate", endD);
// import into gridview
using (SqlDataReader reader = cmd.ExecuteReader())
{
if (reader.Read())
{
GridView1.DataSource = reader;
GridView1.DataBind();
}
else
{
RP_ErroField.Text = "failed to bind data (reader not read) check C# code";
}
}
con.Close();
}
}
This compiles and returns no errors but outputs:
The database table includes all the correct data types and column names:
What I have tried:
creating a static data Source and passing in the same select string from the above code (this returns the hard coded event, with the same exact input from the fields seen in the picture) - which tells me the query isn't wrong AddingDataSource,InputingData,Correct event Grabbed
I have tried changing the conversions in the code, DateTime.Parse and Convert.ToDateTime had the same result. Same can be said for bool and Boolean
I have tried the each where clause separately and got the same no data to display result.
I have debugged this if statement for 2 hrs and all the variable data is doing exactly what it should (going to the if, converting, setting the values, running the reader, and databinding)
I don't know what else to try. I would like help on an action plan to fix this; maybe I am missing something, or my approach is wrong/outdated.
This is really just a debugging exercise.
First, double-check that you haven't simply named the two date-picker controls backwards! That happens a lot.
Next: go to SSMS, and take your existing query:
SELECT RequestID, empName, RequestType, RequestDesc, RequestStartDate
FROM TOR
WHERE (RequestStartDate > #StartDate)
AND (RequestEndDate < #EndDate) AND (granted = #State)
Now; we know that you've used Convert.ToDateTime to parse the dates, and that's great. You might want to check the cultures that it is parsing to what you expect it to parse to (is 1/2/2018 the first of Feb? or the 2nd of Jan?), and when you're 100% sure what the actual date of startD and endD are, prepend these to your query using an unambiguous format (just to help us debug); do the same thing with state; for example:
DECLARE #StartDate datetime = '01 Jan 2018';
DECLARE #EndDate datetime = '03 Jan 2018';
DECLARE #State bit = 1;
or are they?
DECLARE #StartDate datetime = '01 Jan 2018';
DECLARE #EndDate datetime = '01 March 2018';
DECLARE #State bit = 1;
So now we have spoofed the parameters and you have the exact same query: run it. 99% of the time, doing this will show you what is wrong with the query. I would expect that the query in SSMS now behaves like the query from your application does. So; now go fix it!

DateTime from .NET to smalldatetime in SQL - how to do queries?

I have a DateTime component in my code, and I want to use it for a query in my SQL Server database.
When inserting this component, there seems to be no problem, but when querying for smalldatetime values, I just don't know how to do it. The dataset is always empty.
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "ReadDates";
dataset = new DataSet();
SqlParameter parameter = new SqlParameter("#date", SqlDbType.SmallDateTime);
parameter.Value = DateTime.Now();
cmd.Parameters.Add(parameter);
dataAdapter = new SqlDataAdapter(cmd);
dataAdapter.Fill(dataset);
return dataset;
And this is in my stored procedure:
select * from TableDates
where ValueDate <= #date
So I have no problems running the procedure in SQL Server Management Studio, when entering a parameter in this format: '2000-03-03 04:05:01', but when passing a DateTime, the query is always empty. Any suggestions?
I tried it by using SQL Server 2008 R2 Express.
Here is the example stored procedure i wrote:
CREATE PROCEDURE [dbo].[ShowGivenSmallDateTimeValue]
#givenSmallDateTime smalldatetime
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Simply return the given small date time value back to sender.
SELECT #givenSmallDateTime
END
And here is the C# code to execute the procedure:
var connectionBuilder = new SqlConnectionStringBuilder();
connectionBuilder.DataSource = "localhost\\sqlexpress";
connectionBuilder.IntegratedSecurity = true;
var now = DateTime.UtcNow;
using (var connection = new SqlConnection(connectionBuilder.ConnectionString))
using (var command = new SqlCommand())
{
command.Connection = connection;
command.CommandType = CommandType.StoredProcedure;
command.CommandText = "ShowGivenSmallDateTimeValue";
command.Parameters.Add(new SqlParameter("#givenSmallDateTime", SqlDbType.SmallDateTime) { Value = now });
connection.Open();
var result = (DateTime)command.ExecuteScalar();
var difference = result - now;
Console.WriteLine("Due to the smalldatetime roundings we have a difference of " + difference + ".");
}
And it simply works.
Here's my code for creating the SqlParameter for a Datetime; For SQL Server 2008 we pass the value as datetime2 since SQL will implicity convert from datetime2 to every other date type so long as it is within the range of the target type...
// Default conversion is now DateTime to datetime2. The ADO.Net default is to use datetime.
// This appears to be a safe change as any datetime parameter will accept a datetime2 so long as the value is within the
// range for a datetime. Hence this code is acceptable for both datetime and datetime2 parameters, whereas datetime is not
// (because it doesn't handle the full range of datetime2).
SqlParameter sqlParam = new SqlParameter(name, SqlDbType.DateTime2);
Since Your parameter includes zeros in day and month parts...sql server converts it but doest match to your date.... i.e.,
if DATETIME.now() returns '2000-03-03 04:05:01'... it is casted into 2000-3-3 Without including zeros...so u need to specify zeros also to match your date.

Transact SQL date query returning no results

From my code, I call an SP using:
using (var cmd = new SqlCommand("sp_getnotes"))
{
cmd.Parameters.Add("#ndate", SqlDbType.SmallDateTime).Value
= Convert.ToDateTime(txtChosenDate.Text);
cmd.CommandType = commandType;
cmd.Connection = conn;
var dSet = new DataSet();
using (var adapter = new SqlDataAdapter { SelectCommand = cmd })
{
adapter.Fill(dSet, "ntable");
}
}
The Stored Procedure itself runs a simple query:
SELECT * FROM tblNotes WHERE DateAdded = #ndate
The problem is no records are returned! DateAdded is a smalldatetime column.
When I change the query to the following, it works:
SELECT * FROM tblNotes WHERE CONVERT(DATETIME, FLOOR(CONVERT(FLOAT, DateAdded))) = #ndate
Why is this happening? This change affects the entire application and I'd like to find the root cause before getting into changing every single query... The only changes we made are to use parameterized queries and upgrade from SQL Server 2005 to 2008.
TIA.
smalldatetime has a time portion which needs to match as well.
Use this:
SELECT *
FROM tblNotes
WHERE dateAdded >= CAST(#ndate AS DATE)
AND dateAdded < DATEADD(day, 1, CAST(#ndate AS DATE))
SQL Server 2008 and above also let you use this:
SELECT *
FROM tblNotes
WHERE CAST(dateAdded AS DATE) = CAST(#ndate AS DATE)
efficiently, with the transformation to a range performed by the optimizer.
SQL Server 2008 now has a DATE data type, which doesn't keep the time porttion like SMALLDATETIME does. If you can't change the data type of the column, then you'll have to truncate when doing the compare, or simply cast to DATE:
SELECT *
FROM tblNotes
WHERE cast(dateAdded as date) = #ndate
I don't know SQL Server but from Oracle experience I'd suspect you're comparing a date time with a date, eg 01/01/2012 01:01:01 against 01/01/2012.

Is it possible to query records obtained from extended stored procedures from TSQL in C#?

So this is the code I have tried, in C#, which failed to give me the result I needed.
SqlCommand comm = new SqlCommand("exec sys.xp_readerrorlog 0,1,'','',#StartDate,#EndDate,N'Desc'");, conn);
comm.Parameters.AddWithValue("#StartDate", "");
comm.Parameters.AddWithValue("#EndDate", "");
SqlDataReader dr = comm.ExecuteReader();
while (dr.Read())
{
Console.WriteLine(dr.GetString(0));
}
Basically, I need to extract data from these logs (which gets pulled from the SQL Server through this stored procedure), and it seems that When I use a dataReader, there are no records, and if I use a dataset with data adapter, there are also no tables/records in the dataset. This information is critical for me to query.
Is there a way that I can still query the SQL Server error logs without having to resort to stored procedures?
ANOTHER UPDATE:
The parameters for this extended stored procedures are:
Value of error log file you want to read: 0 = current, 1 = Archive, 2 = etc...
Log file type: 1 or NULL = error log, 2 = SQL Agent log
Search string 1: String one you want to search for
Search string 2: String two you want to search for to further refine
the results
Search from start time
Search to end time
Sort order for results: N'asc' = ascending, N'desc' = descending
Another method I tried
SqlCommand comm = new SqlCommand(#"exec sys.xp_readerrorlog 0,1,'','',null,null,N'Desc'", conn);
SqlDataAdapter da = new SqlDataAdapter(comm);
DataSet ds = new DataSet();
Console.WriteLine(ds.Tables.Count); //0 returned: no data in dataset
If i was allowed to use stored procedures to query the data, I could have used this following extract, but it would have been deployed too much and be a pain to maintain and decommission
IF (EXISTS( SELECT * FROM sys.procedures where name = 'writelogs' ))
BEGIN
DROP PROCEDURE Writelogs;
END
GO
CREATE PROCEDURE WriteLogs #Servername varchar(40),#InstanceName varchar(40),#Pattern varchar(max),#ParamBeginDate varchar(40), #ParamEndDate varchar(40) AS
BEGIN
DECLARE #BeginDate DateTime
DECLARE #EndDate DateTime
DECLARE #NextQueryID int
--First we have to convert the timestamps EndDate and BeginDate to something usable
IF (#ParamBeginDate = 'Beginning')
BEGIN
SET #BeginDate = null; --null will cause sys.xp_readerrorlog to read from beginning
END
ELSE IF (#ParamBeginDate = 'Last')
BEGIN
SELECT TOP 1 #BeginDate = L.TimeLogged FROM LogTable L ORDER BY L.TimeLogged Desc
END
ELSE
BEGIN
BEGIN TRY
SET #BeginDate = CAST(#ParamBeginDate AS DATETIME);
END TRY
BEGIN CATCH
SET #BeginDate = null;
END CATCH
END
IF (#ParamEndDate = 'Now')
BEGIN
SET #EndDate = GETDATE(); --null will cause sys.xp_readerrorlog to read till now
END
ELSE
BEGIN
BEGIN TRY
SET #EndDate = CAST(#ParamEndDate AS DATETIME);
END TRY
BEGIN CATCH
SET #EndDate = GETDATE();
END CATCH
END
--Temporary Table to store the logs in the format it is originally written in
CREATE TABLE TMP
(LogDate DateTime2
,Processinfo varchar(40)
,[Text] varchar(max))
--truncate the milliseconds (else ALL records will be retrieved)
SET #EndDate= dateadd(millisecond, -datepart(millisecond, #EndDate),#EndDate);
SET #BeginDate= dateadd(millisecond, -datepart(millisecond, #BeginDate),#BeginDate);
INSERT INTO TMP exec sys.xp_readerrorlog 0,1,'','',#BeginDate,#EndDate,N'DESC';
SELECT TOP 1 L.TimeLogged FROM LogTable L ORDER BY L.Timelogged desc
INSERT INTO LogTable
SELECT #Servername,#InstanceName,T.[text],T.LogDate,GETDATE(),0,0,null,#NextQueryID FROM TMP t WHERE PATINDEX(#Pattern,t.[Text]) > 0;
DROP TABLE TMP;
END
You can't use AddWithValue for the dates.
If the dates are blank, then you need to pass null as the value, not an empty string. Those have completely different meanings.
To test, open Management Studio and execute the following:
exec sys.xp_readerrorlog 0,1, '', '', '', ''
That will have zero results. However if you do this:
exec sys.xp_readerrorlog 0,1, '', '', null, null
You will get back a lot of records.
BTW, your update is still wrong. The dataset code you have will never do anything. Change it to:
SqlCommand comm = new SqlCommand(#"exec sys.xp_readerrorlog 0,1,'','',null,null,N'Desc'", conn);
SqlDataAdapter da = new SqlDataAdapter(comm);
DataSet ds = new DataSet();
da.Fill(ds, "sometablename");
Console.WriteLine(ds.Tables.Count); //0 returned: no data in dataset
Note the fill command...

How to return autoincrement value in insert query in SQLite?

In my project I use System.Data.SQLite. Database has table Tags, which contains autoincrement primary field ID (type Integer). When I write:
using (SQLiteCommand command = conn.CreateCommand())
{
command.CommandText = "insert into Tags(name) values(#name) returning into #id";
command.Parameters.Add("#id", DbType.Int32).Direction = ParameterDirection.Output;
command.ExecuteNonQuery();
}
Visual Studio said that the operation is not supported. How to fix it?
Error occurs on line:
command.Parameters.Add("#id", DbType.Int32).Direction = ParameterDirection.Output;
I found working query:
SELECT last_insert_rowid()
SQLite 3.35.0 and newer supports RETURNING clause:
The RETURNING clause is designed to provide the application with the values of columns that are filled in automatically by SQLite.
The code could look like:
INSERT INTO Tags(name) VALUES(#name) RETURNING ID;
I advice to use Stored Procedure.
IN SQL Server has ##IDENTITY system Variable . it returns the last autoincrement value
CREATE PROCEDURE SPGetLastAutoInc #name varchar, #lastAutoResult INT OUTPUT AS
INSERT INTO Tags(name) values(#name)
SET #lastAutoResult = ##IDENTITY
-- Return the number of all items ordered.
RETURN lastAutoResult
GO

Categories