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
17 changes: 16 additions & 1 deletion src-console/ConsoleAppEF6_InMemory/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ static async Task Main(string[] args)
await using (var context = new TestContextEF6())
{
var result784 = context.Products.Where("NullableInt = @0", 1).ToDynamicArray<ProductDynamic>();
Console.WriteLine("a1 {0}", string.Join(",", result784.Select(r => r.Key)));
Console.WriteLine("a1 {0}", string.Join(", ", result784.Select(r => r.Key)));
}

await using (var context = new TestContextEF6())
Expand Down Expand Up @@ -65,5 +65,20 @@ static async Task Main(string[] args)
Console.WriteLine(result.Key + ":" + JsonSerializer.Serialize(result.Dict, JsonSerializerOptions));
}
}

// #907 and #912
await using (var context = new TestContextEF6())
{
var dynamicData = context.Products
.AsQueryable()
.Select("new { NullableInt as Value }")
.ToDynamicArray();
var dynamicResult = dynamicData
.AsQueryable()
.Select("Value")
.ToDynamicArray();

Console.WriteLine("#907 and #912 = {0}", string.Join(", ", dynamicResult));
}
}
}
42 changes: 38 additions & 4 deletions src-console/ConsoleApp_net6.0/Program.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Linq.Dynamic.Core.NewtonsoftJson;
Expand Down Expand Up @@ -33,15 +34,15 @@ public class GroupedSalesData
public string Region { get; set; }
public string? Product { get; set; }
public int TotalSales { get; set; }

public int GroupLevel { get; set; }
}

class Program
{
static void Main(string[] args)
{
Q912();
Q912a();
Q912b();
return;

Json();
Expand All @@ -67,7 +68,7 @@ static void Main(string[] args)
Dynamic();
}

private static void Q912()
private static void Q912a()
{
var extractedRows = new List<SalesData>
{
Expand All @@ -90,12 +91,45 @@ private static void Q912()
.GroupBy("Region")
.Select<GroupedSalesData>("new (Key as Region, null as Product, Sum(Convert.ToInt32(Sales)) as TotalSales, 1 as GroupLevel)");

var combined = detailed.Concat(regionSubtotal);
var combined = detailed.Concat(regionSubtotal).AsQueryable();
var ordered = combined.OrderBy("Product").ToDynamicList();

int x = 9;
}

private static void Q912b()
{
var eInfoJoinTable = new DataTable();
eInfoJoinTable.Columns.Add("Region", typeof(string));
eInfoJoinTable.Columns.Add("Product", typeof(string));
eInfoJoinTable.Columns.Add("Sales", typeof(int));

eInfoJoinTable.Rows.Add("North", "Apples", 100);
eInfoJoinTable.Rows.Add("North", "Oranges", 150);
eInfoJoinTable.Rows.Add("South", "Apples", 200);
eInfoJoinTable.Rows.Add("South", "Oranges", 250);

var extractedRows =
from row in eInfoJoinTable.AsEnumerable()
select row;

var rows = extractedRows.AsQueryable();

// GROUPING SET 1: (Region, Product)
var detailed = rows
.GroupBy("new (Region, Product)")
.Select("new (Key.Region as Region, Key.Product as Product, Sum(Convert.ToInt32(Sales)) as TotalSales, 0 as GroupLevel)");

// GROUPING SET 2: (Region)
var regionSubtotal = rows
.GroupBy("Region")
.Select("new (Key as Region, null as Product, Sum(Convert.ToInt32(Sales)) as TotalSales, 1 as GroupLevel)");

var combined = detailed.ToDynamicArray().Concat(regionSubtotal.ToDynamicArray()).AsQueryable();
var ordered = combined.OrderBy("Product").ToDynamicList();

int x = 9;
}

private static void NewtonsoftJson()
{
Expand Down
6 changes: 4 additions & 2 deletions src/System.Linq.Dynamic.Core/DynamicGetMemberBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ public DynamicGetMemberBinder(string name, ParsingConfig? config) : base(name, c

public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, DynamicMetaObject? errorSuggestion)
{
var instance = Expression.Call(
var methodCallExpression = Expression.Call(
DynamicGetMemberMethod,
target.Expression,
Expression.Constant(Name),
Expression.Constant(IgnoreCase));

return DynamicMetaObject.Create(target.Value!, instance);
// Fix #907 and #912: "The result of the dynamic binding produced by the object with type '<>f__AnonymousType1`4' for the binder 'System.Linq.Dynamic.Core.DynamicGetMemberBinder' needs at least one restriction.".
var restrictions = BindingRestrictions.GetInstanceRestriction(target.Expression, target.Value);
return new DynamicMetaObject(methodCallExpression, restrictions, target.Value!);
}

public static object? GetDynamicMember(object value, string name, bool ignoreCase)
Expand Down
12 changes: 4 additions & 8 deletions src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1609,9 +1609,9 @@

// Option 2. Call the default (empty) constructor and set the members
var memberBindings = new MemberBinding[properties.Count];
for (int i = 0; i < memberBindings.Length; i++)
for (var i = 0; i < memberBindings.Length; i++)
{
string propertyOrFieldName = properties[i].Name;
var propertyOrFieldName = properties[i].Name;
Type propertyOrFieldType;
MemberInfo memberInfo;
var propertyInfo = type.GetProperty(propertyOrFieldName);
Expand All @@ -1632,12 +1632,8 @@
propertyOrFieldType = fieldInfo.FieldType;
}

// Promote from Type to Nullable Type if needed
var promoted = _parsingConfig.ExpressionPromoter.Promote(expressions[i], propertyOrFieldType, true, true);
if (promoted is null)
{
throw new NotSupportedException($"Unable to promote expression '{expressions[i]}'.");
}
// Call Promote and if that returns false, just try to convert the expression to the destination type using Expression.Convert
var promoted = _parsingConfig.ExpressionPromoter.Promote(expressions[i], propertyOrFieldType, true, true) ?? Expression.Convert(expressions[i], propertyOrFieldType);
memberBindings[i] = Expression.Bind(memberInfo, promoted);
}

Expand Down Expand Up @@ -1934,7 +1930,7 @@
switch (member)
{
case PropertyInfo property:
var propertyIsStatic = property?.GetGetMethod().IsStatic ?? property?.GetSetMethod().IsStatic ?? false;

Check warning on line 1933 in src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs

View workflow job for this annotation

GitHub Actions / Linux: Build and Tests

Dereference of a possibly null reference.
propertyOrFieldExpression = propertyIsStatic ? Expression.Property(null, property) : Expression.Property(expression, property);
return true;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
<Compile Include="..\System.Linq.Dynamic.Core.Tests\TestClasses\*.cs" />
</ItemGroup>

<ItemGroup>
<Compile Remove="..\System.Linq.Dynamic.Core.Tests\TestHelpers\SkipIfGitHubActionsAttribute.cs" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\System.Linq.Dynamic.Core\System.Linq.Dynamic.Core.csproj" />
<ProjectReference Include="..\..\src\EntityFramework.DynamicLinq\EntityFramework.DynamicLinq.csproj" />
Expand Down
8 changes: 4 additions & 4 deletions test/System.Linq.Dynamic.Core.Tests/DynamicClassTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ public void DynamicClass_GetRuntimeType()
typeOf.ToString().Should().Be("System.Linq.Dynamic.Core.DynamicClass");
}

[SkipIfGitHubActions]
[SkipIfGitHubActionsFact]
public void DynamicClassArray()
{
// Arrange
Expand Down Expand Up @@ -249,7 +249,7 @@ public void DynamicClassArray()
isValid.Should().BeTrue();
}

[SkipIfGitHubActions]
[SkipIfGitHubActionsFact]
public void DynamicClassArray_Issue593_Fails()
{
// Arrange
Expand Down Expand Up @@ -281,7 +281,7 @@ public void DynamicClassArray_Issue593_Fails()
isValid.Should().BeFalse(); // This should actually be true, but fails. For solution see Issue593_Solution1 and Issue593_Solution2.
}

[SkipIfGitHubActions]
[SkipIfGitHubActionsFact]
public void DynamicClassArray_Issue593_Solution1()
{
// Arrange
Expand Down Expand Up @@ -318,7 +318,7 @@ public void DynamicClassArray_Issue593_Solution1()
isValid.Should().BeTrue();
}

[SkipIfGitHubActions]
[SkipIfGitHubActionsFact]
public void DynamicClassArray_Issue593_Solution2()
{
// Arrange
Expand Down
172 changes: 172 additions & 0 deletions test/System.Linq.Dynamic.Core.Tests/DynamicGetMemberBinderTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
using System.Data;
using System.Linq.Dynamic.Core.Tests.TestHelpers;
using FluentAssertions;
using Xunit;

namespace System.Linq.Dynamic.Core.Tests;

public class DynamicGetMemberBinderTests
{
public class SalesData
{
public string Region { get; set; } = null!;

public string Product { get; set; } = null!;

public string Sales { get; set; } = null!;
}

public class GroupedSalesData
{
public string Region { get; set; } = null!;
public string? Product { get; set; }
public int TotalSales { get; set; }
public int GroupLevel { get; set; }
}

[Fact]
public void DynamicGetMemberBinder_SelectOnArrayWithComplexObjects()
{
// Arrange
var rows = new SalesData[]
{
new() { Region = "North", Product = "Widget", Sales = "100" },
new() { Region = "North", Product = "Gadget", Sales = "150" },
new() { Region = "South", Product = "Widget", Sales = "200" },
new() { Region = "South", Product = "Gadget", Sales = "100" },
new() { Region = "North", Product = "Widget", Sales = "50" }
}.AsQueryable();

// Act
var grouping1 = rows
.GroupBy("new (Region, Product)")
.Select("new (Key.Region as Region, Key.Product as Product, Sum(Convert.ToInt32(Sales)) as TotalSales, 0 as GroupLevel)");

var grouping2 = rows
.GroupBy("Region")
.Select("new (Key as Region, null as Product, Sum(Convert.ToInt32(Sales)) as TotalSales, 1 as GroupLevel)");

var combined = grouping1.ToDynamicArray().Concat(grouping2.ToDynamicArray()).AsQueryable();
var ordered = combined.OrderBy("Product").ToDynamicList();

// Assert
ordered.Should().HaveCount(6);
}

[Fact]
public void DynamicGetMemberBinder_SelectTypeOnArrayWithComplexObjects()
{
// Arrange
var rows = new SalesData[]
{
new() { Region = "North", Product = "Widget", Sales = "100" },
new() { Region = "North", Product = "Gadget", Sales = "150" },
new() { Region = "South", Product = "Widget", Sales = "200" },
new() { Region = "South", Product = "Gadget", Sales = "100" },
new() { Region = "North", Product = "Widget", Sales = "50" }
}.AsQueryable();

// Act
var grouping1 = rows
.GroupBy("new (Region, Product)")
.Select<GroupedSalesData>("new (Key.Region as Region, Key.Product as Product, Sum(Convert.ToInt32(Sales)) as TotalSales, 0 as GroupLevel)");

var grouping2 = rows
.GroupBy("Region")
.Select<GroupedSalesData>("new (Key as Region, null as Product, Sum(Convert.ToInt32(Sales)) as TotalSales, 1 as GroupLevel)");

var combined = grouping1.Concat(grouping2).AsQueryable();
var ordered = combined.OrderBy("Product").ToDynamicList();

// Assert
ordered.Should().HaveCount(6);
}

[SkipIfGitHubActionsFact]
public void DynamicGetMemberBinder_SelectOnDataTable()
{
// Arrange
var dataTable = new DataTable();
dataTable.Columns.Add("Region", typeof(string));
dataTable.Columns.Add("Product", typeof(string));
dataTable.Columns.Add("Sales", typeof(int));

dataTable.Rows.Add("North", "Apples", 100);
dataTable.Rows.Add("North", "Oranges", 150);
dataTable.Rows.Add("South", "Apples", 200);
dataTable.Rows.Add("South", "Oranges", 250);

var rows = dataTable.Rows.Cast<DataRow>().AsQueryable();

// Act
var grouping1 = rows
.GroupBy("new (Region, Product)")
.Select("new (Key.Region as Region, Key.Product as Product, Sum(Convert.ToInt32(Sales)) as TotalSales, 0 as GroupLevel)");

var grouping2 = rows
.GroupBy("Region")
.Select("new (Key as Region, null as Product, Sum(Convert.ToInt32(Sales)) as TotalSales, 1 as GroupLevel)");

var combined = grouping1.ToDynamicArray().Concat(grouping2.ToDynamicArray()).AsQueryable();
var ordered = combined.OrderBy("Product").ToDynamicList();

// Assert
ordered.Should().HaveCount(6);
}

[SkipIfGitHubActionsFact]
public void DynamicGetMemberBinder_SelectTypeOnDataTable()
{
// Arrange
var dataTable = new DataTable();
dataTable.Columns.Add("Region", typeof(string));
dataTable.Columns.Add("Product", typeof(string));
dataTable.Columns.Add("Sales", typeof(int));

dataTable.Rows.Add("North", "Apples", 100);
dataTable.Rows.Add("North", "Oranges", 150);
dataTable.Rows.Add("South", "Apples", 200);
dataTable.Rows.Add("South", "Oranges", 250);

var rows = dataTable.Rows.Cast<DataRow>().AsQueryable();

// Act
var grouping1 = rows
.GroupBy("new (Region, Product)")
.Select<GroupedSalesData>("new (Key.Region as Region, Key.Product as Product, Sum(Convert.ToInt32(Sales)) as TotalSales, 0 as GroupLevel)");

var grouping2 = rows
.GroupBy("Region")
.Select<GroupedSalesData>("new (Key as Region, null as Product, Sum(Convert.ToInt32(Sales)) as TotalSales, 1 as GroupLevel)");

var combined = grouping1.ToDynamicArray().Concat(grouping2.ToDynamicArray()).AsQueryable();
var ordered = combined.OrderBy("Product").ToDynamicList();

// Assert
ordered.Should().HaveCount(6);
}

[Fact]
public void DynamicGetMemberBinder_SelectOnArrayWithIntegers()
{
// Arrange
var dynamicData = new[] { 1, 2 }
.AsQueryable()
.Select("new { it * 2 as Value }")
.ToDynamicArray()
.AsQueryable();

// Act
var dynamicResult1 = dynamicData
.Select("Value")
.ToDynamicArray();

var dynamicResult2 = dynamicData
.Select("Value")
.ToDynamicArray<int>();

// Assert
dynamicResult1.Should().HaveCount(2);
dynamicResult2.Should().BeEquivalentTo([2, 4]);
}
}
Loading
Loading