My scenario: I have a class called Person that I save to the DB using Dapper. On Person, I have a Dictionary of Values that I serialize to a string and store that as varchar(MAX).
public class Person
{
public string Name {get; set;}
public int Id {get; set;} //PK in DB
public int Age {get; set;}
public IDictionary<string, string> Values {get; set;}
}
This is how I save to the DB:
DynamicProperties dp = new DynamicProperties();
dp.Add("Name", p.Name);
dp.Add("Age", p.Age);
dp.Add("Values", Jil.JSON.Serialize<IDictionary<string, string>>(p.Values));
conneciton.Execute(insertSql, dp, commandType: CommandType.StoredProcedure);
This is where I try to read it out
private Func<Person, object, Person> GetPerson = new Func<Person, object, Person>
((person, values) => {
person.Values = Jil.JSON.Deserialize<Dictionary<string, string>>((string)values);
return person;
});
string sql = "SELECT text
FROM otherTable
SELECT Name, Id, Age, Values
FROM People
WHERE Id = @Id"
SqlMapper.GridReader gridReader = connToDeviceConfig.QueryMultiple(sql, new {Id = 5}, commandType: CommandType.StoredProcedure);
List<string> listOfOtherStuff = gridReader.Read<string>().ToList();
List<Person> people = gridReader.Read<Person, object, Person>(GetPerson, splitOn: "Age").ToList();
// listOfOtherStuff and people are seperate
The second gridReader fails with When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id\r\nParameter name: splitOn
I feel like I am perhaps bending Dapper a bit to try and make it do something it wasn't supposed to do, i.e. reading a string out of the DB and deserializing it into a Dictionary and assigning to Person.Values.
Is this the way to do it (and I just have a bug somewhere)? or is there another approach that I should take?
I used this as a ref: (Link to approximate location in file at Archive.org)
public void TestProcSupport()
{
var p = new DynamicParameters();
p.Add("a", 11);
p.Add("b", dbType: DbType.Int32, direction: ParameterDirection.Output);
p.Add("c", dbType: DbType.Int32, direction: ParameterDirection.ReturnValue);
connection.Execute(@"create proc #TestProc
@a int,
@b int output
as
begin
set @b = 999
select 1111
return @a
end");
connection.Query<int>("#TestProc", p, commandType: CommandType.StoredProcedure).First().IsEqualTo(1111);
p.Get<int>("c").IsEqualTo(11);
p.Get<int>("b").IsEqualTo(999);
}
StackTrace:
at Dapper.SqlMapper.GetNextSplit(Int32 startIdx, String splitOn, IDataReader reader) in D:\Dev\dapper-dot-net\Dapper NET40\SqlMapper.cs:line 2111
at Dapper.SqlMapper.GenerateDeserializers(Type[] types, String splitOn, IDataReader reader) in D:\Dev\dapper-dot-net\Dapper NET40\SqlMapper.cs:line 2057
at Dapper.SqlMapper.<MultiMapImpl>d__71`8.MoveNext() in D:\Dev\dapper-dot-net\Dapper NET40\SqlMapper.cs:line 1857
at Dapper.SqlMapper.GridReader.<MultiReadInternal>d__9`8.MoveNext() in D:\Dev\dapper-dot-net\Dapper NET40\SqlMapper.cs:line 4300
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
at Dapper.SqlMapper.GridReader.Read[TFirst,TSecond,TReturn](Func`3 func, String splitOn, Boolean buffered) in D:\Dev\dapper-dot-net\Dapper NET40\SqlMapper.cs:line 4330
Tried other splitOn
s?
I don't have a DB test with handy, but I think splitOn is supposed to be the first column you want in the next mapped object, not the last column in the previous. Also the columns become members of an object, not the whole value (though there may be an exception for single-column cases, again I don't have a test DB handy).
TL;DR - try something like this
class ValuesObj{
public string Values {get;set;}
}
// whatever code here
private Func<Position, ValuesObj, Position> GetPerson = new Func<Person, ValuesObj, Person>
((person, valuesObj) => {
string values = valuesObj.Values;
person.Values = Jil.JSON.Deserialize<Dictionary<string, string>>((string)values);
return position;
});
string sql = "SELECT text
FROM otherTable
SELECT Name, Id, Age, Values
FROM People
WHERE Id = @Id"
SqlMapper.GridReader gridReader = connToDeviceConfig.QueryMultiple(sql, new {Id = 5}, commandType: CommandType.StoredProcedure);
List<string> listOfOtherStuff = gridReader.Read<string>().ToList();
List<Person> people = gridReader.Read<Person, ValuesObj, Person>(GetPerson, splitOn: "Values").ToList();
Otherwise, I'd suggest just using the dynamic
return Dapper methods.