Skip to content

Commit b0501eb

Browse files
authored
Add IndexerName attribute to DynamicClass to fix naming issues with "Item" (#948)
1 parent f2e0ec7 commit b0501eb

File tree

10 files changed

+65
-8
lines changed

10 files changed

+65
-8
lines changed

src/System.Linq.Dynamic.Core/DynamicClass.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.Dynamic;
44
using System.Reflection;
5+
using System.Runtime.CompilerServices;
56

67
namespace System.Linq.Dynamic.Core;
78

@@ -18,6 +19,8 @@ namespace System.Linq.Dynamic.Core;
1819
/// </summary>
1920
public abstract class DynamicClass : DynamicObject
2021
{
22+
internal const string IndexerName = "System_Linq_Dynamic_Core_DynamicClass_Indexer";
23+
2124
private Dictionary<string, object?>? _propertiesDictionary;
2225

2326
private Dictionary<string, object?> Properties
@@ -99,11 +102,12 @@ public void SetDynamicPropertyValue(string propertyName, object value)
99102
/// <value>The <see cref="object"/>.</value>
100103
/// <param name="name">The name.</param>
101104
/// <returns>Value from the property.</returns>
105+
[IndexerName(IndexerName)]
102106
public object? this[string name]
103107
{
104108
get
105109
{
106-
return Properties.TryGetValue(name, out object? result) ? result : null;
110+
return Properties.TryGetValue(name, out var result) ? result : null;
107111
}
108112

109113
set
@@ -153,7 +157,7 @@ public override bool TryGetMember(GetMemberBinder binder, out object? result)
153157
/// </returns>
154158
public override bool TrySetMember(SetMemberBinder binder, object? value)
155159
{
156-
string name = binder.Name;
160+
var name = binder.Name;
157161
if (Properties.ContainsKey(name))
158162
{
159163
Properties[name] = value;

src/System.Linq.Dynamic.Core/DynamicClass.net35.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ namespace System.Linq.Dynamic.Core;
66
/// </summary>
77
public abstract class DynamicClass
88
{
9+
internal const string IndexerName = "System_Linq_Dynamic_Core_DynamicClass_Indexer";
10+
911
/// <summary>
1012
/// Gets the dynamic property by name.
1113
/// </summary>

src/System.Linq.Dynamic.Core/DynamicClass.uap.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#if UAP10_0
22
using System.Collections.Generic;
33
using System.Dynamic;
4+
using System.Runtime.CompilerServices;
45

56
namespace System.Linq.Dynamic.Core;
67

@@ -9,6 +10,8 @@ namespace System.Linq.Dynamic.Core;
910
/// </summary>
1011
public class DynamicClass : DynamicObject
1112
{
13+
internal const string IndexerName = "System_Linq_Dynamic_Core_DynamicClass_Indexer";
14+
1215
private readonly Dictionary<string, object> _properties = new();
1316

1417
/// <summary>
@@ -31,6 +34,7 @@ public DynamicClass(params KeyValuePair<string, object>[] propertylist)
3134
/// </value>
3235
/// <param name="name">The name.</param>
3336
/// <returns>Value from the property.</returns>
37+
[IndexerName(IndexerName)]
3438
public object this[string name]
3539
{
3640
get

src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2849,7 +2849,7 @@ private static TResult ConvertResultIfNeeded<TResult>(object result)
28492849

28502850
private static LambdaExpression EnsureLambdaExpressionReturnsObject(LambdaExpression lambdaExpression)
28512851
{
2852-
if (!lambdaExpression.GetReturnType().GetTypeInfo().IsSubclassOf(typeof(DynamicClass)))
2852+
if (!TypeHelper.IsDynamicClass(lambdaExpression.GetReturnType()))
28532853
{
28542854
return Expression.Lambda(Expression.Convert(lambdaExpression.Body, typeof(object)), lambdaExpression.Parameters.ToArray());
28552855
}

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1580,7 +1580,7 @@ private Expression CreateNewExpression(List<DynamicProperty> properties, List<Ex
15801580
var propertyInfos = type.GetProperties();
15811581
if (type.GetTypeInfo().BaseType == typeof(DynamicClass))
15821582
{
1583-
propertyInfos = propertyInfos.Where(x => x.Name != "Item").ToArray();
1583+
propertyInfos = propertyInfos.Where(x => x.Name != DynamicClass.IndexerName).ToArray();
15841584
}
15851585

15861586
var propertyTypes = propertyInfos.Select(p => p.PropertyType).ToArray();
@@ -1906,7 +1906,7 @@ private Expression ParseMemberAccess(Type? type, Expression? expression, string?
19061906
#if UAP10_0 || NETSTANDARD1_3
19071907
if (type == typeof(DynamicClass))
19081908
{
1909-
return Expression.MakeIndex(expression, typeof(DynamicClass).GetProperty("Item"), new[] { Expression.Constant(id) });
1909+
return Expression.MakeIndex(expression!, typeof(DynamicClass).GetProperty(DynamicClass.IndexerName), [Expression.Constant(id)]);
19101910
}
19111911
#endif
19121912
if (TryFindPropertyOrField(type!, id, expression, out var propertyOrFieldExpression))
@@ -1920,7 +1920,8 @@ private Expression ParseMemberAccess(Type? type, Expression? expression, string?
19201920

19211921
if (!_parsingConfig.DisableMemberAccessToIndexAccessorFallback && extraCheck)
19221922
{
1923-
var indexerMethod = expression?.Type.GetMethod("get_Item", new[] { typeof(string) });
1923+
var indexerName = TypeHelper.IsDynamicClass(type!) ? DynamicClass.IndexerName : "Item";
1924+
var indexerMethod = expression?.Type.GetMethod($"get_{indexerName}", [typeof(string)]);
19241925
if (indexerMethod != null)
19251926
{
19261927
return Expression.Call(expression, indexerMethod, Expression.Constant(id));

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ namespace System.Linq.Dynamic.Core.Parser;
66

77
internal static class TypeHelper
88
{
9+
internal static bool IsDynamicClass(Type type)
10+
{
11+
return type == typeof(DynamicClass) || type.GetTypeInfo().IsSubclassOf(typeof(DynamicClass));
12+
}
13+
914
internal static bool TryGetAsEnumerable(Type type, [NotNullWhen(true)] out Type? enumerableType)
1015
{
1116
if (type.IsArray)

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,4 +164,15 @@ public void Entities_Select_DynamicClass_And_Select_DynamicClass()
164164

165165
dynamicResult.Should().BeEquivalentTo([1000, 1001]);
166166
}
167+
168+
[Fact]
169+
public void Entities_Select_ClassWithItemProperty()
170+
{
171+
// Act
172+
var result = _context.Posts.Select(x => new { x.Item, x.BlogId }).ToArray();
173+
var resultDynamic = _context.Posts.Select("new (Item, BlogId)").ToDynamicArray();
174+
175+
// Assert
176+
resultDynamic.Should().BeEquivalentTo(result);
177+
}
167178
}

test/System.Linq.Dynamic.Core.Tests/EntitiesTests.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace System.Linq.Dynamic.Core.Tests;
1111

1212
public partial class EntitiesTests : IClassFixture<EntitiesTestsDatabaseFixture>
1313
{
14-
private static readonly Random Rnd = new Random(1);
14+
private static readonly Random Rnd = new(1);
1515

1616
private readonly BlogContext _context;
1717

@@ -66,7 +66,8 @@ private void InternalPopulateTestData()
6666
Content = "My Content",
6767
PostDate = postDate,
6868
CloseDate = Rnd.Next(0, 10) < 5 ? postDate.AddDays(1) : null,
69-
NumberOfReads = Rnd.Next(0, 5000)
69+
NumberOfReads = Rnd.Next(0, 5000),
70+
Item = "Item " + Rnd.Next(0, 1000)
7071
};
7172

7273
_context.Posts.Add(post);

test/System.Linq.Dynamic.Core.Tests/Helpers/Entities/Post.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,6 @@ public class Post
2222
public DateTime PostDate { get; set; }
2323

2424
public DateTime? CloseDate { get; set; }
25+
26+
public string? Item { get; set; }
2527
}

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ namespace System.Linq.Dynamic.Core.Tests
2020
{
2121
public partial class QueryableTests
2222
{
23+
[DynamicLinqType]
24+
public class ClassWithItem
25+
{
26+
public string? Item { get; set; }
27+
28+
public int Value { get; set; }
29+
}
30+
2331
[DynamicLinqType]
2432
public class Example
2533
{
@@ -536,5 +544,24 @@ public void Select_Dynamic_StringConcatDifferentTypes(string expression, string
536544
// Act
537545
queryable.Select(config, expression).ToDynamicArray<string>()[0].Should().Be(expectedResult);
538546
}
547+
548+
[Fact]
549+
public void Select_Dynamic_ClassWithItemProperty()
550+
{
551+
// Arrange
552+
var data = new []
553+
{
554+
new ClassWithItem { Item = "Value1", Value = 1 },
555+
new ClassWithItem { Item = "Value2", Value = 2 }
556+
};
557+
var queryable = data.AsQueryable();
558+
559+
// Act
560+
var result = queryable.Select(x => new {x.Item, x.Value }).ToArray();
561+
var resultDynamic = queryable.Select("new (Item, Value)").ToDynamicArray();
562+
563+
// Assert
564+
resultDynamic.Should().BeEquivalentTo(result);
565+
}
539566
}
540567
}

0 commit comments

Comments
 (0)