如何将Dapper QueryFirst映射到类?

时间:2020-10-09 20:31:13

标签: c# dapper

我正在尝试将QueryFirst调用映射到该实体,

public class QueueItem
{
    public long Id { get; }
    
    public string Item { get; }
    
    [Column("type_id")]
    public int TypeId { get; }
}

我还为此设置了Column属性,

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抛出内部异常

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

我尝试为所有属性添加Column属性,结果是相同的。

流利的映射方法会导致相同的异常。

Dapper初始化(具有流畅的映射):

FluentMapper.Initialize(config =>
{
    config.AddMap(new QueueItemMap());
});

致电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;
}

3 个答案:

答案 0 :(得分:0)

有两种解决方法。

为一个特定的班级添加地图

尝试使用Dapper.FluentMap扩展名(NuGet)使其变得更容易。

请参见medium.com post about it。总结信息,首先创建地图:

internal class QueueItemMap : EntityMap<QueueItem>
{
    internal QueueItemMap()
    {
        Map(qi => qi.TypeId).ToColumn("type_id");
    }
}

然后在配置服务时将其添加:

FluentMapper.Initialize(config =>
    {
        config.Add(new QueueItemMap());
    });

这应该像往常一样自动映射其他属性,但“ {type_id””列将用于TypeId成员。

为每个成员将PascalCase映射到snake_case

如果您通常希望将multiword_column_name映射到MultiwordColumName属性的约定,请使用

Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true;

初始化dapper时。

答案 1 :(得分:0)

我不建议在select语句上使用*,特别是在您希望将结果映射到类型化的类的情况下。

Dapper非常擅长将查询映射到类,而无需映射包,请尝试执行以下操作:

清除所有映射类和调用,

像这样离开班级:

public class QueueItem
{
    public long Id { get; }
    
    public string Item { get; }
    
    public int TypeId { get; }
}

并将select语句更改为此:

    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对映射不区分大小写,所以不用担心大小写

如果这不起作用,您可以尝试保留上述更改,而不要这样做

var item = dbConnection.QueryFirst<QueueItem>(sql);

尝试执行此操作,看看错误是否仍然存在:

var item = dbConnection.QueryFirst<dynamic>(sql);

同样,通过使用QueryFirst,您必须假设它会在数据库中找到某些东西,否则它将抛出异常,在这种情况下,您将检查空值,因此请使用QueryFirstOrDefault。

答案 2 :(得分:0)

我发现仅对sql列进行别名以匹配c#名称要容易得多

SELECT 
  Id,
  Item,
  type_id as TypeId
...
相关问题