Skip to content

Commit

Permalink
Define expected behaviour for value-tuple parameters and returns, and…
Browse files Browse the repository at this point in the history
… implement
  • Loading branch information
mgravell committed Apr 7, 2017
1 parent ae7a29a commit 9cc975b
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 3 deletions.
1 change: 1 addition & 0 deletions Dapper.Tests/Dapper.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
<PackageReference Include="xunit.runner.console" Version="2.2.0" />
<PackageReference Include="xunit" Version="2.2.0" />
<PackageReference Include="System.ValueTuple" Version="4.3.0"/>
</ItemGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'net452' ">
Expand Down
48 changes: 48 additions & 0 deletions Dapper.Tests/TupleTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System;
using Xunit;

namespace Dapper.Tests
{
public class TupleTests : TestBase
{
[Fact]
public void TupleStructParameter_Fails_HelpfulMessage()
{
try
{
// I can see this happening...
connection.QuerySingle<int>("select @id", (id: 42, name: "Fred"));
Assert.Fail();
ValueTuple<int, int> b = (24, 13);
b.Item1.IsEqualTo(24);
}
catch (NotSupportedException ex)
{
ex.Message.IsEqualTo("ValueTuple should not be used for parameters - the language-level names are not available to use as parameter names, and it adds unnecessary boxing");
}
}

[Fact]
public void TupleClassParameter_Works()
{
connection.QuerySingle<int>("select @Item1", Tuple.Create(42, "Fred")).IsEqualTo(42);
}

[Fact]
public void TupleReturnValue_Works_ByPosition()
{
var val = connection.QuerySingle<(int id, string name)>("select 42, 'Fred'");
val.id.IsEqualTo(42);
val.name.IsEqualTo("Fred");
}

[Fact]
public void TupleReturnValue_Works_NamesIgnored()
{
var val = connection.QuerySingle<(int id, string name)>("select 42 as [Item2], 'Fred' as [Item1]");
val.id.IsEqualTo(42);
val.name.IsEqualTo("Fred");
}

}
}
36 changes: 33 additions & 3 deletions Dapper/SqlMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2232,11 +2232,42 @@ public static Action<IDbCommand, object> CreateParamInfoGenerator(Identity ident
{
return CreateParamInfoGenerator(identity, checkForDuplicates, removeUnused, GetLiteralTokens(identity.sql));
}
static bool IsValueTuple(Type type) => type != null && type.IsValueType() && type.FullName.StartsWith("System.ValueTuple`");

static List<IMemberMap> GetValueTupleMembers(Type type, string[] names)
{
var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance);
int keep = Math.Min(fields.Length, names.Length);
var result = new List<IMemberMap>(keep);
for(int i = 0; i < keep; i++)
{
FieldInfo field = null;
string name = "Item" + (i + 1).ToString(CultureInfo.InvariantCulture);
foreach(var test in fields)
{
if(test.Name == name)
{
field = test;
break;
}
}
if (field != null)
{
result.Add(new SimpleMemberMap(string.IsNullOrWhiteSpace(names[i]) ? name : names[i], field));
}
}
return result;
}

internal static Action<IDbCommand, object> CreateParamInfoGenerator(Identity identity, bool checkForDuplicates, bool removeUnused, IList<LiteralToken> literals)
{
Type type = identity.parametersType;

if (IsValueTuple(type))
{
throw new NotSupportedException("ValueTuple should not be used for parameters - the language-level names are not available to use as parameter names, and it adds unnecessary boxing");
}

bool filterParams = false;
if (removeUnused && identity.commandType.GetValueOrDefault(CommandType.Text) == CommandType.Text)
{
Expand Down Expand Up @@ -2924,7 +2955,6 @@ private static Func<IDataReader, object> GetTypeDeserializerImpl(
ITypeMap typeMap = GetTypeMap(type);

int index = startBound;

ConstructorInfo specializedConstructor = null;

#if !COREFX
Expand Down Expand Up @@ -3010,9 +3040,9 @@ private static Func<IDataReader, object> GetTypeDeserializerImpl(
il.Emit(OpCodes.Ldloc_1);// [target]
}

var members = (specializedConstructor != null
var members = IsValueTuple(type) ? GetValueTupleMembers(type, names) :((specializedConstructor != null
? names.Select(n => typeMap.GetConstructorParameter(specializedConstructor, n))
: names.Select(n => typeMap.GetMember(n))).ToList();
: names.Select(n => typeMap.GetMember(n))).ToList());

// stack is now [target]

Expand Down

0 comments on commit 9cc975b

Please sign in to comment.