Cannot pass object to Dapper.SimpleCrud insert - c#

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.

Related

Why DapperRow.GetType() return null?

As far as I knew, Object.GetType() should never return null. (related discussion)
Dapper .Query() return private class DapperRow instances to be treated as dynamic objects. I found a strange thing: DapperRow's .GetType() return null.
Here's the sample code to reproduce the problem. Create a C# project, reference Dapper and open a connection to SQL Server (or other database), use .Query() to execute simple select query and retrieve the first row of result. Use GetType() to get the type of result object, the return value is null.
using (SqlConnection cn = new SqlConnection(csSql))
{
var rec = cn.Query("select getdate() as D").Single();
var t = rec.GetType(); // t == null
Console.WriteLine(t.Name); // null reference exception
}
I suspect that dynamic or private type is the cause of null, so I write my class library for test:
namespace Lib
{
public class Blah
{
public static dynamic SecretObject;
static Blah()
{
SecretObject = new PrivateType();
}
}
class PrivateType
{
}
}
In another project, get the dynamic type static field and call GetType():
dynamic obj = Lib.Blah.SecretObject;
Console.WriteLine(obj.GetType().Name); // "Lib.PrivateType"
According to the test result, even cast private type as dynamic, I still can get the private type information from GetType(), why DapperRow.GetType() return null?
DapperRow is specifically built and utilized within Dapper to provide highly optimized row returns without reiterating header information. This is to help condense the size of the object and reduce redundant data, making it more efficient.
However, it would appear that the StackExchange team took the meta programming even further than a first glance would indicate.
DapperRow implements the System.Dynamic.IDynamicMetaObjectProvide interface, which requires that the GetMetaObject method be implemented:
System.Dynamic.DynamicMetaObject System.Dynamic.IDynamicMetaObjectProvider.GetMetaObject(
System.Linq.Expressions.Expression parameter)
{
return new DapperRowMetaObject(parameter,
System.Dynamic.BindingRestrictions.Empty, this);
}
DapperRowMetaObject is a custom implementation of DynamicMetaObject that essentially hijacks and overrides what methods can be invoked against the dynamic type and what those calls should translate to. In this case, calls to anything other than the DapperRow's IDictionary.Item getter or the DapperRow.SetValue will fail since they are always routed to those two calls, but the value will be defaulted to null for any "get" calls where the target property does not exist in the table.
public bool TryGetValue(string name, out object value)
{
var index = table.IndexOfName(name);
if (index < 0)
{ // doesn't exist
value = null;
return false;
}
...
}
At that point, any methods invoked on a null dynamic value will throw a RuntimeBinderException:
RuntimeBinderException: Cannot perform runtime binding on a null
reference
You can easily test this hypothesis by replacing GetType() with another call that will throw the exact same exception:
var rec = cn.Query("select getdate() as D").Single();
var t = rec.AsEnumerable();
Console.WriteLine(t.ToList());
Keep in mind, the underlying type information of any properties on the dynamic object itself can still be accessed directly:
var rec = cn.Query("select getdate() as D").Single();
var t = rec.D.GetType();
Console.WriteLine(t.Name);

c# Asp.net Error :Specified cast is not valid

Here is a portion of a code ..
public Admin_GetCourseById_spResult GetCourseById(long? courseId, short languageId)
{
ISQUserDataContext db = CreateDataContext();
Admin_GetCourseById_spResult result;
result = db.Admin_GetCourseById_sp(courseId,false,languageId).FirstOrDefault();
return result;
}
the third line in the function throws error Specified cast is not valid.
Any clue about whats happening ??
Small rewrite (needs more work)
public Course GetCourseById(long? courseId, short languageId)
{
ISQUserDataContext db = CreateDataContext();
return new Course(db.Admin_GetCourseById_sp(courseId, false, languageId).FirstOrDefault());
}
and then add a class Course with a constructor accepting the returntype of Admin_GetCourseById_sp to build a nice Course object.
Your
db.Admin_GetCourseById_sp(courseId,false,languageId).FirstOrDefault() line returns a datarow.
(It might return null if there are no data)
Therefore what you can do is this,
you can set each attribute of your
Admin_GetCourseById_spResult result object with the matching value from the returned datarow
i.e result.courseId = row["course_id"].ToString()
{ I assumed that courseId is String in your Admin_GetCourseById_spResult class + the matching db column is course_id.
You have given little data, so could answer only like this. Good Luck !

Casting an Object is making it become null

In a call back like this:
this.Model.LoadStudent(student_key, (o, a) =>
{
var student = o as Students;
If I put a break point on first line, debugger shows me that "o" has what I am looking for and also shows me its type which is "Students" but as soon as it goes to next line and does the cast, the result is null. Why? What is happening?
You're not casting - you're using the as operator. If you instead actually cast, I suspect you'll get an exception:
var student = (Students) o;
I suspect you've actually got multiple types called Students, and if you really cast instead of using as you'll see the actual type involved.
For example:
using System;
namespace Bad
{
class Students {}
class Test
{
static void Main()
{
object o = new Good.Students();
Students cast = (Students) o;
}
}
}
namespace Good
{
class Students {}
}
This leads to:
Unhandled Exception: System.InvalidCastException:
Unable to cast object of type 'Good.Students' to type 'Bad.Students'.
at Bad.Test.Main()
Note how you get the fully-qualified type names in the exception.
In general, it's a bad idea to use as unless you really expect that the object may not be of the right type, and usually you check for that afterwards. See my blog post on the topic for more details.
In that case your object isn't of the Students type. Casting in this way won't throw an error if you are casting to the wrong type it will just make the object null.

What causes "extension methods cannot be dynamically dispatched" here?

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.

Using Linq with a DbDataReader

I am trying to make use of the code in this question to implement a query like this:
public void LoadLive(DbConnection pConnection)
{
using (DbDataReader lReader = pConnection.ExecuteReader("..."))
{
mList.AddRange(from t in lReader select new MyObject { Name = t.GetString(0) });
}
}
When I attempt to compile this (with the extension method in place), I receive this error:
error CS1934: Could not find an implementation of the query pattern for source type 'System.Data.Common.DbDataReader'. 'Select' not found. Consider explicitly specifying the type of the range variable 't'.
Am I missing something about how this is supposed to work?
You must call the extension method from the answer in the linked question:
mList.AddRange(from t in lReader.AsEnumerable()
select new MyObject { Name = t.GetString(0) });
Unless you are going to write your own extension method, Select, Where, etc all return an IEnumerable and the typical method signature will resemble Func<DbDataReader, myT>. What you are looking for is something like Jon Skeet's sample here.
Compiler error CS1934 is produced when no standard query operators are implemented for a given datasource.
In your case (DbDataReader), you could specify the type of t as IDataRecord:
mList.AddRange(from IDataRecord t in lReader
select new MyObject { Name = t.GetString(0) });

Categories