I made a stored procedure in sql server 2008 which gives me the changes made to a table. I am using Linq to SQL to use this table in C#.
my stored procedure is
CREATE PROCEDURE dbo.getlog
-- Add the parameters for the stored procedure here
#p1 int = 0,
#p2 int = 0
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
DECLARE #from_lsn binary(10), #to_lsn binary(10)
SET #from_lsn =
sys.fn_cdc_get_min_lsn('dbo_User_Info')
SET #to_lsn = sys.fn_cdc_get_max_lsn()
SELECT ID_number, Name, Age FROM cdc.fn_cdc_get_all_changes_dbo_User_Info
(#from_lsn, #to_lsn, N'all');
END
GO
The above procedure runs fine in sql server. However when i run this statement using linq in C#
mytestDataContext obj = new mytestDataContext();
var test=obj.ExecuteCommand("dbo.getlog");
foreach( var abc in test)
{}
I get this error
Error 1 foreach statement cannot operate on variables of type 'int'
because 'int' does not contain a public definition for
'GetEnumerator'
ExecuteCommand returns an int.. not your results.
See MSDN here: http://msdn.microsoft.com/en-us/library/system.data.linq.datacontext.executecommand.aspx
public int ExecuteCommand(
string command,
params Object[] parameters
)
I think you're after ExecuteQuery.
ExecuteCommand method returns Int32 and you can't use magical foreach loop using a simple integer.
Return Value
Type: System.Int32
The number of rows modified by the executed command.
I'm not too much familiar with DataContext class but you can use DataContext.ExecuteQuery which returns IEnumerable<TResult> and you can use foreach loop with it.
Return Value
Type: System.Collections.Generic.IEnumerable<TResult>
A collection of objects returned by the query.
I don't know why do you use foreach-statement, but method 'ExecuteCommand' returns int value, and foreach cycle need object that implements IEnumerable
I may be assuming too much, but if you are doing the C# with a recent version of Visual Studio, rather directly specifying the T-SQL call to run the stored procedure as string of literal text, you can drag and drop the stored procedure onto the LINQ to SQL modelling window. This will add it to the LINQ to SQL data context.
int param1 = 1;
int param2 = 2;
mytestDataContext obj = new mytestDataContext();
var test=obj.getlog(param1, param2);
foreach( var abc in test)
{
/* code inside loop */
}
The same technique can be used for calling user-defined functions.
Doing this will reduce typing and provide intellisense to help with calling the stored procedures and SQL functions.
You can use this library:
https://github.com/mrmmins/C-StoreProcedureModelBinding
Returns the values as a List, you only need create a simple class with the names and values types, like:
var productos = DataReaderT.ReadStoredProceadures<MyCustomModel>(myDbEntityInstance, "dbo.MySPName", _generic);
and MyCumtomModel class is something like:
public int id {get; set;}
public int salary {get; set;}
public string name {get; set;}
public string school {get; set;}
and generic like:
List<Generic> _generic = = new List<Generic>
{
new Generic
{
Key = "#phase", Type = SqlDbType.Int, Value = "207"
}
}
};
And now, your products has the options like: products.First(), products.Count(), foreach etc.
Related
I have created a stored procedure that returns a recordset model which joins several different tables in a single database. I have scoured the internet for information regarding "proper syntax for calling a stored procedure from mvc 6 C# without parameters" and have learned several things.
Firstly, there used to be what looked like understandable answers, to wit: "ExecuteSqlCommand " and "ExecuteSqlCommandAsync ", which, evidently are no longer used. Their replacements are explained here: [https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.x/breaking-changes#fromsql][1]. They seem to be limited to "FromSql/FromSqlRaw" (which returns a recordset model) and "ExecuteSqlRaw/ExecuteSqlRawAsync()" which returns an integer with a specified meaning.
The second thing is that, everywhere examples of "before and after" are given, the example without parameters are skipped (as in all of the MS docs).
And thirdly, all of the examples that return a recordset model with data seem tied to a table, such as:
"var students = context.Students.FromSql("GetStudents 'Bill'").ToList();" And, as stored procedures are stored in their own directories, can reference any tables, multiple tables, or even no tables, I don't understand this relationship requirement in calling them.
(such as here:
[https://www.entityframeworktutorial.net/efcore/working-with-stored-procedure-in-ef-core.aspx][2]
var students = context.Students.FromSql("GetStudents 'Bill'").ToList();)
Or maybe they are models (since in this entity framework, everything seems to have the exact same name)... But what if your stored procedure isn't returning a recordset tied to a model. Do you have to create a new model just for the output of this stored procedure? I tried that, and it didn't seem to help.
So, my fundamental question is, how do I call a stored procedure without any parameters that returns a recordset model with data?
return await _context.(what goes here?).ExecuteSqlRaw("EXEC MyStoredProcedure").ToListAsync();
return await _context.ReturnModel.ExecuteSqlRaw("EXEC? MyStoredProcedure").ToListAsync();
Updated Code:
Added Model
public class InquiryQuote
{
public Inquiry inquiry { get; set; }
public int QuoteID { get; set; } = 0;
}
Added DBSet:
public virtual DbSet<InquiryQuote> InquiryQuotes { get; set; } = null!;
And updated the calling controller:
// GET: api/Inquiries
[HttpGet]
public async Task<ActionResult<IEnumerable<InquiryQuote>>> GetInquiries()
{
//return await _context.Inquiries.ToListAsync();
//return await _context.Inquiries.Where(i => i.YNDeleted == false).ToListAsync();
// var IQ = await _context.InquiryQuotes.FromSqlRaw("GetInquiryList").ToListAsync();
var IQ = await _context.InquiryQuotes.FromSqlRaw("EXEC GetInquiryList").ToListAsync();
return Ok(IQ);
}
Both versions of "IQ" return the same results:
System.Data.SqlTypes.SqlNullValueException: Data is Null. This method or property cannot be called on Null values.
at Microsoft.Data.SqlClient.SqlBuffer.ThrowIfNull()
at Microsoft.Data.SqlClient.SqlBuffer.get_Int32()
at Microsoft.Data.SqlClient.SqlDataReader.GetInt32(Int32 i)
at lambda_method17(Closure , QueryContext , DbDataReader , Int32[] )
...
[And here is the image of the stored procedure run directly from my development site:][1]
UPDATE (And partial answer to the question in the comments):
I am using the Entity Framework, and will be performing data manipulation prior to returning the newly created InquiryQuotes model from the stored procedure to be used in several views.
Why I am getting a SQL error thrown in postman (System.Data.SqlTypes.SqlNullValueException: Data is Null. This method or property cannot be called on Null values.) when calling the stored procedure directly from visual studio returns a "dataset" as shown in my image. Does it have something to do with additional values being returned from the stored procedure that are not being accounted for, like "DECLARE #return_value Int / SELECT #return_value as 'Return Value' ", or is this just a feature of executing it from VS. Since it has no input params, where is the NULL coming from?
[1]: https://i.stack.imgur.com/RJhMr.png
I seem to have found the answer (but still don't know the why...)
I started breaking it down bit-by-bit. The procedure ran on sql, and ran remotely on Visual Studio when directly accessing sql, but not when called. So I replaced the complex stored procedure with a simple one that returned all fields from the inquiry table where the id matched an input variable (because I had LOTS) of examples for that.
Stored Procedure:
CREATE PROCEDURE [dbo].[GetInquiry]
#InquiryID int = 0
AS
BEGIN
SET NOCOUNT ON
select i.*
FROM dbo.Inquiries i
WHERE i.YNDeleted = 0 AND i.InquiryId = #InquiryID
END
And the controller method (with the InquiryQuote model modified to eliminate the "quote" requirement:
public async Task<ActionResult<IEnumerable<InquiryQuote>>> GetInquiries()
{
//return await _context.Inquiries.ToListAsync();
//return await _context.Inquiries.Where(i => i.YNDeleted == false).ToListAsync();
SqlParameter ID = new SqlParameter();
ID.Value = 0;
var IQ = _context.InquiryQuotes.FromSqlRaw("GetInquiryList {0}", ID).ToList();
//var IQ = await _context.InquiryQuotes.FromSqlRaw("dbo.GetInquiryList").ToListAsync();
return IQ;
}
And (after a bit of tweaking) it returned a JSON result of the inquiry data for the ID in Postman.
{
"inquiryId": 9,
(snip)
"ynDeleted": false
}
So, once I had something that at least worked, I added just the quote back in to this simple model and ran it again
select i.*, 0 AS Quoteid
FROM dbo.Inquiries i
LEFT JOIN dbo.Quotes q ON i.InquiryId = q.InquiryId
WHERE i.YNDeleted = 0 AND i.InquiryId = #InquiryID
(I set the QuoteID to 0, because I had no data in the Quotes table yet).
AND the Model:
[Keyless]
public class InquiryQuote
{
public Inquiry inquiry { get; set; }
public bool QuoteID{ get; set; } = 0;
}
And ran it again, and the results were astonishing:
{
inquiry:{null},
QuoteID:0
}
I still don't understand why, but, evidently it must have been because of my LEFT join of the inquiryID from the Inquiry Table left joined with a null table returned null results - but when running on SQL, results were returned... The join in sql worked and returned results, but somewhere between sql and the API, the data was being nullified...
To test this theory, I updated my InquiryQuote model to put the "inquiry" data and "quoteid" at the same level, to wit:
public class InquiryQuote
{
public int InquiryId { get; set; } = 0;
(snip)
public Boolean YNDeleted { get; set; } = false;
public int QuoteID { get; set; } = 0;
}
and the entire results set was null...
So at that point, I figured it must have something to do with that LEFT JOIN with a table with no records. So I added a blank (default) entry into that table and, voila, the data I was expecting:
{
"inquiryId": 9,
(snip)
"ynDeleted": false,
"quoteID": 0
}
So, now I have a working way to call a stored procedure with one parameter!!
I then updated the stored procedure to deal with nulls from the database as so:
select i.*, ISNULL(q.QuoteId,0) AS Quoteid
FROM dbo.Inquiries i
LEFT JOIN dbo.Quotes q ON i.InquiryId = q.InquiryId
WHERE i.YNDeleted = 0 AND i.InquiryId = #InquiryID
And now am returning correct data.
I still don't know why the stored procedure runs in sql and returns data, but returns a SQL error when run from the controller. That will require a deeper dive into the interconnectivity between the sql and the API and how errors are passed between the two. And I am pretty certain I will be able to figure out how to convert this call into one that uses no parameters.
Thank you everyone for your help.
Maybe this topic is duplicated from this Array of composite type as stored procedure input passed by C# Npgsql. But that is old one from 2017 and some APIs, properties are deprecated.
Currently, I am trying to pass an array of composite types to the stored procedures. I do map a globally composite type. But there an exception was thrown Can't write CLR type Web.API.Models.UdtSpParameter[] with handler type MappedCompositeHandler`1
I try to google that seems to not find any result to resolve that. The following below that what I create, mapping, calling command stored procedure.
Postgresql
/* Create composite type */
CREATE TYPE udt_sp_parameter AS (
field VARCHAR(50),
value VARCHAR
);
/* Create stored procedure */
CREATE OR REPLACE PROCEDURE stored_example(
parameters udt_sp_parameter[])
LANGUAGE 'plpgsql'
AS $BODY$
DECLARE
_refCursor CURSOR FOR SELECT field, value FROM UNNEST(parameters::udt_sp_parameter[]);
_record udt_sp_parameter;
BEGIN
OPEN _refCursor;
LOOP
FETCH _refCursor INTO _record;
IF NOT FOUND THEN
EXIT;
END IF;
RAISE NOTICE 'Field: %', _record.field;
RAISE NOTICE 'Value: %', _record.value IS NULL;
END LOOP;
CLOSE _refCursor;
END;
$BODY$;
And I try to call the stored by plpgsql language and work well.
DO
$$
DECLARE
parameters udtt_input_param[] := ARRAY[
ROW('YED','Yeti')
,ROW('INTELLIGENT','NOOB')
,ROW('ZXC',NULL)
,ROW('CXX','1')];
BEGIN
CALL stored_example(parameters);
END
$$
LANGUAGE plpgsql
C# Npgsql (nuget Npgsql 4.1.4)
// mapping global type on Startup.cs
NpgsqlConnection.GlobalTypeMapper.MapComposite<UdtSpParameter>("udt_sp_parameter");
// class model UdtSpParameter
public class UdtSpParameter
{
[PgName("field")]
public string Field { get; set; }
[PgName("value")]
public string Value { get; set; }
public UdtSpParameter() { }
}
// call stored procedure at data access layer for example StudentDAL.cs
public IEnumerable<T> CallStoredResultSet<T>(UdtSpParameter[] inputParameters ) where T : class
{
var conn = _GetOpenConnection();
var tran = _BeginTransaction(conn);
NpgsqlCommand command = new NpgsqlCommand("stored_example", conn);
command.CommandType = CommandType.StoredProcedure;
var cmdParam = command.CreateParameter();
cmdParam.ParameterName = "parameters";
cmdParam.DbType = DbType.Object;
cmdParam.Value = inputParameters;
cmdParam.DataTypeName = "udt_sp_parameter";
command.Parameters.Add(cmdParam);
// throw exception here
// Can't write CLR type Web.API.Models.UdtSpParameter[] with handler type MappedCompositeHandler`1
NpgsqlDataReader dr = command.ExecuteReader();
var result = new List<T>();
while (dr.Read())
{
Console.WriteLine(dr[0].ToString(), dr[1].ToString());
}
_CommitTransaction(tran);
_CloseConnection(conn);
return result;
}
Please find some stuff if I do anything wrong and point me to fix that. Thanks in advance.
You have this error because you have specified the type name for the parameter which is the composite type and not an array of composites. Therefore, you should specify udt_sp_parameter[] as the value of DataTypeName instead of udt_sp_parameter:
var cmdParam = command.CreateParameter();
cmdParam.ParameterName = "parameters";
cmdParam.Value = inputParameters;
cmdParam.DataTypeName = "udt_sp_parameter[]";
Since your type is registered and the driver already knows its PostgreSQL name, there is no need to specify it during the parameter setup. Feel free to remove the last line from the code above:
var cmdParam = command.CreateParameter();
cmdParam.ParameterName = "parameters";
cmdParam.Value = inputParameters;
The driver is smart enough to detect types automatically in most cases, but when there is an ambiguity then it should be specified. For example, you have a string which should be passed as JSON, or a CLR type should be serialized to JSON. In other cases just rely on the driver internals and allow him do his work.
The official documentation of Npgsql says:
The only way to call a stored procedure is to write your own CALL
my_proc(...) command, without setting CommandBehavior.StoredProcedure.
In your particular case you should modify your code like this:
NpgsqlCommand command = new NpgsqlCommand("call stored_example(:parameters)", conn);
// comment this line command.CommandType = CommandType.StoredProcedure;
Hope it helps bro.
I have a stored procedure which is not part of my data context.
I am attempting to call it via context.Database.SqlQuery but it is returning no data. However if I run the same query from within SSMS I get the expected data back.
My thinking is maybe I need to add to add the stored procedure to my model?
Secondly, my sproc returns a lot of data, but for testing I have created a small subset class just with one property. It matches one of the columns that the sproc returns. I assume that if the sproc returns columns which are not in my POCO, they will be ignored?
My POCO
public class TestExpotLeads
{
public int LeadID { get; set; } // The stored proc has many more columns, but I am just trying to get one column/property to lineup correctly
}
Populating that model:
var testResult = dataContext.Database.SqlQuery<TestExpotLeads>("[dbo].[sp_ExportLeads] #EventID, #CompanyID",
new SqlParameter("#EventID", eventId),
new SqlParameter("#CompanyID", companyId)).ToList();
testResult is an empty list, but if I run this via SSMS I get data:
exec [dbo].[sp_ExportLeads] #EventID = 1, #CompanyID = 1
Edit:
I tried to log the query to see the generated SQL and I got this:
[dbo].[sp_ExportLeads]
-- #EventID: '1' (Type = Int32, IsNullable = false)
-- #CompanyID: '1' (Type = Int32, IsNullable = false)
It does not look like valid SQL though, but that might just be how the logging outputs 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)
I'm using asp.net to process a SQL query that returns a column from some table. Normally what I'd do is set a variable equal to the stored procedure function call and add .ToArray() at the end, which is what I want to do here but I'm getting an error message int does not contain a definition for toarray...
I'm confused because I followed the same syntax that I used in another part of the program for a similar thing. It worked fine before but I can't figure out why it wants to fight with me now.
Here's my SQL:
IF OBJECT_ID('#temp') IS NOT NULL
BEGIN
DROP TABLE #temp
END
--Create temp table to store data
CREATE TABLE #temp
(
EventID nvarchar(50),
RunDate date,
SectionCode nvarchar(50),
SectionMapCode nvarchar(50),
DispSort int,
Capacity int,
Attendance int,
PctCap int,
HeatColor nvarchar(50)
)
DECLARE #runDate date = GETDATE()
INSERT #temp Exec GamedayReporting.dbo.uspGetEventKillSheetDetailedReport #EventID, #runDate;
select Capacity from #temp;
This returns exactly what I want in SQL but when I call it in my Controller I get the error I posted above.
Here's my C# code:
public ActionResult Dropdown()
{
// add your code after post method is done
var selectedOption = Request["eventId"];
var date = DateTime.Today;
var myQuery = db.uspGetEventKillSheetDetailedReport(selectedOption, date).ToArray();
ViewData["query"] = myQuery;
System.Diagnostics.Debug.WriteLine(myQuery);
TempData["option"] = selectedOption;
return RedirectToAction("Map");
}
public ActionResult Map()
{
var secAttendance = db.uspGetSectionAttendance("option").ToArray();
var secCapacity = db.uspGetSecCapacity("option");
var secMapCode = db.uspGetSectionMapCode("option");
}
public JsonResult GetDropdownList()
{
var ids = db.uspGetAllEventIds().ToArray();
ViewData["ids"] = db.uspGetAllEventIds().ToArray();
return Json(new { data = ids }, JsonRequestBehavior.AllowGet);
}
So Dropdown() and GetDropdownList() work fine, but I'm getting the problem with Map().
Basically I want to take the column returned from my SP and store it into an array but it won't let me. Anybody able to help me work through this?
Update
I changed .ToArray() to .toString().toArray(), which got me past the compiler error, but upon logging it into the console I found it was returning char instead of string. So I changed the whole line to
string secAttendance = new string(db.uspGetSectionAttendance("option").ToString().ToArray());
and output the result into the console and found it returns 0.
0 comes from the Return Value in SQL, which I don't understand. It will fetch the correct column but will not send the correct data to ASP.
Here's a screenshot of the output from my SQL:
The error is correct. There must be only one row, one column value in the output. You are selecting
select Capacity from #temp;
which returns a single value as an int.
It cannot be directly cast into an array.
Instead, if you want an array, you can create a blank
Array<int> a = new Array<int>[1]
and then push this output to that array.