未验证 提交 33090c02 编写于 作者: M Marc Gravell 提交者: GitHub

Underscore handling (#1947)

* Adds MatchConstructorParametersWithUnderscores option

* generalize to single MatchNamesWithUnderscores (pre-existing)

* cite 2nd PR

---------
Co-authored-by: NJonas Goronczy <goronczy.jonas@gmail.com>
上级 19355d5c
...@@ -74,8 +74,16 @@ public ConstructorInfo FindConstructor(string[] names, Type[] types) ...@@ -74,8 +74,16 @@ public ConstructorInfo FindConstructor(string[] names, Type[] types)
int i = 0; int i = 0;
for (; i < ctorParameters.Length; i++) for (; i < ctorParameters.Length; i++)
{ {
if (!string.Equals(ctorParameters[i].Name, names[i], StringComparison.OrdinalIgnoreCase)) if (EqualsCI(ctorParameters[i].Name, names[i]))
{ } // exact match
else if (MatchNamesWithUnderscores && EqualsCIU(ctorParameters[i].Name, names[i]))
{ } // match after applying underscores
else
{
// not a name match
break; break;
}
if (types[i] == typeof(byte[]) && ctorParameters[i].ParameterType.FullName == SqlMapper.LinqBinary) if (types[i] == typeof(byte[]) && ctorParameters[i].ParameterType.FullName == SqlMapper.LinqBinary)
continue; continue;
var unboxedType = Nullable.GetUnderlyingType(ctorParameters[i].ParameterType) ?? ctorParameters[i].ParameterType; var unboxedType = Nullable.GetUnderlyingType(ctorParameters[i].ParameterType) ?? ctorParameters[i].ParameterType;
...@@ -119,9 +127,8 @@ public ConstructorInfo FindExplicitConstructor() ...@@ -119,9 +127,8 @@ public ConstructorInfo FindExplicitConstructor()
/// <returns>Mapping implementation</returns> /// <returns>Mapping implementation</returns>
public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, string columnName) public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, string columnName)
{ {
var parameters = constructor.GetParameters(); ParameterInfo param = MatchFirstOrDefault(constructor.GetParameters(), columnName, static p => p.Name);
return new SimpleMemberMap(columnName, param);
return new SimpleMemberMap(columnName, parameters.FirstOrDefault(p => string.Equals(p.Name, columnName, StringComparison.OrdinalIgnoreCase)));
} }
/// <summary> /// <summary>
...@@ -131,14 +138,7 @@ public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, ...@@ -131,14 +138,7 @@ public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor,
/// <returns>Mapping implementation</returns> /// <returns>Mapping implementation</returns>
public SqlMapper.IMemberMap GetMember(string columnName) public SqlMapper.IMemberMap GetMember(string columnName)
{ {
var property = Properties.Find(p => string.Equals(p.Name, columnName, StringComparison.Ordinal)) var property = MatchFirstOrDefault(Properties, columnName, static p => p.Name);
?? Properties.Find(p => string.Equals(p.Name, columnName, StringComparison.OrdinalIgnoreCase));
if (property == null && MatchNamesWithUnderscores)
{
property = Properties.Find(p => string.Equals(p.Name, columnName.Replace("_", ""), StringComparison.Ordinal))
?? Properties.Find(p => string.Equals(p.Name, columnName.Replace("_", ""), StringComparison.OrdinalIgnoreCase));
}
if (property != null) if (property != null)
return new SimpleMemberMap(columnName, property); return new SimpleMemberMap(columnName, property);
...@@ -174,6 +174,54 @@ public SqlMapper.IMemberMap GetMember(string columnName) ...@@ -174,6 +174,54 @@ public SqlMapper.IMemberMap GetMember(string columnName)
/// </summary> /// </summary>
public static bool MatchNamesWithUnderscores { get; set; } public static bool MatchNamesWithUnderscores { get; set; }
static T MatchFirstOrDefault<T>(IList<T> members, string name, Func<T, string> selector) where T : class
{
if (members is { Count: > 0 })
{
// try exact first
foreach (var member in members)
{
if (string.Equals(name, selector(member), StringComparison.Ordinal))
{
return member;
}
}
// then exact ignoring case
foreach (var member in members)
{
if (string.Equals(name, selector(member), StringComparison.OrdinalIgnoreCase))
{
return member;
}
}
if (MatchNamesWithUnderscores)
{
// same again, minus underscore delta
name = name?.Replace("_", "");
foreach (var member in members)
{
if (string.Equals(name, selector(member)?.Replace("_", ""), StringComparison.Ordinal))
{
return member;
}
}
foreach (var member in members)
{
if (string.Equals(name, selector(member)?.Replace("_", ""), StringComparison.OrdinalIgnoreCase))
{
return member;
}
}
}
}
return null;
}
internal static bool EqualsCI(string x, string y)
=> string.Equals(x, y, StringComparison.OrdinalIgnoreCase);
internal static bool EqualsCIU(string x, string y)
=> string.Equals(x?.Replace("_", ""), y?.Replace("_", ""), StringComparison.OrdinalIgnoreCase);
/// <summary> /// <summary>
/// The settable properties for this typemap /// The settable properties for this typemap
/// </summary> /// </summary>
......
...@@ -22,6 +22,8 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command ...@@ -22,6 +22,8 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command
### unreleased ### unreleased
- add underscore handling with constructors (#1786 via @jo-goro, fixes #818; also #1947 via mgravell)
(note: new PRs will not be merged until they add release note wording here) (note: new PRs will not be merged until they add release note wording here)
### 2.0.143 ### 2.0.143
......
...@@ -220,5 +220,45 @@ public void TestWithNonPublicConstructor() ...@@ -220,5 +220,45 @@ public void TestWithNonPublicConstructor()
var output = connection.Query<WithPrivateConstructor>("select 1 as Foo").First(); var output = connection.Query<WithPrivateConstructor>("select 1 as Foo").First();
Assert.Equal(1, output.Foo); Assert.Equal(1, output.Foo);
} }
[Fact]
public void CtorWithUnderscores()
{
var obj = connection.QueryFirst<Type_ParamsWithUnderscores>("select 'abc' as FIRST_NAME, 'def' as LAST_NAME");
Assert.NotNull(obj);
Assert.Equal("abc", obj.FirstName);
Assert.Equal("def", obj.LastName);
}
[Fact]
public void CtorWithoutUnderscores()
{
DefaultTypeMap.MatchNamesWithUnderscores = true;
var obj = connection.QueryFirst<Type_ParamsWithoutUnderscores>("select 'abc' as FIRST_NAME, 'def' as LAST_NAME");
Assert.NotNull(obj);
Assert.Equal("abc", obj.FirstName);
Assert.Equal("def", obj.LastName);
}
class Type_ParamsWithUnderscores
{
public string FirstName { get; }
public string LastName { get; }
public Type_ParamsWithUnderscores(string first_name, string last_name)
{
FirstName = first_name;
LastName = last_name;
}
}
class Type_ParamsWithoutUnderscores
{
public string FirstName { get; }
public string LastName { get; }
public Type_ParamsWithoutUnderscores(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
}
} }
} }
...@@ -1273,5 +1273,26 @@ private class HazGetOnly ...@@ -1273,5 +1273,26 @@ private class HazGetOnly
public int Id { get; } public int Id { get; }
public string Name { get; } = "abc"; public string Name { get; } = "abc";
} }
[Fact]
public void TestConstructorParametersWithUnderscoredColumns()
{
DefaultTypeMap.MatchNamesWithUnderscores = true;
var obj = connection.QuerySingle<HazGetOnlyAndCtor>("select 42 as [id_property], 'def' as [name_property];");
Assert.Equal(42, obj.IdProperty);
Assert.Equal("def", obj.NameProperty);
}
private class HazGetOnlyAndCtor
{
public int IdProperty { get; }
public string NameProperty { get; }
public HazGetOnlyAndCtor(int idProperty, string nameProperty)
{
IdProperty = idProperty;
NameProperty = nameProperty;
}
}
} }
} }
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册