How to convert a datetime string to datetime in SQL Server - c#

From within my C# app I'm calling a stored procedure with a TVP. A couple of columns are datetime. A call to the SP might look like:
declare #p1 dbo.MyTvp
insert into #p1 values('2020-03-19 00:00:01','2020-03-30 23:59:59')
exec MySp #criteria=#p1
The above code is automatically generated in C#. In the SP, the part handling the dates is:
declare #datefrom datetime;
---
SET #sql = CONCAT(#sql, ' AND date_from >= ''', #datefrom, '''');
SQL Server locale is German.
The above throws an error due to conversion from varchar to datetime. However, if the datetime values that I pass are formatted as follows:
declare #p1 dbo.MyTvp
insert into #p1 values('19.03.2020 00:00:01','30.03.2020 23:59:59')
exec MySp #criteria=#p1
The SP works fine.
The class used as a source is:
public class MyCriteria
{
public DateTime DateFrom { get; set; }
}
And the table type is:
CREATE TYPE [dbo].[MyTvp] AS TABLE(
[DateFrom] [datetime] NULL
)
I convert an instance of MyCriteria into a DataTable using an extension method, and then use Dapper to execute the SP:
var criteria = new List<MyCriteria>() { myCriteria }.ToDataTable();
return await conn.QueryAsync<SomeResult>(new CommandDefinition("MySp", new { criteria }, commandType: CommandType.StoredProcedure, cancellationToken: ct));
What I don't understand is at what stage does the conversion from datetime to varchar or DateTime to string occurs.
So how exactly do I need to convert the dates to get the SP to work? Should I do the conversion at the DB level or in my C# app?
EDIT
This is the extension method used to convert a class to a datatable so that it can be passed on as a TVP to the SP:
public static DataTable ToDataTable<T>(this IEnumerable<T> items)
{
var dataTable = new DataTable(typeof(T).Name);
//Get all the properties not marked with Ignore attribute
var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(x => x.GetCustomAttributes(typeof(XmlIgnoreAttribute), false).Length == 0).ToList();
//Set column names as property names
foreach (var property in properties)
{
if (!property.PropertyType.IsEnum && !property.PropertyType.IsNullableEnum())
{
var type = property.PropertyType;
//Check if type is Nullable like int?
if (Nullable.GetUnderlyingType(type) != null)
type = Nullable.GetUnderlyingType(type);
dataTable.Columns.Add(property.Name, type);
}
else dataTable.Columns.Add(property.Name, typeof(int));
}
//Insert property values to datatable rows
foreach (T item in items)
{
var values = new object[properties.Count];
for (int i = 0; i < properties.Count; i++)
{
values[i] = properties[i].GetValue(item, null);
}
dataTable.Rows.Add(values);
}
return dataTable;
}
EDIT 2
The problem is the SQL that is being generated by C#/Dapper which is used to populate the TVP passed to the SP. A simple test can be seen by doing the following:
DECLARE #test TABLE (
[DateCol] datetime NOT NULL
);
INSERT INTO #test VALUES ('2020-02-19 00:00:01'); --doesnt work
INSERT INTO #test VALUES (CONVERT(datetime, '2020-02-19 00:00:01', 120)); --works
The CONVERT function returns the date in the same format as the first INSERT statement. However the first statement doesn't work.

From discussion in the comments, it sounds like a: the data in the TVP is typed (datetime), and b: there is only one row in this case; that's great - it means we can simplify hugely; what we'd want to do here is pull the values from the TVP into locals, and just work with those. Now, based on #datefrom in the example code, it sounds like you've already done the first step, so all we need to do is fix how the dynamic SQL is composed and executed. In the question we have:
SET #sql = CONCAT(#sql, ' AND date_from >= ''', #datefrom, '''');
which is presumably followed later by:
EXEC (#sql);
Instead, we can parameterize the dynamic SQL:
SET #sql = #sql + ' AND date_from >= #datefrom ';
and pass the parameters into our dynamic SQL:
exec sp_executesql #sql, N'#datefrom datetime', #datefrom
The second parameter to sp_executesql gives the definition string for all the actual parameters, which come after it sequentially.
Now the code is entirely safe from SQL injection, and you don't have any string/date conversions to worry about.
Note that the parameter names don't need to be the same in the "inside" and "outside" parts, but they often are (for convenience and maintainability).

Related

How to pass in parameters for in clause that is in a stored procedure?

How would I take this query that is in my stored procedure and pass in the correct parameters?
select * from Inventory
where category in (#categories) and qty > #qty and condition in (#conditions)
I seen that I should do something like this
CREATE PROCEDURE [dbo].[usp_DoSomethingWithTableTypedParameter]
(
#categories categories READONLY
#Qty int,
#conditions
)
But how does the ADO.NET side look like? If I were to pass in for categories 'Tools', 'Hardware' and for conditions 'New', 'Used'.
How would I do this?
To add 3 further parameters to your SP, #Qty, #Category & #Condition you just duplicate the steps you've already taken.
Create any additional User Defined Table Types
Both #Category and #Condition need a UDT, #Qty doesn't as it is a native type.
Some people will prefer having a separate UDT each for #Category and #Condition, personally given they both take the same datatype I create a single general purpose utility UDT e.g.
CREATE TYPE [dbo].[udt_ShortString] AS TABLE
(
[Value] [varchar](128) NULL
)
Modify the SP e.g.
CREATE PROCEDURE [dbo].[usp_DoSomethingWithTableTypedParameter]
(
#UserIdList udt_UserId READONLY
, #CategoryList udt_ShortString READONLY
, #ConditionList udt_ShortString READONLY
, #Qty int
)
AS
Add the values to your command object, where you load the new datatables exactly the same as you are already loading your existing userId table e.g.
cmd.Parameters.Add(new SqlParameter("#UserIdList", System.Data.SqlDbType.Structured) { TypeName = "udt_UserId", Value = userIdList });
cmd.Parameters.Add(new SqlParameter("#CategoryList", System.Data.SqlDbType.Structured) { TypeName = "udt_ShortString", Value = categoryList });
cmd.Parameters.Add(new SqlParameter("#ConditionList", System.Data.SqlDbType.Structured) { TypeName = "udt_ShortString", Value = conditionList });
cmd.Parameters.Add(new SqlParameter("#Qty", SqlDbType.Int) { Value = qty });
Note: For clarity I have hardcoded the parameter names and type names in - you are of course free to pass them as variabled as you were doing.

Dapper stored procedure has too many arguments specified when passing IEnumerable to it

I'm calling my procedure by this method:
public async Task<IEnumerable<Algorithm>> GetAlgorithmsByNameAsync(IEnumerable<string> names)
{
var parameters = new DynamicParameters();
parameters.Add("#names", names);
var connection = _connection.GetOpenConnection();
return await connection.QueryAsync<Algorithm>("GetAlgorithmsByName", parameters, commandType: CommandType.StoredProcedure);
}
My Procedure looks like this:
CREATE TYPE [dbo].[StringList] AS TABLE(
[Item] [NVARCHAR](MAX) NULL
);
--PROCEDURE HERE--
CREATE PROCEDURE GetAlgorithmsByName
#names StringList READONLY -- my own type
AS
BEGIN
SELECT ALgorithmId, Name From Algorithms WHERE Name IN (SELECT Item FROM #names)
END
From the code above, I get an error:
"Procedure or function GetAlgorithmsByName has too many arguments specified."
What am I doing wrong? How do I pass IEnumerable<string> to a stored procedure using dapper?
Table valued parameters aren't trivial to use; one way is via the extension method that Dapper adds on DataTable (something like AsTableValuedParameter), but: it doesn't work as simply as IEnumerable<T> - at least, not today. You also probably don't need DynamicParameters here.
If what you want is just a set of strings, then one very pragmatic option is to look at the inbuilt string_split API in SQL Server, if you can define a separator token that is never used in the data. Then you can just pass a single delimited string.
In your stored procedure is expecting [Item] [NVARCHAR](MAX), it means one item Whereas you are passing IEnumerable<string> names. So that's the reason why you are getting the error.
There are numerous way to pass the list of string to sp
XML
Using table-valued parameters like CREATE TYPE NameList AS TABLE ( Name Varchar(100) );
Using names = "Name1, Name2, .. , Namen"; then sql you can use T-SQL split string to get the name list
Updated
You are passing param incorrectly, Let's fix it by this way
using (var table = new DataTable())
{
table.Columns.Add("Item", typeof(string));
for (int i = 0; i < names.length; i++)
table.Rows.Add(i.ToString());
var pList = new SqlParameter("#names", SqlDbType.Structured);
pList.TypeName = "dbo.StringList";
pList.Value = table;
parameters.Add(pList);
}
You can use the IEnumerable (dynamic) rather than IEnumerable (string).
Check this link and try How to Implement IEnumerable (dynamic)

String was not recognized as a valid DateTime, stored procedure empty parameter

I wrote a stored procedure in SQL Server. I have a parameter of type smalldatetime. I want to send this parameter blank when I run it with LINQ. When I want to send it, I get this error.
String was not recognized as a valid DateTime.
How can I send the date format blank?
C#, LINQ;
var query = ctx.onayListele(Convert.ToDateTime(dataList.olusturulmaTarihi)).ToList();
SQL:
ALTER PROCEDURE [dbo].[onayListele]
#in_olusturmaTarihi smalldatetime = NULL
AS
BEGIN
SELECT
Onay.onayID, alep.olusturulmaTarihi, TalepTuru.talepTuruAdi,
TalepDurumu.talepDurumuAciklamasi
FROM
Onay
WHERE
(#var_olusturmaTarihi IS NULL OR
CONVERT(DATE, Talep.olusturulmaTarihi) = CONVERT(DATE, #var_olusturmaTarihi))
END
At first, I thought you needed to change your stored procedure.
Now that I've read the question again, I've realized that the error message comes from the c# side, not from the stored procedure (that I still think you should change).
Attempting to convert a null or empty string to DateTime will result with the error in your question. To avoid that, you need to make sure the string can in fact be converted to DateTime before sending it to the stored procedure:
DateTime datetime;
DateTime? olusturulmaTarihi = null;
if(DateTime.TryParse(dataList.olusturulmaTarihi, out datetime))
{
olusturulmaTarihi = (DateTime?)datetime;
}
var query = ctx.onayListele(olusturulmaTarihi).ToList();
This way, you will send null to the stored procedure if the string can't be parsed as DateTime, and avoid the error.
As to the stored procedure, I would suggest writing it like this instead:
ALTER PROCEDURE [dbo].[onayListele]
#in_olusturmaTarihi date = NULL
AS
BEGIN
SELECT Onay.onayID,
alep.olusturulmaTarihi,
TalepTuru.talepTuruAdi,
TalepDurumu.talepDurumuAciklamasi
FROM Onay
WHERE #var_olusturmaTarihi IS NULL
OR CONVERT(date,Talep.olusturulmaTarihi) = #var_olusturmaTarihi
END
Please note that if you have an index on Talep.olusturulmaTarihi, this stored procedure will not be able to use it. In that case, you better use something like this instead:
ALTER PROCEDURE [dbo].[onayListele]
#in_olusturmaTarihi date = NULL
AS
BEGIN
SELECT Onay.onayID,
alep.olusturulmaTarihi,
TalepTuru.talepTuruAdi,
TalepDurumu.talepDurumuAciklamasi
FROM Onay
WHERE #var_olusturmaTarihi IS NULL
OR
(
Talep.olusturulmaTarihi >= CAST(#var_olusturmaTarihi as datetime) -- or whatever the data type of the column is
AND Talep.olusturulmaTarihi < DATEADD(DAY, 1, CAST(#var_olusturmaTarihi as datetime)) -- or whatever the data type of the column is
)
END

EF 6.x and function with table-valued parameter

I have a DB function requiring a table-valued parameter as argument (#c).
CREATE TABLE Test
(
CD varchar(10) not null
)
GO
INSERT INTO Test VALUES ('TEST')
GO
CREATE TYPE [CdTable] AS TABLE (CD varchar(10));
GO
CREATE FUNCTION TestTbl ( #x varchar(10), #c CdTable READONLY )
RETURNS TABLE
AS
RETURN
SELECT t.CD
FROM test t
JOIN #c c ON t.CD = c.CD OR c.CD IS NULL
WHERE t.CD = #x
GO
DECLARE #tt AS CdTable;
INSERT INTO #tt VALUES ('TEST');
SELECT * FROM TestTbl('TEST', #tt);
DELETE FROM #tt;
INSERT INTO #tt VALUES (NULL);
SELECT * FROM TestTbl('TEST', #tt);
GO
The function is built from the EF Designer (Database First) as this in the DbContext:
[DbFunction("MyDbContext", "TestTbl")]
public virtual IQueryable<TestTbl_Result> TestTbl(Nullable<System.String> x)
{
var xParameter = user.HasValue ?
new ObjectParameter("x", x) :
new ObjectParameter("x", typeof(System.String));
return ((IObjectContextAdapter)this).ObjectContext.CreateQuery<TestTbl_Result>("[MyDbContext].[TestTbl](#x)", xParameter);
}
If I call this function passing only the available x/#x parameter I get this exception:
ex {"An error occurred while executing the command definition. See the inner exception for details."} System.Exception {System.Data.Entity.Core.EntityCommandExecutionException}
ex.InnerException {"An insufficient number of arguments were supplied for the procedure or function TestTbl."} System.Exception {System.Data.SqlClient.SqlException}
I don't know how to pass the #c parameter to the function. Anyone can help?
Thanks in advance.
p.s.: I am using MS SQL 2012 (or newer)
You should use another method ExecuteStoreQuery that allows specifying table valued parameters (SqlDbType.Structured).
using (var table = new DataTable ())
{
table.Columns.Add("cs", typeof(string));
foreach (var item in ITEMS)
table.Rows.Add(item.CD.ToString());
var param1 = new SqlParameter("#x", SqlDbType.NVarChar)
{
Value = myValue
};
var param2 = new SqlParameter("#c", SqlDbType.Structured)
{
Value = table
};
((IObjectContextAdapter)this).ObjectContext.ExecuteStoreQuery<TestTbl_Result>(
"select * from [TestTbl](#x, #c)", param1, param2);
}

How to take input from SQL stored procedure to a return statement

alter procedure [dbo].[XXX]
(
#vendorworksationID uniqueidentifier ,
#sdate date,
#edate date,
#total int out
)
begin
select #total = COUNT(*)
from AdvertisedCampaignHistory a
where
CAST(a.CreationDate AS DATE) BETWEEN CAST(#sdate as DATE) AND CAST(#edate as DATE)
and a.CampaignID in (select cc.BCampaignID
from BeaconCampaign cc, VendorWorkStation vw
where cc.VendorWorkStationID = vw.VendorWorkStationID
and VendorID = #vendorworksationID)
return #total
end
The above code shows the stored procedure that return an integer value from SQL Server
ObjectParameter Output = new ObjectParameter("total", typeof(Int32));
var resBC = this.Context.getTotalSentBeaconCampaign(VendorWorkstationID, sdate,edate,Output).FirstOrDefault();
The above code shows how I am passing parameters and retrieving the value on the C# side
While running the code I am getting following error
The data reader returned by the store data provider does not have
enough columns for the query requested.
What could be the possible cause for this error?
Entity Framework cannot support Stored Procedure Return scalar values out of the box.To get this to work with Entity Framework, you need to use "Select" instead of "Return" to return back the value.
More Ref : http://www.devtoolshed.com/using-stored-procedures-entity-framework-scalar-return-values

Categories