Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 23 additions & 6 deletions src-console/ConsoleAppEF3.1/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,20 @@
using System.Linq;
using ConsoleAppEF2.Database;
using System.Linq.Dynamic.Core;
using Newtonsoft.Json;

namespace ConsoleAppEF31
{
class Program
{
static void Main(string[] args)
{
var users = new[] { new User { FirstName = "Doe" } }.AsQueryable();
foreach (dynamic x in users.Select("new (int?(Field) as fld, string(null) as StrNull, string(\"a\") as StrA, string(\"\") as StrEmpty1)"))
{
Console.WriteLine($"x = {JsonConvert.SerializeObject(x)}");
}

var context = new TestContext();

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

var config = new ParsingConfig { AllowNewToEvaluateAnyType = true, ResolveTypesBySimpleName = false };
var select = context.Cars.Select<Car>(config, $"new {typeof(Car).FullName}(it.Key as Key, \"?\" as Brand)");
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)");
foreach (Car car in select)
{
Console.WriteLine($"{car.Key}");
}

// Users
var users = new[] { new User { FirstName = "Doe" } }.AsQueryable();

var resultDynamic = users.Any("c => np(c.FirstName, string.Empty).ToUpper() == \"DOE\"");
Console.WriteLine(resultDynamic);

// Fails because Field is not a property but a field!
var users2 = users.Select<User>(config, "new User(it.FirstName as FirstName, 1 as Field)");
foreach (User u in users2)
{
Console.WriteLine($"u = {u.FirstName} {u.Field}");
Console.WriteLine($"u.FirstName = {u.FirstName}, u.Field = {u.Field}");
}

try
{
users.Select<User>(config, "new User(1 as FieldDoesNotExist)");
}
catch (Exception e)
{
Console.WriteLine(e);
}

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)"))
{
Console.WriteLine($"x.FirstName = '{x.FirstName}' ; x.Str = '{x.Str == null}'");
}
}

Expand Down
37 changes: 32 additions & 5 deletions src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1455,11 +1455,29 @@ private Expression CreateNewExpression(List<DynamicProperty> properties, List<Ex
MemberBinding[] bindings = new MemberBinding[properties.Count];
for (int i = 0; i < bindings.Length; i++)
{
PropertyInfo property = type.GetProperty(properties[i].Name);
Type propertyType = property.PropertyType;
string propertyOrFieldName = properties[i].Name;
Type propertyOrFieldType;
MemberInfo memberInfo;
PropertyInfo propertyInfo = type.GetProperty(propertyOrFieldName);
if (propertyInfo != null)
{
memberInfo = propertyInfo;
propertyOrFieldType = propertyInfo.PropertyType;
}
else
{
FieldInfo fieldInfo = type.GetField(propertyOrFieldName);
if (fieldInfo == null)
{
throw ParseError(Res.UnknownPropertyOrField, propertyOrFieldName, TypeHelper.GetTypeName(type));
}

memberInfo = fieldInfo;
propertyOrFieldType = fieldInfo.FieldType;
}

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

return Expression.MemberInit(Expression.New(type), bindings);
Expand Down Expand Up @@ -1494,12 +1512,18 @@ Expression ParseTypeAccess(Type type)
_textParser.NextToken();
}

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

// If only 1 argument, and the arg is ConstantExpression, just return the ConstantExpression
if (args.Length == 1 && args[0] is ConstantExpression)
{
return args[0];
}

// If only 1 argument, and if the type is a ValueType and argType is also a ValueType, just Convert
if (args.Length == 1)
{
Expand All @@ -1511,7 +1535,10 @@ Expression ParseTypeAccess(Type type)
}
}

switch (_methodFinder.FindBestMethod(type.GetConstructors(), args, out MethodBase method))
var constructorsWithOutPointerArguments = type.GetConstructors()
.Where(c => !c.GetParameters().Any(p => p.ParameterType.GetTypeInfo().IsPointer))
.ToArray();
switch (_methodFinder.FindBestMethod(constructorsWithOutPointerArguments, args, out MethodBase method))
{
case 0:
if (args.Length == 1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,10 @@ public int FindMethod(Type type, string methodName, bool staticAccess, Expressio

public int FindBestMethod(IEnumerable<MethodBase> methods, Expression[] args, out MethodBase method)
{
MethodData[] applicable = methods.
Select(m => new MethodData { MethodBase = m, Parameters = m.GetParameters() }).
Where(m => IsApplicable(m, args)).
ToArray();
MethodData[] applicable = methods
.Select(m => new MethodData { MethodBase = m, Parameters = m.GetParameters() })
.Where(m => IsApplicable(m, args))
.ToArray();

if (applicable.Length > 1)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,5 +69,39 @@ public void Parse_ParseAndOperator(string expression, string result)
// Assert
Check.That(parsedExpression).Equals(result);
}

[Theory]
[InlineData("string(null)", null)]
[InlineData("string(\"\")", "")]
[InlineData("string(\"a\")", "a")]
public void Parse_CastStringShouldReturnConstantExpression(string expression, object result)
{
// Arrange
ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(bool), "x") };
var sut = new ExpressionParser(parameters, expression, null, null);

// Act
var constantExpression = (ConstantExpression)sut.Parse(null);

// Assert
Check.That(constantExpression.Value).Equals(result);
}

[Theory]
[InlineData("int?(null)", null)]
[InlineData("int?(5)", 5)]
[InlineData("int(42)", 42)]
public void Parse_CastIntShouldReturnConstantExpression(string expression, object result)
{
// Arrange
ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(bool), "x") };
var sut = new ExpressionParser(parameters, expression, null, null);

// Act
var constantExpression = (ConstantExpression)sut.Parse(null);

// Assert
Check.That(constantExpression.Value).Equals(result);
}
}
}
32 changes: 21 additions & 11 deletions test/System.Linq.Dynamic.Core.Tests/QueryableTests.Select.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public partial class QueryableTests
{
public class Example
{
public int Field;
public DateTime Time { get; set; }
public DateTime? TimeNull { get; set; }
public DayOfWeek? DOWNull { get; set; }
Expand Down Expand Up @@ -268,15 +269,27 @@ public void Select_Dynamic_IntoTypeWithNullableProperties2()
Check.That(resultDynamic.Last()).Equals(result.Last());
}

[Fact]
public void Select_Dynamic_WithField()
{
// Arrange
var config = new ParsingConfig { AllowNewToEvaluateAnyType = true };
var queryable = new List<int> { 1, 2 }.AsQueryable();

// Act
var projectedData = queryable.Select<Example>(config, $"new {typeof(Example).FullName}(~ as Field)");

// Assert
Check.That(projectedData.First().Field).Equals(1);
Check.That(projectedData.Last().Field).Equals(2);
}

[Fact]
public void Select_Dynamic_IntoKnownNestedType()
{
// Arrange
var config = new ParsingConfig { AllowNewToEvaluateAnyType = true };
#if NETCOREAPP
// config.CustomTypeProvider = new NetStandardCustomTypeProvider();
#endif
// Assign
var queryable = new List<string>() { "name1", "name2" }.AsQueryable();
var queryable = new List<string> { "name1", "name2" }.AsQueryable();

// Act
var projectedData = queryable.Select<Example.NestedDto>(config, $"new {typeof(Example.NestedDto).FullName}(~ as Name)");
Expand All @@ -289,13 +302,9 @@ public void Select_Dynamic_IntoKnownNestedType()
[Fact]
public void Select_Dynamic_IntoKnownNestedTypeSecondLevel()
{
// Arrange
var config = new ParsingConfig { AllowNewToEvaluateAnyType = true };
#if NETCOREAPP
// config.CustomTypeProvider = new NetStandardCustomTypeProvider();
#endif

// Assign
var queryable = new List<string>() { "name1", "name2" }.AsQueryable();
var queryable = new List<string> { "name1", "name2" }.AsQueryable();

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

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