querybuilder icon indicating copy to clipboard operation
querybuilder copied to clipboard

Binding a type implementing IEnumerable expands in to more values than there are parameters.

Open Swahhillie opened this issue 6 months ago • 0 comments

I am trying to use a custom type handler for a collection type. The problem is that SqlKata.Helper.Flatten(IEnumerable<object> array) is decomposing my collection type into its parts. Thereby it creates an invalid query with more values than parameters.

Reproduction code:

using System.Collections;
using System.Data;
using System.Data.SQLite;
using Dapper;

using SqlKata.Compilers;
using SqlKata.Execution;

var sqliteCompiler = new SqliteCompiler();
var connectionStringBuilder = new SQLiteConnectionStringBuilder() { DataSource = ":memory:" };
using var connection = new SQLiteConnection(connectionStringBuilder.ToString());
var db = new QueryFactory(connection, sqliteCompiler);

db.Statement(@"
CREATE TABLE test (
    id integer primary key autoincrement,
    single blob,
    multi blob
);
");

SqlMapper.AddTypeHandler(new SomeTypeHandler());
SqlMapper.AddTypeHandler(new SomeCollectionTypeHandler());

// Ok
db.Query("test")
    .Insert(new { single = new SomeSingleObject("Alice") });

//multi type fails, SqlKata.Helper.Flatten public static IEnumerable<object> Flatten(IEnumerable<object> array)
//flattens what is supposed to be one parameter and creates multiple instead because the collection type implements IEnumerable
db.Query("test")
    .Insert(new { multi = new SomeCollectionType([new("Alice"), new("Bob")]) });

class SomeSingleObject
{
    public SomeSingleObject(string name)
    {
        Name = name;
    }

    public string Name { get; set; }
}

class SomeCollectionType : IEnumerable<SomeSingleObject>
{
    public SomeCollectionType(SomeSingleObject[] myThings)
    {
        MyThings = myThings;
    }

    public SomeSingleObject[] MyThings { get; set; }

    public IEnumerator<SomeSingleObject> GetEnumerator()
    {
        foreach (var thing in MyThings)
            yield return thing;
    }

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

class SomeTypeHandler : SqlMapper.TypeHandler<SomeSingleObject>
{
    public override void SetValue(IDbDataParameter parameter, SomeSingleObject value)
    {
        parameter.DbType = DbType.String;
        parameter.Value = value.Name;
    }

    public override SomeSingleObject Parse(object value)
    {
        return new SomeSingleObject((string)value);
    }
}

class SomeCollectionTypeHandler : SqlMapper.TypeHandler<SomeCollectionType>
{
    public override void SetValue(IDbDataParameter parameter, SomeCollectionType value)
    {
        parameter.DbType = DbType.String;
        parameter.Value = string.Join(",", value.MyThings.Select(my => my.Name));
    }

    public override SomeCollectionType Parse(object value)
    {
        return new SomeCollectionType(((string)value).Split(",")
            .Select(s => new SomeSingleObject(s)).ToArray());
    }
}

Swahhillie avatar Jun 25 '25 09:06 Swahhillie