Reputation:
I am trying to map a QueryFirst
call to this entity,
public class QueueItem
{
public long Id { get; }
public string Item { get; }
[Column("type_id")]
public int TypeId { get; }
}
I have also set this up for the Column
attribute to work,
Dapper.SqlMapper.SetTypeMap(
typeof(QueueItem),
new Dapper.CustomPropertyTypeMap(
typeof(QueueItem),
(type, columnName) =>
type.GetProperties().FirstOrDefault(prop =>
prop.GetCustomAttributes(false)
.OfType<ColumnAttribute>()
.Any(attr => attr.Name == columnName) || prop.Name == columnName)));
Dapper throws an internal exception,
Unhandled exception. System.ArgumentNullException: Value cannot be null. (Parameter 'meth')
at System.Reflection.Emit.DynamicILGenerator.Emit(OpCode opcode, MethodInfo meth)
at Dapper.SqlMapper.GenerateDeserializerFromMap(Type type, IDataReader reader, Int32 startBound, Int32 length, Boolean returnNullIfFirstMissing, ILGenerator il) in /_/Dapper/SqlMapper.cs:line 3289
at Dapper.SqlMapper.GetTypeDeserializerImpl(Type type, IDataReader reader, Int32 startBound, Int32 length, Boolean returnNullIfFirstMissing) in /_/Dapper/SqlMapper.cs:line 3075
at Dapper.SqlMapper.TypeDeserializerCache.GetReader(IDataReader reader, Int32 startBound, Int32 length, Boolean returnNullIfFirstMissing) in /_/Dapper/SqlMapper.TypeDeserializerCache.cs:line 153
at Dapper.SqlMapper.TypeDeserializerCache.GetReader(Type type, IDataReader reader, Int32 startBound, Int32 length, Boolean returnNullIfFirstMissing) in /_/Dapper/SqlMapper.TypeDeserializerCache.cs:line 50
at Dapper.SqlMapper.GetTypeDeserializer(Type type, IDataReader reader, Int32 startBound, Int32 length, Boolean returnNullIfFirstMissing) in /_/Dapper/SqlMapper.cs:line 3026
at Dapper.SqlMapper.GetDeserializer(Type type, IDataReader reader, Int32 startBound, Int32 length, Boolean returnNullIfFirstMissing) in /_/Dapper/SqlMapper.cs:line 1789
at Dapper.SqlMapper.QueryRowImpl[T](IDbConnection cnn, Row row, CommandDefinition& command, Type effectiveType) in /_/Dapper/SqlMapper.cs:line 1192
at Dapper.SqlMapper.QueryFirst[T](IDbConnection cnn, String sql, Object param, IDbTransaction transaction, Nullable`1 commandTimeout, Nullable`1 commandType) in /_/Dapper/SqlMapper.cs:line 741
I have tried adding Column
attribute for all properties, the outcome is the same.
The fluent mapping approach results in the same exception.
Dapper initialization (with fluent mapping):
FluentMapper.Initialize(config =>
{
config.AddMap(new QueueItemMap());
});
Call to QueryFirst:
public bool TryGetItem(out QueueItem item)
{
const string sql = @"
SELECT *
FROM `queue_items`
WHERE `processed_at` IS NULL AND `completed_at` IS NULL ORDER BY `id` ASC
LIMIT 1;";
using var dbConnection = _dbConnection;
item = dbConnection.QueryFirst<QueueItem>(sql);
return item != null;
}
Upvotes: 0
Views: 2682
Reputation: 74605
I find it a lot easier to just alias the sql columns to match the c# names:
SELECT
Id,
Item,
type_id as TypeId
...
Upvotes: 0
Reputation: 1030
I do not recomend using * on select statements, specially in this type of situation when you want map the result to a typed class.
Dapper is pretty good at mapping queries to classes without necessity of mapping packages, try do this:
Clear all your mapping classes and calls,
Leave the class like this:
public class QueueItem
{
public long Id { get; }
public string Item { get; }
public int TypeId { get; }
}
And change the select statement to this:
SELECT `ID` AS ID,
`ITEM` AS ITEM,
`TYPE_ID` AS TYPEID
FROM `QUEUE_ITEMS`
WHERE `PROCESSED_AT` IS NULL AND `COMPLETED_AT` IS NULL ORDER BY `ID` ASC
LIMIT 1;
*Dapper is case insensitive to mapping, so dont worry about casing
If this dont work, you can try leave the changes as above and instead of doing this
var item = dbConnection.QueryFirst<QueueItem>(sql);
Try doing this and see if the error still occours:
var item = dbConnection.QueryFirst<dynamic>(sql);
As well, by using QueryFirst you have to assume that it aways will find something in the database, otherwise it will throw an exceotion, in your case youre checking for null values, so use QueryFirstOrDefault.
Upvotes: 0
Reputation: 1069
There's two ways to solve this.
Try using Dapper.FluentMap
extension (NuGet) to make this a bit easier.
See medium.com post about it. Summing up information from it, first create a map:
internal class QueueItemMap : EntityMap<QueueItem>
{
internal QueueItemMap()
{
Map(qi => qi.TypeId).ToColumn("type_id");
}
}
Then add it when configuring services:
FluentMapper.Initialize(config =>
{
config.Add(new QueueItemMap());
});
This should auto-map other properties as usual, but "type_id" column will be used for TypeId
member.
If you want to generally use this convention that multiword_column_name
would be mapped to MultiwordColumName
property, use
Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true;
when initializing dapper.
Upvotes: 1