Skip to content

Commit 025cc31

Browse files
authored
Better error message in case property or field is not present in new() (#340)
* Also support Fields and give better error message. * Select_Dynamic_WithField * If only 1 argument, and the arg is ConstantExpression, just return the ConstantExpression * .
1 parent a708a28 commit 025cc31

File tree

5 files changed

+114
-26
lines changed

5 files changed

+114
-26
lines changed

src-console/ConsoleAppEF3.1/Program.cs

+23-6
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,20 @@
22
using System.Linq;
33
using ConsoleAppEF2.Database;
44
using System.Linq.Dynamic.Core;
5+
using Newtonsoft.Json;
56

67
namespace ConsoleAppEF31
78
{
89
class Program
910
{
1011
static void Main(string[] args)
1112
{
13+
var users = new[] { new User { FirstName = "Doe" } }.AsQueryable();
14+
foreach (dynamic x in users.Select("new (int?(Field) as fld, string(null) as StrNull, string(\"a\") as StrA, string(\"\") as StrEmpty1)"))
15+
{
16+
Console.WriteLine($"x = {JsonConvert.SerializeObject(x)}");
17+
}
18+
1219
var context = new TestContext();
1320

1421
//context.Database.EnsureDeleted();
@@ -60,23 +67,33 @@ static void Main(string[] args)
6067
}
6168

6269
var config = new ParsingConfig { AllowNewToEvaluateAnyType = true, ResolveTypesBySimpleName = false };
63-
var select = context.Cars.Select<Car>(config, $"new {typeof(Car).FullName}(it.Key as Key, \"?\" as Brand)");
70+
var select = context.Cars.Select<Car>(config, $"new {typeof(Car).FullName}(it.Key as Key, \"?\" as Brand, string(null) as Color, string(\"e\") as Extra)");
6471
foreach (Car car in select)
6572
{
6673
Console.WriteLine($"{car.Key}");
6774
}
6875

69-
// Users
70-
var users = new[] { new User { FirstName = "Doe" } }.AsQueryable();
71-
7276
var resultDynamic = users.Any("c => np(c.FirstName, string.Empty).ToUpper() == \"DOE\"");
7377
Console.WriteLine(resultDynamic);
7478

75-
// Fails because Field is not a property but a field!
7679
var users2 = users.Select<User>(config, "new User(it.FirstName as FirstName, 1 as Field)");
7780
foreach (User u in users2)
7881
{
79-
Console.WriteLine($"u = {u.FirstName} {u.Field}");
82+
Console.WriteLine($"u.FirstName = {u.FirstName}, u.Field = {u.Field}");
83+
}
84+
85+
try
86+
{
87+
users.Select<User>(config, "new User(1 as FieldDoesNotExist)");
88+
}
89+
catch (Exception e)
90+
{
91+
Console.WriteLine(e);
92+
}
93+
94+
foreach (dynamic x in users.Select("new (FirstName, string(\"a\") as StrA, string('c') as StrCh, string(\"\") as StrEmpty1, string('\0') as StrEmpty2, string(null) as StrNull)"))
95+
{
96+
Console.WriteLine($"x.FirstName = '{x.FirstName}' ; x.Str = '{x.Str == null}'");
8097
}
8198
}
8299

src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs

+32-5
Original file line numberDiff line numberDiff line change
@@ -1455,11 +1455,29 @@ private Expression CreateNewExpression(List<DynamicProperty> properties, List<Ex
14551455
MemberBinding[] bindings = new MemberBinding[properties.Count];
14561456
for (int i = 0; i < bindings.Length; i++)
14571457
{
1458-
PropertyInfo property = type.GetProperty(properties[i].Name);
1459-
Type propertyType = property.PropertyType;
1458+
string propertyOrFieldName = properties[i].Name;
1459+
Type propertyOrFieldType;
1460+
MemberInfo memberInfo;
1461+
PropertyInfo propertyInfo = type.GetProperty(propertyOrFieldName);
1462+
if (propertyInfo != null)
1463+
{
1464+
memberInfo = propertyInfo;
1465+
propertyOrFieldType = propertyInfo.PropertyType;
1466+
}
1467+
else
1468+
{
1469+
FieldInfo fieldInfo = type.GetField(propertyOrFieldName);
1470+
if (fieldInfo == null)
1471+
{
1472+
throw ParseError(Res.UnknownPropertyOrField, propertyOrFieldName, TypeHelper.GetTypeName(type));
1473+
}
1474+
1475+
memberInfo = fieldInfo;
1476+
propertyOrFieldType = fieldInfo.FieldType;
1477+
}
14601478

14611479
// Promote from Type to Nullable Type if needed
1462-
bindings[i] = Expression.Bind(property, _parsingConfig.ExpressionPromoter.Promote(expressions[i], propertyType, true, true));
1480+
bindings[i] = Expression.Bind(memberInfo, _parsingConfig.ExpressionPromoter.Promote(expressions[i], propertyOrFieldType, true, true));
14631481
}
14641482

14651483
return Expression.MemberInit(Expression.New(type), bindings);
@@ -1494,12 +1512,18 @@ Expression ParseTypeAccess(Type type)
14941512
_textParser.NextToken();
14951513
}
14961514

1497-
// This is a shorthand for explicitely converting a string to something
1515+
// This is a shorthand for explicitly converting a string to something
14981516
bool shorthand = _textParser.CurrentToken.Id == TokenId.StringLiteral;
14991517
if (_textParser.CurrentToken.Id == TokenId.OpenParen || shorthand)
15001518
{
15011519
Expression[] args = shorthand ? new[] { ParseStringLiteral() } : ParseArgumentList();
15021520

1521+
// If only 1 argument, and the arg is ConstantExpression, just return the ConstantExpression
1522+
if (args.Length == 1 && args[0] is ConstantExpression)
1523+
{
1524+
return args[0];
1525+
}
1526+
15031527
// If only 1 argument, and if the type is a ValueType and argType is also a ValueType, just Convert
15041528
if (args.Length == 1)
15051529
{
@@ -1511,7 +1535,10 @@ Expression ParseTypeAccess(Type type)
15111535
}
15121536
}
15131537

1514-
switch (_methodFinder.FindBestMethod(type.GetConstructors(), args, out MethodBase method))
1538+
var constructorsWithOutPointerArguments = type.GetConstructors()
1539+
.Where(c => !c.GetParameters().Any(p => p.ParameterType.GetTypeInfo().IsPointer))
1540+
.ToArray();
1541+
switch (_methodFinder.FindBestMethod(constructorsWithOutPointerArguments, args, out MethodBase method))
15151542
{
15161543
case 0:
15171544
if (args.Length == 1)

src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodFinder.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,10 @@ public int FindMethod(Type type, string methodName, bool staticAccess, Expressio
5252

5353
public int FindBestMethod(IEnumerable<MethodBase> methods, Expression[] args, out MethodBase method)
5454
{
55-
MethodData[] applicable = methods.
56-
Select(m => new MethodData { MethodBase = m, Parameters = m.GetParameters() }).
57-
Where(m => IsApplicable(m, args)).
58-
ToArray();
55+
MethodData[] applicable = methods
56+
.Select(m => new MethodData { MethodBase = m, Parameters = m.GetParameters() })
57+
.Where(m => IsApplicable(m, args))
58+
.ToArray();
5959

6060
if (applicable.Length > 1)
6161
{

test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.cs

+34
Original file line numberDiff line numberDiff line change
@@ -69,5 +69,39 @@ public void Parse_ParseAndOperator(string expression, string result)
6969
// Assert
7070
Check.That(parsedExpression).Equals(result);
7171
}
72+
73+
[Theory]
74+
[InlineData("string(null)", null)]
75+
[InlineData("string(\"\")", "")]
76+
[InlineData("string(\"a\")", "a")]
77+
public void Parse_CastStringShouldReturnConstantExpression(string expression, object result)
78+
{
79+
// Arrange
80+
ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(bool), "x") };
81+
var sut = new ExpressionParser(parameters, expression, null, null);
82+
83+
// Act
84+
var constantExpression = (ConstantExpression)sut.Parse(null);
85+
86+
// Assert
87+
Check.That(constantExpression.Value).Equals(result);
88+
}
89+
90+
[Theory]
91+
[InlineData("int?(null)", null)]
92+
[InlineData("int?(5)", 5)]
93+
[InlineData("int(42)", 42)]
94+
public void Parse_CastIntShouldReturnConstantExpression(string expression, object result)
95+
{
96+
// Arrange
97+
ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(bool), "x") };
98+
var sut = new ExpressionParser(parameters, expression, null, null);
99+
100+
// Act
101+
var constantExpression = (ConstantExpression)sut.Parse(null);
102+
103+
// Assert
104+
Check.That(constantExpression.Value).Equals(result);
105+
}
72106
}
73107
}

test/System.Linq.Dynamic.Core.Tests/QueryableTests.Select.cs

+21-11
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public partial class QueryableTests
1818
{
1919
public class Example
2020
{
21+
public int Field;
2122
public DateTime Time { get; set; }
2223
public DateTime? TimeNull { get; set; }
2324
public DayOfWeek? DOWNull { get; set; }
@@ -268,15 +269,27 @@ public void Select_Dynamic_IntoTypeWithNullableProperties2()
268269
Check.That(resultDynamic.Last()).Equals(result.Last());
269270
}
270271

272+
[Fact]
273+
public void Select_Dynamic_WithField()
274+
{
275+
// Arrange
276+
var config = new ParsingConfig { AllowNewToEvaluateAnyType = true };
277+
var queryable = new List<int> { 1, 2 }.AsQueryable();
278+
279+
// Act
280+
var projectedData = queryable.Select<Example>(config, $"new {typeof(Example).FullName}(~ as Field)");
281+
282+
// Assert
283+
Check.That(projectedData.First().Field).Equals(1);
284+
Check.That(projectedData.Last().Field).Equals(2);
285+
}
286+
271287
[Fact]
272288
public void Select_Dynamic_IntoKnownNestedType()
273289
{
290+
// Arrange
274291
var config = new ParsingConfig { AllowNewToEvaluateAnyType = true };
275-
#if NETCOREAPP
276-
// config.CustomTypeProvider = new NetStandardCustomTypeProvider();
277-
#endif
278-
// Assign
279-
var queryable = new List<string>() { "name1", "name2" }.AsQueryable();
292+
var queryable = new List<string> { "name1", "name2" }.AsQueryable();
280293

281294
// Act
282295
var projectedData = queryable.Select<Example.NestedDto>(config, $"new {typeof(Example.NestedDto).FullName}(~ as Name)");
@@ -289,13 +302,9 @@ public void Select_Dynamic_IntoKnownNestedType()
289302
[Fact]
290303
public void Select_Dynamic_IntoKnownNestedTypeSecondLevel()
291304
{
305+
// Arrange
292306
var config = new ParsingConfig { AllowNewToEvaluateAnyType = true };
293-
#if NETCOREAPP
294-
// config.CustomTypeProvider = new NetStandardCustomTypeProvider();
295-
#endif
296-
297-
// Assign
298-
var queryable = new List<string>() { "name1", "name2" }.AsQueryable();
307+
var queryable = new List<string> { "name1", "name2" }.AsQueryable();
299308

300309
// Act
301310
var projectedData = queryable.Select<Example.NestedDto.NestedDto2>(config, $"new {typeof(Example.NestedDto.NestedDto2).FullName}(~ as Name2)");
@@ -365,6 +374,7 @@ public void Select_Dynamic_Exceptions()
365374
Assert.Throws<ParseException>(() => qry.Select("new Id, UserName"));
366375
Assert.Throws<ParseException>(() => qry.Select("new (Id, UserName"));
367376
Assert.Throws<ParseException>(() => qry.Select("new (Id, UserName, Bad)"));
377+
Check.ThatCode(() => qry.Select<User>("new User(it.Bad as Bad)")).Throws<ParseException>().WithMessage("No property or field 'Bad' exists in type 'User'");
368378

369379
Assert.Throws<ArgumentNullException>(() => DynamicQueryableExtensions.Select(null, "Id"));
370380
Assert.Throws<ArgumentNullException>(() => qry.Select(null));

0 commit comments

Comments
 (0)