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
Related
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();
public int ExcuteStoreProc(string query, SqlParameter[] Parameters)
{
return _context.Database.ExecuteSqlCommand(query, Parameters);
}
Above attached is my method in my class, now I want to call this method in my services class. So whenever I call it this way:
_colDeptAccessRepository.ExcuteStoreProc("CloneDeptPermissions",
new []
{
new SqlParameter("newDepartment",dept.ID),
new SqlParameter("oldDepartment",deptDto.DeptId),
});
It throws error saying : The SqlParameterCollection only accepts non-null SqlParameter type objects, not SqlParameter objects. Please help me how can I execute a method call.
You need to use Microsoft.Data.SqlClient iso System.Data.SqlClient.
So add this using statement:
using Microsoft.Data.SqlClient;
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.
Compile Error
'System.Data.SqlClient.SqlConnection' has no applicable method named 'Query' but appears to have an extension method by that name. Extension methods cannot be dynamically dispatched. Consider casting the dynamic arguments or calling the extension method without the extension method syntax.
Now, I know how to work around the problem, but I'm trying to get a better understanding of the error itself. I have class that I'm building to leverage Dapper. In the end I'm going to provide some more custom functionality to make our type of data access a lot more streamlined. In particular building in tracing and stuff. However, right now it's as simple as this:
public class Connection : IDisposable
{
private SqlConnection _connection;
public Connection()
{
var connectionString = Convert.ToString(ConfigurationManager.ConnectionStrings["ConnectionString"]);
_connection = new SqlConnection(connectionString);
_connection.Open();
}
public void Dispose()
{
_connection.Close();
_connection.Dispose();
}
public IEnumerable<dynamic> Query(string sql, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null)
{
// this one works fine, without compile error, so I understand how to
// workaround the error
return Dapper.SqlMapper.Query(_connection, sql, param, transaction, buffered, commandTimeout, commandType);
}
public IEnumerable<T> Query<T>(string sql, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null)
{
// this one is failing with the error
return (IEnumerable<T>)_connection.Query(sql, param, transaction, buffered, commandTimeout, commandType);
}
}
but interestingly enough, if I were to simply issue a statement like this:
_connection.Query("SELECT * FROM SomeTable");
it compiles just fine.
So, can somebody please help me understand why leveraging the same overload inside of those other methods is failing with that error?
So, can somebody please help me understand why leveraging the same overload inside of those other methods is failing with that error?
Precisely because you're using a dynamic value (param) as one of the arguments. That means it will use dynamic dispatch... but dynamic dispatch isn't supported for extension methods.
The solution is simple though: just call the static method directly:
return SqlMapper.Query(_connection, sql, param, transaction,
buffered, commandTimeout, commandType);
(That's assuming you really need param to be of type dynamic, of course... as noted in comments, you may well be fine to just change it to object.)
Another solution to the same issue is to apply type casting to the dynamic value.
I encountered the same compile error with:
Url.Asset( "path/" + article.logo );
Which was resolved by doing:
Url.Asset( "path/" + (string) article.logo );
Note: the dynamic value is well-known to be a string, in this case; a fact reinforced by the string concatenation that is present.
I am curious to see if anyone else has run into this same issue...
I am using Dapper as on ORM for a project and was creating some of my own extension methods off of the IDbConnection interface in order to simplify code, where I ran into (what I found to be) puzzling error.
I will walk through the process I went through.
First, I added an extension method to my project in a static class named DbExtensions like so:
using System.Collections.Generic;
using System.Data;
using System.Linq;
public static class DbExtensions
{
public static T Scalar<T>(
this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null)
{
var ret = cnn.Query<T>(sql, param as object, transaction, buffered, commandTimeout, commandType).First();
return ret;
}
}
This creates a compile error with the following description:
'System.Data.IDbConnection' has no applicable method named 'Query' but appears to have an extension method by that name. Extension methods cannot be dynamically dispatched. Consider casting the dynamic arguments or calling the extension method without the extension method syntax.
This is fine, and the error is actually rather helpful as it even tells me how to fix it. So I then try:
using System.Collections.Generic;
using System.Data;
using System.Linq;
public static class DbExtensions
{
public static T Scalar<T>(
this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null)
{
var ret = SqlMapper.Query<T>(cnn, sql, param, transaction, buffered, commandTimeout, commandType).First();
return ret;
}
}
and it compiles correctly. Something strange is going on though. In Visual Studio, if I take the return value of SqlMapper.Query<T> which should be IEnumerable<T>, and I try to operate on it, Visual Studio gives me NO intellisense properties except for those inherited via object.
Thinking I am just doing something that intellisense isn't smart enough to figure out, I go on my merry way... until I actually try to RUN the code.
When I try to run it, it trips up where I am calling .First() with the following error:
'System.Collections.Generic.List<MyNameSpace.MyClass>' does not contain a definition for 'First'
Now THIS error, I thought was interesting... After banging my head for a while, I realized the first argument was complaining about the dynamic typing...
I suppose this error is occurring because the compiler cannot build the Generic Template because it does not know that Query is returning IEnumerable<T> as it is being executed in the DLR? I would love to hear someone explain this who was knowledgeable. I have essentially found two ways to fix it:
Cast the dynamic param to an object
Cast the returned value to an IEnumerable<T>
using System.Collections.Generic;
using System.Data;
using System.Linq;
public static class DbExtensions
{
public static T Scalar<T>(
this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null)
{
var ret = SqlMapper.Query<T>(cnn, sql, param as object, transaction, buffered, commandTimeout, commandType).First();
return ret;
}
}
using System.Collections.Generic;
using System.Data;
using System.Linq;
public static class DbExtensions
{
public static T Scalar2<T>(
this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null)
{
var ret = ((IEnumerable<T>)SqlMapper.Query<T>(cnn, sql, param, transaction, commandTimeout, commandType)).First();
return ret;
}
}
IN SUMMARY:
I am new to working through the qwerks of the DLR and there seem to be some caveats to keep in mind when messing around with dynamic + Generics...?
I know this isn't a question per-se, but when I actually started writing this I didn't know what was going on and I figured it out in the process! I thought it might help someone else with similar issues though...
As suggested, I will try and Answer my question in an actual answer... (Now that it's been 8 hours)
My understanding of the issue is this:
As described in the referenced question, dynamic types do not have extension methods available to them, but extension methods can be used normally (as instance methods), just as they would be without the this keyword...
for instance:
dynamic list = someListObject;
var item = list.First(); //this will not compile
var item = Enumerable.First(list); //this will compile
As Jon Skeet has pointed out in this answer this is all by design and part of the DLR implementation - where if any invocation has a dynamic argument it will have a return type considered dynamic.
For similar reasons, using dynamic variables in extension methods is a bit wonky...
public static Enumerable<T> ExtensionMethod(this ExtendedObject p1, dynamic p2) {
//Do Stuff
}
dynamic y = something;
var x = new ExtendedObject();
//this works
var returnedEnumerable = x.ExtensionMethod(y);
//this doesn't work
var returnedValue = x.ExtensionMethod(y).SomeEnumerableExtensionMethodLikeFirst()
To make the above example work you can do one of the following:
//cast dynamic as object
var returnedValue = x.ExtensionMethod(y as object).First();
//cast returned object
var returnedValue = ((IEnumerable<KnownType>)x.ExtensionMethod(y)).First();