diff --git a/src/System.Linq.Dynamic.Core/DynamicClass.cs b/src/System.Linq.Dynamic.Core/DynamicClass.cs index 5dee90d3..33f06aee 100644 --- a/src/System.Linq.Dynamic.Core/DynamicClass.cs +++ b/src/System.Linq.Dynamic.Core/DynamicClass.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Dynamic; using System.Reflection; +using System.Runtime.CompilerServices; namespace System.Linq.Dynamic.Core; @@ -18,6 +19,8 @@ namespace System.Linq.Dynamic.Core; /// public abstract class DynamicClass : DynamicObject { + internal const string IndexerName = "System_Linq_Dynamic_Core_DynamicClass_Indexer"; + private Dictionary? _propertiesDictionary; private Dictionary Properties @@ -99,11 +102,12 @@ public void SetDynamicPropertyValue(string propertyName, object value) /// The . /// The name. /// Value from the property. + [IndexerName(IndexerName)] public object? this[string name] { get { - return Properties.TryGetValue(name, out object? result) ? result : null; + return Properties.TryGetValue(name, out var result) ? result : null; } set @@ -153,7 +157,7 @@ public override bool TryGetMember(GetMemberBinder binder, out object? result) /// public override bool TrySetMember(SetMemberBinder binder, object? value) { - string name = binder.Name; + var name = binder.Name; if (Properties.ContainsKey(name)) { Properties[name] = value; diff --git a/src/System.Linq.Dynamic.Core/DynamicClass.net35.cs b/src/System.Linq.Dynamic.Core/DynamicClass.net35.cs index 67237fab..d585f231 100644 --- a/src/System.Linq.Dynamic.Core/DynamicClass.net35.cs +++ b/src/System.Linq.Dynamic.Core/DynamicClass.net35.cs @@ -6,6 +6,8 @@ namespace System.Linq.Dynamic.Core; /// public abstract class DynamicClass { + internal const string IndexerName = "System_Linq_Dynamic_Core_DynamicClass_Indexer"; + /// /// Gets the dynamic property by name. /// diff --git a/src/System.Linq.Dynamic.Core/DynamicClass.uap.cs b/src/System.Linq.Dynamic.Core/DynamicClass.uap.cs index 6f6486c4..8778de98 100644 --- a/src/System.Linq.Dynamic.Core/DynamicClass.uap.cs +++ b/src/System.Linq.Dynamic.Core/DynamicClass.uap.cs @@ -1,6 +1,7 @@ #if UAP10_0 using System.Collections.Generic; using System.Dynamic; +using System.Runtime.CompilerServices; namespace System.Linq.Dynamic.Core; @@ -9,6 +10,8 @@ namespace System.Linq.Dynamic.Core; /// public class DynamicClass : DynamicObject { + internal const string IndexerName = "System_Linq_Dynamic_Core_DynamicClass_Indexer"; + private readonly Dictionary _properties = new(); /// @@ -31,6 +34,7 @@ public DynamicClass(params KeyValuePair[] propertylist) /// /// The name. /// Value from the property. + [IndexerName(IndexerName)] public object this[string name] { get diff --git a/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs b/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs index b3ff3092..9f1a5772 100644 --- a/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs +++ b/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs @@ -2849,7 +2849,7 @@ private static TResult ConvertResultIfNeeded(object result) private static LambdaExpression EnsureLambdaExpressionReturnsObject(LambdaExpression lambdaExpression) { - if (!lambdaExpression.GetReturnType().GetTypeInfo().IsSubclassOf(typeof(DynamicClass))) + if (!TypeHelper.IsDynamicClass(lambdaExpression.GetReturnType())) { return Expression.Lambda(Expression.Convert(lambdaExpression.Body, typeof(object)), lambdaExpression.Parameters.ToArray()); } diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs index a43b534e..be2a43c1 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs @@ -1580,7 +1580,7 @@ private Expression CreateNewExpression(List properties, List x.Name != "Item").ToArray(); + propertyInfos = propertyInfos.Where(x => x.Name != DynamicClass.IndexerName).ToArray(); } var propertyTypes = propertyInfos.Select(p => p.PropertyType).ToArray(); @@ -1906,7 +1906,7 @@ private Expression ParseMemberAccess(Type? type, Expression? expression, string? #if UAP10_0 || NETSTANDARD1_3 if (type == typeof(DynamicClass)) { - return Expression.MakeIndex(expression, typeof(DynamicClass).GetProperty("Item"), new[] { Expression.Constant(id) }); + return Expression.MakeIndex(expression!, typeof(DynamicClass).GetProperty(DynamicClass.IndexerName), [Expression.Constant(id)]); } #endif if (TryFindPropertyOrField(type!, id, expression, out var propertyOrFieldExpression)) @@ -1920,7 +1920,8 @@ private Expression ParseMemberAccess(Type? type, Expression? expression, string? if (!_parsingConfig.DisableMemberAccessToIndexAccessorFallback && extraCheck) { - var indexerMethod = expression?.Type.GetMethod("get_Item", new[] { typeof(string) }); + var indexerName = TypeHelper.IsDynamicClass(type!) ? DynamicClass.IndexerName : "Item"; + var indexerMethod = expression?.Type.GetMethod($"get_{indexerName}", [typeof(string)]); if (indexerMethod != null) { return Expression.Call(expression, indexerMethod, Expression.Constant(id)); diff --git a/src/System.Linq.Dynamic.Core/Parser/TypeHelper.cs b/src/System.Linq.Dynamic.Core/Parser/TypeHelper.cs index 6ffd2a19..19002c4f 100644 --- a/src/System.Linq.Dynamic.Core/Parser/TypeHelper.cs +++ b/src/System.Linq.Dynamic.Core/Parser/TypeHelper.cs @@ -6,6 +6,11 @@ namespace System.Linq.Dynamic.Core.Parser; internal static class TypeHelper { + internal static bool IsDynamicClass(Type type) + { + return type == typeof(DynamicClass) || type.GetTypeInfo().IsSubclassOf(typeof(DynamicClass)); + } + internal static bool TryGetAsEnumerable(Type type, [NotNullWhen(true)] out Type? enumerableType) { if (type.IsArray) diff --git a/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.Select.cs b/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.Select.cs index 6626d842..87976802 100644 --- a/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.Select.cs +++ b/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.Select.cs @@ -164,4 +164,15 @@ public void Entities_Select_DynamicClass_And_Select_DynamicClass() dynamicResult.Should().BeEquivalentTo([1000, 1001]); } + + [Fact] + public void Entities_Select_ClassWithItemProperty() + { + // Act + var result = _context.Posts.Select(x => new { x.Item, x.BlogId }).ToArray(); + var resultDynamic = _context.Posts.Select("new (Item, BlogId)").ToDynamicArray(); + + // Assert + resultDynamic.Should().BeEquivalentTo(result); + } } \ No newline at end of file diff --git a/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.cs b/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.cs index 7ea211e4..96af723a 100644 --- a/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.cs @@ -11,7 +11,7 @@ namespace System.Linq.Dynamic.Core.Tests; public partial class EntitiesTests : IClassFixture { - private static readonly Random Rnd = new Random(1); + private static readonly Random Rnd = new(1); private readonly BlogContext _context; @@ -66,7 +66,8 @@ private void InternalPopulateTestData() Content = "My Content", PostDate = postDate, CloseDate = Rnd.Next(0, 10) < 5 ? postDate.AddDays(1) : null, - NumberOfReads = Rnd.Next(0, 5000) + NumberOfReads = Rnd.Next(0, 5000), + Item = "Item " + Rnd.Next(0, 1000) }; _context.Posts.Add(post); diff --git a/test/System.Linq.Dynamic.Core.Tests/Helpers/Entities/Post.cs b/test/System.Linq.Dynamic.Core.Tests/Helpers/Entities/Post.cs index 00d2e9a6..5883ab4a 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Helpers/Entities/Post.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Helpers/Entities/Post.cs @@ -22,4 +22,6 @@ public class Post public DateTime PostDate { get; set; } public DateTime? CloseDate { get; set; } + + public string? Item { get; set; } } \ No newline at end of file diff --git a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Select.cs b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Select.cs index 0383b1c2..364419a3 100644 --- a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Select.cs +++ b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Select.cs @@ -20,6 +20,14 @@ namespace System.Linq.Dynamic.Core.Tests { public partial class QueryableTests { + [DynamicLinqType] + public class ClassWithItem + { + public string? Item { get; set; } + + public int Value { get; set; } + } + [DynamicLinqType] public class Example { @@ -536,5 +544,24 @@ public void Select_Dynamic_StringConcatDifferentTypes(string expression, string // Act queryable.Select(config, expression).ToDynamicArray()[0].Should().Be(expectedResult); } + + [Fact] + public void Select_Dynamic_ClassWithItemProperty() + { + // Arrange + var data = new [] + { + new ClassWithItem { Item = "Value1", Value = 1 }, + new ClassWithItem { Item = "Value2", Value = 2 } + }; + var queryable = data.AsQueryable(); + + // Act + var result = queryable.Select(x => new {x.Item, x.Value }).ToArray(); + var resultDynamic = queryable.Select("new (Item, Value)").ToDynamicArray(); + + // Assert + resultDynamic.Should().BeEquivalentTo(result); + } } } \ No newline at end of file