0

I have a class which implements some interface:

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

After this I use Dapper Extensions to insert the object into the DB. This code works well:

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

But when I try insert an instance of this class, casting on the corresponding interface, it gives an Exception:

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

System.ArgumentException: 'No columns were mapped.'

JBC
  • 667
  • 1
  • 9
  • 21
giokoguashvili
  • 2,013
  • 3
  • 18
  • 37

1 Answers1

0

Reason why it not work:

Proceeding from the fact that, DapperExtensions.Insert method is generic, DapperExstensions can't find corresponding map for IDb

Solution:

Create mapper class for IDb and register DbModel properties

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

After this it will be work:

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.

Edit:

After some debuging I notice that, in every Insert method call, dapper do mapping, and construct corresponding ClassMapper<> class. We can use this weirdness.

For this we should create SharedState and store in it instance type before calling Insert method.

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

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

After this we can access this property when Dapper will do mapping

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

Whole code snippet:

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

It works for me

IDb obj = new DbModel();
sqlConnection.Insert(obj);
giokoguashvili
  • 2,013
  • 3
  • 18
  • 37