Using Dapper (C#) to call PostgreSQL stored procedure - c#

I'm posting this for two reasons
I'm new to PostgreSQL and it took a while to piece this information together, so I thought someone else would find this helpful and
to ask if there is another way to call a PostgreSQL stored procedure that doesn't require all of the parameters to be included in the stored procedure name/signature.
The following uses Dapper and Npgsql to make a call to a PostgreSQL stored procedure that inserts (null id_inout passed in) or updates a record (id_inout has a value).
I'd like to understand why PostgreSQL requires the entire stored procedure signature when making the call.
public static int? PO_Save(PurchaseOrder po)
{
int? recordId = null;
using (var cn = new NpgsqlConnection(AppSettings.ConnectionString))
{
if (cn.State != ConnectionState.Open)
cn.Open();
var procName = "CALL po_save(#in_ponumber,#in_deliverydate,#in_bldnum," +
"#in_facname,#in_facnumber,#in_facaddress1,#in_facaddress2,#in_city," +
"#in_state,#in_zip,#in_theme,#id_inout)";
var p = new Dapper.DynamicParameters();
p.Add("#in_ponumber", po.PONumber);
p.Add("#in_deliverydate", po.DeliveryDate);
p.Add("#in_bldnum", po.BldNum);
p.Add("#in_facname", po.FacName);
p.Add("#in_facnumber", po.FacNumber);
p.Add("#in_facaddress1", po.FacAddress1);
p.Add("#in_facaddress2", po.FacAddress2);
p.Add("#in_city", po.City);
p.Add("#in_state", po.State);
p.Add("#in_zip", po.Zip);
p.Add("#in_theme", po.Theme);
p.Add("#id_out", po.POID, null, ParameterDirection.InputOutput);
var res = cn.Execute(procName, p);
recordId = p.Get<int>("#id_inout");
}
return recordId;
}

You should be able to pass commandType: CommandType.StoredProcedure to Execute, e.g:
var res = cn.Execute(
"po_save",
new {
in_ponumber = po.PONumber,
in_deliverydate = po.DeliveryDate,
// etc...
},
commandType: CommandType.StoredProcedure,
);
Here's the docs with such an example: https://github.com/StackExchange/Dapper/blob/main/Readme.md#stored-procedures

I wanted to find an answer to this myself, and beginning with Npgsql 7.0, CommandType.StoredProcedure will now invoke stored procedures and not functions as before.

Related

Using Dapper with SQL Server

I am currently developing an application using C# (Visual Studio 2019)and SQL Server 2017 using Dapper. Below is a routine that works fine right now to execute a stored procedure in SQL Server.
The C# code uses the MVVM framework, and I currently have 60 model classes to map the tables in the SQL Server database. There are about 120 stored procedures that produce results that are mapped to the model classes using Dapper.
Consider the following code snippet as pseudocode (but it actually works to execute the stored procedure and return the correct results).
public List<SomeDefinedModel> ExecuteSQLSPROC(string SPROCName, SqlParameter[] parameters)
{
using (IDbConnection connection = new System.Data.SqlClient.SqlConnection(DBHelper.CNNVal("MYLocalServer")))
{
System.Data.SqlClient.SqlCommand command = new System.Data.SqlClient.SqlCommand();
command.Connection = (SqlConnection)connection;
command.CommandText = SPROCName;
DynamicParameters parms = new DynamicParameters();
for (int i = 0; i < parameters.Length; i++)
{
var parmname = parameters[i].ParameterName;
var parmvalue = parameters[i].Value;
parms.Add(parmname, parmvalue);
}
var output = connection.Query<SomeDefinedModel>(SPROCName, parms, commandType: CommandType.StoredProcedure).ToList();
return output;
}
}
I want to change this routine so the routine so that it method signature is as follows:
public List<TheNameOfTheModelToMap> ExecuteSQLSPROC(string SPROCName, SqlParameter[] parameters, TheNameOfTheModelToMap)
That is, the designation of the model to which Dapper is to map is a variable - it can be any valid model. I have define the variable TheNameOfTheModelToMap as type object, and the return value as var, as List<object>, and so forth. They produce errors because of this syntax:
var output = connection.Query<TheNameOfTheModelToMap>(SPROCName, parms, commandType: CommandType.StoredProcedure).ToList();
I want to comply with the principles of DRY and code just one method to accept ANY model designation, ANY stored procedure designation and have Dapper map the query results. Any possibilities?
I think you just need one generic method like so:
public List<T> ExecuteSQLSPROC<T>(string SPROCName, SqlParameter[] parameters)
{
using (var connection = new System.Data.SqlClient.SqlConnection(DBHelper.CNNVal("MYLocalServer")))
{
var parms = new DynamicParameters();
foreach (var parameter in parameters)
parms.Add(parameter.ParameterName, parameter.Value);
var output = connection.Query<T>(
SPROCName,
parms,
commandType: CommandType.StoredProcedure
)
.ToList();
return output;
}
}
And every other method becomes a specialized invoke of it:
public List<SomeDefinedModel> ExecuteOneProc(SqlParameter[] parameters)
=> ExecuteSQLSPROC<SomeDefinedModel>("OneProcName", parameters);
Is this ok for your use case?
If you need one procedure for single values (which would not use Dapper's Query method) you would just create specialized variations of the ExecuteSQLSPROC like ExecuteStoredProcSingleRow or something along those lines.

Is it possible to use a string for an where clause in a select statement [duplicate]

This question already has answers here:
Pass Array Parameter in SqlCommand
(11 answers)
Closed 6 years ago.
For some reason the Sqlparameter for my IN() clause is not working. The code compiles fine, and the query works if I substitute the parameter with the actual values
StringBuilder sb = new StringBuilder();
foreach (User user in UserList)
{
sb.Append(user.UserId + ",");
}
string userIds = sb.ToString();
userIds = userIds.TrimEnd(new char[] { ',' });
SELECT userId, username
FROM Users
WHERE userId IN (#UserIds)
You have to create one parameter for each value that you want in the IN clause.
The SQL needs to look like this:
SELECT userId, username
FROM Users
WHERE userId IN (#UserId1, #UserId2, #UserId3, ...)
So you need to create the parameters and the IN clause in the foreach loop.
Something like this (out of my head, untested):
StringBuilder sb = new StringBuilder();
int i = 1;
foreach (User user in UserList)
{
// IN clause
sb.Append("#UserId" + i.ToString() + ",");
// parameter
YourCommand.Parameters.AddWithValue("#UserId" + i.ToString(), user.UserId);
i++;
}
Possible "cleaner" version:
StringBuilder B = new StringBuilder();
for (int i = 0; i < UserList.Count; i++)
YourCommand.Parameters.AddWithValue($"#UserId{i}", UserList[i].UserId);
B.Append(String.Join(",", YourCommand.Parameters.Select(x => x.Name)));
If you are using SQL 2008, you can create a stored procedure which accepts a Table Valued Parameter (TVP) and use ADO.net to execute the stored procedure and pass a datatable to it:
First, you need to create the Type in SQL server:
CREATE TYPE [dbo].[udt_UserId] AS TABLE(
[UserId] [int] NULL
)
Then, you need to write a stored procedure which accepts this type as a parameter:
CREATE PROCEDURE [dbo].[usp_DoSomethingWithTableTypedParameter]
(
#UserIdList udt_UserId READONLY
)
AS
BEGIN
SELECT userId, username
FROM Users
WHERE userId IN (SELECT UserId FROM #UserIDList)
END
Now from .net, you cannot use LINQ since it does not support Table Valued Parameters yet; so you have to write a function which does plain old ADO.net, takes a DataTable, and passes it to the stored procedure: I've written a generic function I use which can do this for any stored procedure as long as it takes just the one table-typed parameter, regardless of what it is;
public static int ExecStoredProcWithTVP(DbConnection connection, string storedProcedureName, string tableName, string tableTypeName, DataTable dt)
{
using (SqlConnection conn = new SqlConnection(connection.ConnectionString))
{
SqlCommand cmd = new SqlCommand(storedProcedureName, conn);
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter p = cmd.Parameters.AddWithValue(tableName, dt);
p.SqlDbType = SqlDbType.Structured;
p.TypeName = tableTypeName;
conn.Open();
int rowsAffected = cmd.ExecuteNonQuery(); // or could execute reader and pass a Func<T> to perform action on the datareader;
conn.Close();
return rowsAffected;
}
}
Then you can write DAL functions which use this utility function with actual names of stored procedures; to build on the example in your question, here is what the code would look like:
public int usp_DoSomethingWithTableTypedParameter(List<UserID> userIdList)
{
DataTable dt = new DataTable();
dt.Columns.Add("UserId", typeof(int));
foreach (var userId in updateList)
{
dt.Rows.Add(new object[] { userId });
}
int rowsAffected = ExecStoredProcWithTVP(Connection, "usp_DoSomethingWithTableTypedParameter", "#UserIdList", "udt_UserId", dt);
return rowsAffected;
}
Note the "connection" parameter above - I actually use this type of function in a partial DataContext class to extend LINQ DataContext with my TVP functionality, and still use the (using var context = new MyDataContext()) syntax with these methods.
This will only work if you are using SQL Server 2008 - hopefully you are and if not, this could be a great reason to upgrade! Of course in most cases and large production environments this is not that easy, but FWIW I think this is the best way of doing this if you have the technology available.
SQL Server sees your IN clause as:
IN ('a,b,c')
What it needs to look like is:
IN ('a','b','c')
There is a better way to do what you're trying to do.
If the user id's are in the DB, then the IN clause should be changed to a subquery, like so:
IN (SELECT UserID FROM someTable WHERE someConditions)
This is a hack -- it doesn't work well with indexes, and you have to be careful it works right with your data, but I've used it successfully in the past:
#UserIDs LIKE '%,' + UserID + ',%' -- also requires #UserID to begin and end with a comma

Pass table-valued parameter to a stored procedure

In my project we are using Dapper to connect with database for operations.
I have created a stored procedure in my SQL Server database as:
CREATE PROCEDURE dbo.usp_Check
#TestTableType [dbo].[TestTableType] READONLY
AS
BEGIN
SELECT
TestTableType.BrokerNPN,
CASE WHEN Sales.BrokerNPN IS NULL THEN 0 ELSE 1 END as isBrokerNPNExist
FROM
[dbo].[tbl_Sales] Sales
LEFT JOIN
#TestTableType TestTableType ON Sales.BrokerNPN = TestTableType.BrokerNPN
END
And in C# I am trying to consume my stored procedure with a TVP like this:
public void CheckSP(List<string> ParamData)
{
using (var connection = new ConnectionProvider("DbName").GetOpenConnection())
{
var dt = new DataTable("dbo.TestTableType");
dt.Columns.Add("NPN", typeof(string));
dt.Rows.Add("12345");
// First attempt
var result = connection.Query<CheckData>("usp_Check", new { BrokerNPN = ParamData }, CommandType.StoredProcedure).ToList();
// Second attempt
var result = connection.Query<CheckData>("usp_Check", new { BrokerNPN = dt}, CommandType.StoredProcedure).ToList();
}
}
But I am not able to pass the TVP to the stored procedure.
For the first attempt, I am getting this error
Procedure or function CheckBrokerNPNExist has too many arguments specified
And for the second attempt, I cannot use pass DataTable directly.
Use the same column name in [dbo].[TestTableType] in c# code
I think you're just using a wrong name for the parameter...
See your stored procedure definition:
CREATE PROCEDURE dbo.usp_Check
#TestTableType [dbo].[TestTableType] READONLY
Your parameter is called #TestTableType - yet when you call this stored procedure from C#, you're using a different name:
var result = connection
.Query<CheckData>("usp_Check",
new { BrokerNPN = ParamData },
CommandType.StoredProcedure).ToList();
I would try to use the same name:
var result = connection
.Query<CheckData>("usp_Check",
new { TestTableType = ParamData },
CommandType.StoredProcedure).ToList();

How to call an existing stored procedure from Entity Framework

I am writing an app using .net Web Api, Entity Framework and PostgreSql and I am having trouble calling a stored procedure. I've tried to go off of this question: using stored procedure in entity framework but it doesn't seem to help so I'll ask for my specific case and hope I can get help.
I have written a stored procedure in postgres and all I want to do is call it from my repository. What is the best way to go about doing this? If I want to call it though a DbContext object do I need to tell the DbContext that the stored procedure exists before I call it? Would it just be better to use ado.Net to use sql directly? This is what I tried and it gives me a syntax error:
var serverName = new PgSqlParameter("server_name", server);
var dt = new PgSqlParameter("data_tag", dataTag);
var timestamp = new PgSqlParameter("ts", DateTime.Now);
var val = new PgSqlParameter("val", value);
if (context.Database.Connection.State == ConnectionState.Closed)
context.Database.Connection.Open();
try
{
context.Database.ExecuteSqlCommand(#"EXEC [dbo].[historical_tag_values_insert]", serverName, dt, timestamp, val);
}
finally
{
if (context.Database.Connection.State == ConnectionState.Open)
context.Database.Connection.Close();
}

C# SQL execute stored procudere regardless of passed parameter name [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
SQL Server & .net support calling a stored procedure with param's values wihout providing param's names?
I want to call a command.ExecuteReader() of type Stored Procedure, however I do not want the parameter names that I pass to be identical to the ones in the SP. Below is a sample of what I'm trying to do
SP:
ALTER PROCEDURE SPName
#Id nvarchar(50)
AS
BEGIN
SET NOCOUNT ON;
SELECT * FROM TableName
WHERE ColumnName = #Id
END
GO
Code:
using (SqlCommand command = new SqlCommand(spName, connection) { CommandType = CommandType.StoredProcedure })
{
command.parameters.Add(new SqlParameter(*paramaeter name*, sqlDbType.nvarchar){ Value = "SomeValue"};
}
If you want a generic style function, without needing an extra round-trip, and you're happy to use reflection you could use something like this.
// Return an array of SqlParameter's by using reflection on ParamObject
private static SqlParameter[] GetParametersFromObject( object ParamObject )
{
var Params = new List<SqlParameter>();
foreach( var PropInfo in ParamObject.GetType().GetProperties() )
{
Params.Add( new SqlParameter( PropInfo.Name, PropInfo.GetValue( ParamObject, null ) ) );
}
return Params.ToArray();
}
public static void ExecuteSP( SqlConnection Connection, string SPName, object ParamObject )
{
using( var Command = new SqlCommand() )
{
Command.Connection = Connection;
Command.CommandType = CommandType.StoredProcedure;
Command.CommandText = SPName;
Command.Parameters.AddRange( GetParametersFromObject( ParamObject ) );
// Command.ExecuteReader()...
}
}
This uses reflection to get the property names and values out of the anonymous object to populate the SqlCommand. This can be used as such;
ExecuteSP( Conn, "GetStuff", new { Id = 7, Name = "Test" } );
This way ExecuteSP is 'generic' and you pick the parameter names and values when you call ExecuteSP.
Simple fact - you ultimately have to use the correct parameter name when calling a stored procedure because SQL server binds parameters by name (even when you use EXEC to call an SP without using named parameters, the parser binds them by name from left to right).
So if you want to use a different name you will need to introduce an intermediate layer between your SqlCommand and the target SP.
But if you just want to not care about the name and have it automatically discovered - then you can use the technique mentioned by Conrad Frix in his accepted answer on SQL Server & .net support calling a stored procedure with param's values wihout providing param's names? - which is why I've marked as a duplicate, because it is ultimately what you want to do, even if the reasons are different.
For SqlServer there is a DeriveParameters method that takes a command object and queries the database for the parameters (names and types) of the requested stored procedure.
You can then iterate over them and supply values.
Note: this means an extra trip to the database, so if you need this often, you might want to cache the results.
The method below allows you to write generic code for calling stored procedures, but also gives you the flexibility to perform specific actions for each different stored procedure
public delegate void SqlCOmmandDelegate(SqlCommand command);
public class Dal
{
public void ExecuteStoredProcedure(string procedureName,
SqlCommandDelgate commandDelegate)
{
using (SqlConnection connection = new SqlConnection())
{
connection.ConnectionString = GetConnectionString();
using (SqlCommand command = connection.CreateCommand())
{
command.CommandType = CommandType.StoredProcedure;
command.CommandText = procedureName;
connection.Open();
commandDelegate(command);
}
}
}
}
class UsesDal
{
public CallFirstProcedure(int value)
{
string userName;
ExecuteStoredProcedure("FIRST_PROCEDURE",
delegate(SqlCommand command)
{
command.Parameters.Add("UserID", value);
command.ExecuteReader();
//Do stuff with results e.g.
username = command.Parameters.Parameters["UserName"].ToString();
}
}
public CallOtherProcedure(string value)
{
int id;
ExecuteStoredProcedure("OTHER_PROCEDURE",
delegate(SqlCommand command)
{
command.Parameters.Add("ParameterName", value);
id = command.ExecuteScalar();
}
}
}

Categories

Resources