I have a stored procedure defined in the database and I am trying to call it from the C# server.
In my repository I have a generic method that looks like this:
private IList<T> ExecuteReturningProcedure<T>(string procedureName, params ValueTuple<string, object>[] parameters)
{
List<T> objList = null;
ExecuteCommand(procedureName, parameters, CommandType.StoredProcedure, (cmd) =>
{
using (var reader = cmd.ExecuteReader())
{
objList = MapToList<T>(reader).ToList();
}
});
return objList;
}
and I made the specific method for my call that looks like this:
public List<dynamic> GetDataSubmissionEntries(Guid dataSubmissionId)
{
var parameters = Array.Empty<ValueTuple<string, object>>();
parameters.Append(new ValueTuple<string,object>("#dataSubmissionId", dataSubmissionId));
var entries = ExecuteReturningProcedure<dynamic>("[DataSubmission].[RetrieveDataSubmissionEntries]", parameters);
return entries.ToList();
}
The problem is that when the code is executed I get an exception with the message:
Procedure or function 'RetrieveDataSubmissionEntries' expects parameter '#dataSubmissionId', which was not supplied.
Am I contructing the array of parameters the wrong way?
Try setting the parameters for the SP like this:
SqlCommand cmd_Sp = new SqlCommand("YourSPname", conn);
cmd_Sp.CommandType = CommandType.StoredProcedure;
cmd_Sp.Parameters.Add("#dataSubmissionId", SqlDbType.VarChar).Value = dataSubmissionId.ToString();
Related
In our datalayer, I want to discourage the use of string concatenation and make people use parameters instead.
But as far as I know, there is no way to see if a parameter is a concatenated string.
I have no code example to show of detection, since I know of none.
var result = db.executeQuery("SELECT * FROM table WHERE id = " + id);
This is the kind of code I'd like to get rid of, either to replace with something like:
db.executeQuery($"SELECT * FROM table WHERE id = {id}");
or
db.executeQuery("SELECT * FROM table WHERE id = {0}", id);
Edit:
The command executeQuery is in our datalayer and handles parameters as SqlParameters, with types and values.
So in this case a SqlParameter called #id with type int would be created.
Regarding the FormattableString:
public T ExecuteObject<T>(FormattableString sql)
{
return executeSingleRow(sql.Format, sql.GetArguments()).ToType<T>();
}
Regarding the ExecuteQuery:
public int executeNonQuery(string sql, params object[] parameters)
{
var traceI = Traceadd(sql, parameters);
if (!open())
throw new Exception("Error executing query!", lastException);
try
{
command = Connection.CreateCommand();
command.CommandText = sql;
sql.SQLFormat(ref command, parameters);
var res = command.ExecuteNonQuery();
command.Parameters.Clear();
if (traceI != null)
traceI.Stop();
return res;
}
catch (Exception ex)
{
if (traceI != null)
traceI.Stop();
throw new DBException(command.CommandText, command.Parameters, ex);
}
}
If your executeQuery method only has a parameter of FormattableString, then you should be fine already - there's no conversion from string to FormattableString. For example:
using System;
class Program
{
static void Main(string[] args)
{
int id = 10;
ExecuteQuery("SELECT * FROM table WHERE id = " + id);
}
static void ExecuteQuery(FormattableString query)
{
}
}
That gives an error:
Test.cs(8,22): error CS1503: Argument 1: cannot convert from 'string' to 'System.FormattableString'
You just need to make sure that you don't have an overload of your method accepting string. The result of string concatenation is never a FormattableString. Indeed, I would strongly advise that you avoid ever overloading a method to accept FormattableString and string... there's no point in doing so if you're not going to change the behaviour, and if you are going to change the behaviour, that could be really confusing.
I'd personally consider changing to use an extension method on FormattableString though - something like:
public static SqlCommand ToCommand(
this FormattableString query,
SqlConnection connection)
{
// ...
}
That way you can separate the command creation from the execution... which means (aside from anything else) that you can then call ExecuteReader or ExecuteNonQuery without having any extra code yourself.
I have a project where I have been given a stored procedure that I must use. It is used to create a new record in the database. It returns an Int32 which is the new ID of the record in the table. It also has a temp table where errors are stored and sent back in a second result. So basically the last two lines are
SELECT #NewID
SELECT * From #ErrorsTable
I have to use Entity Framework to call the stored procedure. My problem I can't figure out how to get both return results.
The default When updating from the Database is to just return an Int32. I tried following some examples where they do multiple return results from a stored procedure but I think its not working for me because all the examples return multiple complex types, and I have a mix of a scalar and complex type.
So I guess I ended up solving it by mostly following the code code only option at this link http://msdn.microsoft.com/en-us/data/jj691402.aspx
I added a new method to my Repository that returned called a stored procedure and returned multiple results sets. Something along these lines
Public Object[] StoredProcCall(string storedProc, SqlDbParams sqlParams, type[] types)
{
var cmd = dbContext.Database.Connection.CreateCommand();
cmd.CommandText = storedProc;
cmd.CommandType = CommandType.StoredProcedure;
if(parameters != null)
cmd.Parameters.AddRange(sqlParams);
var reader = cmd.ExecuteReader();
try
{
dbContext.Database.Connection.Open();
object[] mObj = new object[types.Count];
for(int i = 0; i < types.Count; i++)
{
System.Reflection.MethodInfo method = typeof(AutoMapper.Mapper).GetMethod("DynamicMap", new Type[] { typeof(object)});
var generic = method.MakeGenericMethod(types[i]);
objected mappedData = generic.Invoke(this, new object[] { reader});
mObj[i] = mappedData;
if(!reader.NextResult())
break;
}
return mObj;
}
finally
{
dbContext.Database.Connection.Close();
}
}
The only think is that for that to work I had to make a class that that consist of only one property which is my simple type. and every type in the array needs to be IEnumerable which is ok in my case. But That is the basics of what I did and can be changed as needed.
Could I get some help explaining this answer below and how it works with the delegate. Its the answer from here: C# abstraction and database layer
...if you are stuck on the idea of using a DataReader, you could pass a delegate to the helper, which gets invoked inside of the using statements:
public string GetMySpecId(string dataId)
{
return _dbHelper.ExecuteQuery(
dr =>
{
if(dr.Read())
{
return dr[0].ToString();
}
// do whatever makes sense here.
},
#"select ""specId"" from ""MyTable"" where ""dataId"" = :dataId",
new SqlParameter("dataId", dataId));
return result.Rows[0][0].ToString();
}
You could also use a lightweight tool like Dapper to simplify some of the syntax and take care of mapping to your data types. (You'd still need to deal with opening a connection and such.)
Declaring the ExecuteQuery Method from above should look something like this:
public DataTable ExecuteQuery(Func<DataReader, DataTable> delegateMethod, string sqlQuery, SqlParameter param)
{
using (SqlConnection conn = new SqlConnection(this.MyConnectionString))
{
conn.Open();
// Declare the parameter in the query string
using (SqlCommand command = new SqlCommand(sqlQuery, conn))
{
// Now add the parameter to the parameter collection of the command specifying its type.
command.Parameters.Add(param);
command.Prepare();
// Now, add a value to it and later execute the command as usual.
command.Parameters[0].Value = dataId;
using (SqlDataReader dr = command.ExecuteReader())
{
return delegateMethod(dr);
}
}
}
}
That should be right, you may have to swap the DataReader and the DataTable in the Func, I can't remember which comes first the param types or the return type.
Here's another example of using the Func delegate, there's also the Action Delegate if you don't need a return type.
Func Delegate Reading
Normal Delegate Reading
I am missing something obvious here, and I think I need a new perspective.
I am passing a parameter of type object[]. I want to extract the element within the array and assign its value to another variable, as I'm looping through another list:
private void PopulateValues(List<SqlParameters> parameters, object[] parameterValues) {
int index = 0;
foreach(var parameter in parameters) {
parameter.Value = parameterValues[index];
index++;
}
Try as I might, the parameter.Value is set to the object array rather than the object's value at position i.
What am I missing here? Is there a different way to get at the object value inside an object array other than by index/position?
Okay .. here's exactly what I'm doing.
protected SqlCommand CreateStoredProcedureCommand(string storedProcedureName, object[] parameterValues)
{
SqlCommand cmd = new SqlCommand(storedProcedureName, Connection)
{CommandTimeout = _commandTimeout, CommandType = CommandType.StoredProcedure};
SqlCommandBuilder.DeriveParameters(cmd);
int index = 0;
foreach(SqlParameter parameter in cmd.Parameters)
{
if (parameter.Direction == ParameterDirection.Input || parameter.Direction == ParameterDirection.InputOutput)
{
parameter.Value = parameterValues[index]
index++;
}
}
return cmd;
}
It seems like this should work, so I think I'll go back and investigate what I'm actually passing. Maybe I'm passing an array of arrays or doing something else weird.
Found it. I was doing something weird.
I was sending the parameterValues as a list this way:
CreateStoredProcedureCommand("spName", pContext.SqlParameters.Select(b=>b.Value).ToList())
Changed it to ToArray() and it worked.
Given an array of values, I would like to create an anonymous object with properties based on these values. The property names would be simply "pN" where N is the index of the value in the array.
For example, given
object[] values = { 123, "foo" };
I would like to create the anonymous object
new { p0 = 123, p1 = "foo" };
The only way I can think of to do this would be to to use a switch or if chain up to a reasonable number of parameters to support, but I was wondering if there was a more elegant way to do this:
object[] parameterValues = new object[] { 123, "foo" };
dynamic values = null;
switch (parameterValues.Length)
{
case 1:
values = new { p0 = parameterValues[0] };
break;
case 2:
values = new { p0 = parameterValues[0], p1 = parameterValues[1] };
break;
// etc. up to a reasonable # of parameters
}
Background
I have an existing set of methods that execute sql statements against a database. The methods typically take a string for the sql statement and a params object[] for the parameters, if any. The understanding is that if the query uses parameters, they will be named #p0, #p1, #p2, etc..
Example:
public int ExecuteNonQuery(string commandText, CommandType commandType, params object[] parameterValues) { .... }
which would be called like this:
db.ExecuteNonQuery("insert into MyTable(Col1, Col2) values (#p0, #p1)", CommandType.Text, 123, "foo");
Now I would like to use Dapper within this class to wrap and expose Dapper's Query<T> method, and do so in a way that would be consistent with the existing methods, e.g. something like:
public IEnumerable<T> ExecuteQuery<T>(string commandText, CommandType commandType, params object[] parameterValues) { .... }
but Dapper's Query<T> method takes the parameter values in an anonymous object:
var dog = connection.Query<Dog>("select Age = #Age, Id = #Id", new { Age = (int?)null, Id = guid });
leading to my question about creating the anonymous object to pass parameters to Dapper.
Adding code using the DynamicParameter class as requested by #Paolo Tedesco.
string sql = "select * from Account where Id = #p0 and username = #p1";
dynamic values = new DynamicParameter(123, "test");
var accounts = SqlMapper.Query<Account>(connection, sql, values);
throws an exception at line 581 of Dapper's SqlMapper.cs file:
using (var reader = cmd.ExecuteReader())
and the exception is a SqlException:
Must declare the scalar variable "#p0".
and checking the cmd.Parameters property show no parameters configured for the command.
You are misusing Dapper, you should never need to do this, instead either implement IDynamicParameters or use the specific extremely flexible DynamicParameters class.
In particular:
string sql = "select * from Account where Id = #id and username = #name";
var values = new DynamicParameters();
values.Add("id", 1);
values.Add("name", "bob");
var accounts = SqlMapper.Query<Account>(connection, sql, values);
DynamicParameters can take in an anonymous class in the constructor. You can concat DynamicParameters using the AddDynamicParams method.
Further more, there is no strict dependency on anon-types. Dapper will allow for concrete types as params eg:
class Stuff
{
public int Thing { get; set; }
}
...
cnn.Execute("select #Thing", new Stuff{Thing = 1});
Kevin had a similar question: Looking for a fast and easy way to coalesce all properties on a POCO - DynamicParameters works perfectly here as well without any need for magic hoop jumping.
Not exactly an anonymous object, but what about implementing a DynamicObject which returns values for p1 ... pn based on the values in the array? Would that work with Dapper?
Example:
using System;
using System.Dynamic;
using System.Text.RegularExpressions;
class DynamicParameter : DynamicObject {
object[] _p;
public DynamicParameter(params object[] p) {
_p = p;
}
public override bool TryGetMember(GetMemberBinder binder, out object result) {
Match m = Regex.Match(binder.Name, #"^p(\d+)$");
if (m.Success) {
int index = int.Parse(m.Groups[1].Value);
if (index < _p.Length) {
result = _p[index];
return true;
}
}
return base.TryGetMember(binder, out result);
}
}
class Program {
static void Main(string[] args) {
dynamic d1 = new DynamicParameter(123, "test");
Console.WriteLine(d1.p0);
Console.WriteLine(d1.p1);
}
}
You cannot dynamically create anonymous objects. But Dapper should work with dynamic object. For creating the dynamic objects in a nice way, you could use Clay. It enables you to write code like
var person = New.Person();
person["FirstName"] = "Louis";
// person.FirstName now returns "Louis"