What causes "extension methods cannot be dynamically dispatched" here? - c#

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.

Related

Cannot pass object to Dapper.SimpleCrud insert

I am using Dapper with the Dapper.SimpleCrud extension.
In my Dapper class, I have made an abstraction of the insert method, like this:
public static int Insert(object entity) {
try {
using (SqlConnection sqlConnection = new SqlConnection(connectionString)) {
sqlConnection.Open();
return sqlConnection.Insert(entity) ?? -1;
}
}
catch(Exception ex) {
// log
return -1;
}
}
This lets me call insert and pass an object of any type that is in the db.
The code that currently calls this method looks like this:
DapperORM.Insert(new Menu {
RestaurantID = RestaurantID,
Name = Name});
This throws an error: {"Incorrect syntax near ')'."}
Ok, so now I think there is something wierd with the data I pass in or something. But no. When I change my own Insert method to take a Menu-object instead of a general object, it works.
The Dapper.SimpleCrud Insert overload method obviously can't figure out which object it is. Do you know how to fix it?
Have you had a look at the generated SQL? In stack trace may be? I guess it must be missing name of database table. Yes; I guess. Because I never used SimpleCRUD.
an object of any type that is in the db
How do SimpleCRUD know that the object you send in is "of type that is in the db"?
I think object type parameter is the problem. To accept "an object of any type that is in the db" you should consider converting your method to use generic type instead of object.
When I change my own Insert method to take a Menu-object instead of a general object, it works.
This confirms my earlier diagnosis.
Convert your method to something like below:
public static int Insert<TPoco>(TPoco entity) where TPoco : class
or
public static int Insert<TPoco>(TPoco entity) where TPoco : BasePoco
or similar as per your other code structure.

Dapper strongly typed collection from Type

In my application I use Dapper to access the database.
I've the following example code:
public IEnumerable GetByParentId(Type childType, string table)
{
IDbConnection _connection = _dbProvider.GetConnection();
var _sqlString = "select * from " + table;
IEnumerable _ret = _connection.Query(_sqlString).ToList();
//return IEnumerable<Dapper.SqlMapper.FastExpando>
return _ret;
}
It is possible to cast FastExpando item to my childType or force Dapper to return a strongly typed collection?
I cannot change my method signature!
You can call Query method via reflection and supply generic TItem argument of your childType. Then, Dapper will return IEnumerable and you will be able to cast it.
Also, you could fork Dapper (it is not very big) and make a very simple overload of Query, that will include (Type childType) argument and call proper methods inside.
What you face is the problem of C# in working with generics. Being statically typed language, C# is bad a working with dynamic types. If you want to work dynamically, you always end up with reflection.
Here's a sample of how you could call query method with type arguments. You may have to correct this a bit:
public IEnumerable GetByParentId(Type childType, string table)
{
IDbConnection _connection = _dbProvider.GetConnection();
var _sqlString = "select * from " + table;
var t = typeof(SqlMapper);
var genericQuery = t.GetMethods().Where(x => x.Name == "Query" && x.GetGenericArguments().Length == 1).First(); // You can cache this object.
var concreteQuery = genericQuery.MakeGenericMethod(childType); // you can also keep a dictionary of these, for speed.
var _ret = (IEnumerable)concreteQuery.Invoke(null, new object[] { _connection, _sqlString });
return _ret;
}
Append:
Also, I see a more general design problem here. You want to specify type dynamically, but then want to get statically typed objects, that you will be able to cast (I assume statically, or you want to continue with reflection?). Then... Why do you create dynamic interface in the first place? You say, that you can't change the interface, but this looks a bit stupid. It seems, that all your context is statically typed, but then, for some reason, you have one dynamically typed method.
If you know types during compilation time (or via generic arguments in the runtime), then you should simply change your method to something like this:
public IEnumerable<T> GetByParentId<T>(string table)
{
IDbConnection _connection = _dbProvider.GetConnection();
var _sqlString = "select * from " + table;
var _ret = _connection.Query<T>(_sqlString);
return _ret;
}

InvalidCastException long to ulong

I have the following method:
public static T ExecuteScalar<T>(
string query,
SqlConnection connection,
params SqlParameter[] parameters) where T : new()
{
// Create SqlCommand
SqlCommand command = CreateCommand(query, connection, parameters);
// Execute command using ExecuteScalar
object result = command.ExecuteScalar();
// Return value as expected type
if (result == null || result is DBNull) return default(T);
return (T)result;
}
I want to have the MIN_ACTIVE_ROWVERSION of the database as an ulong. The strange thing is.. the First method call below generates an error but the second method call works fine.
Method call 1 generates an error:
ulong minActiveRowversion =
SqlUtils.ExecuteScalar<ulong>(
"SELECT CAST(MIN_ACTIVE_ROWVERSION() AS BIGINT)"
, _connectionString);
Error:
System.InvalidCastException: Specified cast is not valid.
Method call 2 works fine:
ulong minActiveRowversion =
(ulong)SqlUtils.ExecuteScalar<long>(
"SELECT CAST(MIN_ACTIVE_ROWVERSION() AS BIGINT)"
, _connectionString);
I don't understand how that is possible because the result of the command.ExecuteScalar() method is this:
object result | 1955612
result.GetType() | {Name = "Int64" FullName = "System.Int64"}
Can someone tell me why the first scenario is not possible and the second scenario works?
Can someone tell me how I can solve it so I can use scenario 1.
Why
You can only unbox a value type to it's original type. In your case, the cast first needs to go to long from object and then to ulong.
See this question for more detail:
Why can't I unbox an int as a decimal?
It also links a blog post by Eric Lippert.
How
One way, as you know, is to cast to the original type before casting to T - unless, of course, the original type is T.
As mentioned in the comments, another way is to use conversion routines (Convert.ToUInt64) and not explicit casting.
This could potentially be achieved using a Func<object, T>:
public static T ExecuteScalar<T>(
Func<object, T> conversionFunctor,
string query,
SqlConnection connection,
params SqlParameter[] parameters) where T : new()
{
// Create SqlCommand
SqlCommand command = CreateCommand(query, connection, parameters);
// Execute command using ExecuteScalar
object result = command.ExecuteScalar();
// Return value as expected type
if (result == null || result is DBNull)
return default(T);
return conversionFunctor(result);
}
Making your call:
ulong minActiveRowversion =
SqlUtils.ExecuteScalar<ulong>(
Convert.ToUInt64,
"SELECT CAST(MIN_ACTIVE_ROWVERSION() AS BIGINT)"
, _connectionString);
Adam's answer correctly identifies the problem; here is a solution: you can use LINQ to unbox any type, as long as it can be cast to T with a built-in or a custom conversion.
static T UnboxUnchecked<T>(object obj) {
var pe = Expression.Parameter(typeof(object));
return Expression.Lambda<Func<object,T>>(
Expression.Convert(
Expression.Convert(pe, obj.GetType())
, typeof (T)
)
, pe
).Compile()(obj);
}
This method produces a LINQ expression that first unboxes the object to its actual type, and then applies the conversion. Replace the last line of your method
return (T)result;
with
return UnboxUnchecked<T>(result);
to make it work.
Here is a link to an article that explains how to make conversions of this kind more efficient by caching the compiled lambdas.

Cannot call extension methods with dynamic params and generics

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();

C#: Convert String to DBType.AnsiStringFixedLength

I have a stored procedure. One of its input parameters is expecting a char(8). I try to convert a string "AAA" to this particular parameter type, which is a DBType.AnsiStringFixedLength.
object v = Convert.ChangeType("AAA", param.DbType.GetTypeCode());
// param is AnsiStringFixedLength
However, all I get is an exception: Input string was not in a correct format.
And the stack trace says: at System.Number.StringToNumber(String str, NumberStyles options, NumberBuffer& number, NumberFormatInfo info, Boolean parseDecimal) [...]
Why is System.Convert trying to convert a string into a number, even though the prodecure's parameter is expecting a char(8)? How do I solve this? I don't want to use one huge switch case mapping all SQL types to CLR types...
EDIT:
This is the code in question: (A generic method to call any MS SQL stored procedure)
using (SqlConnection conn = new SqlConnection(this.config.ConnectionString))
{
using (SqlCommand cmd = conn.CreateCommand())
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = this.config.StoredProcedureName;
conn.Open();
SqlCommandBuilder.DeriveParameters(cmd);
foreach (SqlParameter param in cmd.Parameters)
{
if (param.Direction == ParameterDirection.Input ||
param.Direction == ParameterDirection.InputOutput)
{
try
{
string rawParam = param.ParameterName.Replace("#", "");
if (this.config.Parameters.ContainsKey(rawParam))
{
try
{
param.Value = Convert.ChangeType(this.config.Parameters[rawParam],
param.DbType.GetTypeCode());
}
catch(Exception oops)
{
throw new Exception(string.Format("Could not convert to '{0}'.", param.DbType), oops);
}
}
else
throw new ArgumentException("parameter's not available");
}
catch (Exception e)
{
throw;
}
}
}
cmd.ExecuteNonQuery();
}
}
The actual parameter values are provided by this.config.Parameters - all of them are strings. I iterate through SqlCommand's parameter list and set them accordingly. Converting the string values to the parameter's Sql type is necessary here, and as far as I can see, the Sql type is provided by param.DBType.
You seem to mix up some things here, or I don't get what you try to do. The DbType (an enumeration) inherits Enum and that implements IConvertible -> You can call GetTypeCode(). But - you are now calling Enum.GetTypeCode(), which returns the underlying type. If you didn't specify it (and DbType didn't) any Enum is backed by an int.
What are you trying to solve with the code anyway? Why would you want to change the type of a string if the parameter is a string (although with a fixed length)?
Looking at the question some more it seems even more odd. You have an object v (probably for value?) - what do you care about the type?
object v1 = "Foo";
object v1 = 42;
What is the difference for you? I guess you want to pass the values to something else, but - if you only reference the value as object you might still need to cast it.
Please update your question and explain what you really want to do, what you expect to gain.
Regarding the comment:
I'm using Convert.ChangeType(object
value, TypeCode typeCode), so it's not
really converting into an Enum/int. At
least that's what I thought...
See above: DbType.GetTypeCode() is not what you want. Try it, give me the benefit of the doubt: What do you expect to get from DbType.AnsiStringFixedLength.GetTypeCode()? What is the actual result, if you try it?
Now to your code: You try to set the SqlParameter.Value property to the "correct" type. Two things: According to the documentation you probably want to set the SqlParameter.SqlValue, which is the value using SQL types according to the docs. SqlParameter.Value, on the other hand, is the value using CLR types and allows to infer both DbType and SqlValue. Sidenote, implementation detail: The SqlParameter.SqlValue setter just calls the setter of SqlParameter.Value again...
I would expect that the ADO.NET stuff converts the value on its own, if at all possible. What error are you getting without jumping through this hoops?

Categories