使用 Dapper Extensions 时出现消息 'No columns were mapped' 的异常

Exception with message 'No columns were mapped' using Dapper Extensions

我有一个 class 实现了一些 interface:

public interface IDb {}
public class DbModel : IDb {}

在此之后,我使用 Dapper Extensions 将对象插入到数据库中。此代码运行良好:

var obj = new DbModel();
sqlConnection.Insert(obj); 

但是当我尝试插入这个 class 的实例时,在相应的 interface 上进行转换时,它给出了一个异常:

IDb obj = new DbModel();
sqlConnection.Insert(obj); // exception here

System.ArgumentException: 'No columns were mapped.'

不工作的原因:

DapperExtensions.Insert方法是通用的这一事实出发,DapperExstensions无法为IDb

找到对应的映射

解法:

IDb 创建映射器 class 并注册 DbModel 属性

public sealed class IDbMapper : ClassMapper<IDb>
{
    public IDbMapper()
    {
        base.Table("TableName");
        Map(m => new DbModel().Title);
        // and such mapping for other properties
    }
}

在此之后它将起作用:

IDb obj = new DbModel();
sqlConnection.Insert(obj);

But here is a problem, when we have many implementations of IDb interface, in IDbMapper config we can't map columns to corresponding tables here:

base.Table("TableName");
Map(m => new DbModel().Title);

Because we don't know type of object instance when we do mapping.

编辑:

调试后发现,在每次Insert方法调用中,dapper都会做映射,并构造对应的ClassMapper<>class。我们可以利用这个怪癖。

为此,我们应该在调用 Insert 方法之前创建 SharedState 并在其中存储实例类型。

public static class DapperExstensionsExstensions
{
    public static Type SharedState_ModelInstanceType { get; set; }
    ...
}

IDb obj = new DbModel();
DapperExstensionsExstensions.SharedState_ModelInstanceType = obj.GetType();
sqlConnection.Insert(obj);

之后我们可以访问这个 属性 当 Dapper 将进行映射时

public sealed class IDbMapper : ClassMapper<IDb>
{
    public IDbMapper()
    {
         // here we can retrieve Type of model instance and do mapping using reflection
         DapperExstensionsExstensions.SharedState_ModelInstanceType
    }
}

整个代码片段:

public interface IDb { }

[MapConfig("TableName", "Schema")]
public class DbTemp : IDb
{
    public string Title { get; set; }
}

public class MapConfigAttribute : Attribute
{
    public MapConfigAttribute(string name, string schema)
    {
        Name = name;
        Schema = schema;
    }
    public string Name { get; }
    public string Schema { get; }
}

public sealed class DbMapper : ClassMapper<IDb>
{
    public DbMapper()
    {
        DapperExstensionsExstensions.CorrespondingTypeMapper<IDb>((tableName, sechemaName, exprs) =>
        {
            Table(tableName);
            Schema(SchemaName);
            return exprs.Select(Map);
        });
    }
}

public static class DapperExstensionsExstensions
{
    private static readonly object _LOCK = new object();
    public static Type SharedState_ModelInstanceType { get; set; }
    public static List<PropertyMap> CorrespondingTypeMapper<T>(Func<string, string, IEnumerable<Expression<Func<T, object>>>, IEnumerable<PropertyMap>> callback)
    {
        var tableNameAttribute = (MapConfigAttribute)SharedState_ModelInstanceType.GetCustomAttribute(typeof(MapConfigAttribute));
        var tableName = tableNameAttribute.Name;
        var schemaName = tableNameAttribute.Schema;
        var result = callback(tableName, schemaName, new GetPropertyExpressions<T>(SharedState_ModelInstanceType));
        Monitor.Exit(_LOCK);
        return result.ToList();
    }
    public static object Insert<TInput>(this IDbConnection connection, TInput entity) where TInput : class
    {
        Monitor.Enter(_LOCK);
        SharedState_ModelInstanceType = entity.GetType();
        return DapperExtensions.DapperExtensions.Insert(connection, entity);
    }
}

public class GetPropertyExpressions<TInput> : IEnumerable<Expression<Func<TInput, object>>>
{
    private readonly Type _instanceType;

    public GetPropertyExpressions(Type instanceType)
    {
        _instanceType = instanceType;
    }
    public IEnumerator<Expression<Func<TInput, object>>> GetEnumerator()
    {
        return _instanceType
            .GetProperties()
            .Select(p => new GetPropertyExpression<TInput>(_instanceType, p.Name).Content()).GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

public class GetPropertyExpression<TInput> : IContent<Expression<Func<TInput, object>>>
{
    private readonly Type _instanceType;
    private readonly string _propertyName;
    public GetPropertyExpression(Type instanceType, string propertyName)
    {
        _instanceType = instanceType;
        _propertyName = propertyName;
    }
    public Expression<Func<TInput, object>> Content()
    {
        // Expression<Func<IDb, object>> :: model => (object)(new DbModel().Title)

        var newInstance = Expression.New(_instanceType);
        var parameter = Expression.Parameter(typeof(TInput), "model");

        var getPropertyExpression = Expression.Property(newInstance, _propertyName);
        var convertedProperty = Expression.Convert(getPropertyExpression, typeof(object));
        var lambdaExpression = Expression.Lambda(convertedProperty, parameter);

        return (Expression<Func<TInput, object>>)lambdaExpression;
    }
}

对我有用

IDb obj = new DbModel();
sqlConnection.Insert(obj);