diff --git a/src/System.Linq.Dynamic.Core/DynamicClassFactory.cs b/src/System.Linq.Dynamic.Core/DynamicClassFactory.cs index 94ea978e..e6152af9 100644 --- a/src/System.Linq.Dynamic.Core/DynamicClassFactory.cs +++ b/src/System.Linq.Dynamic.Core/DynamicClassFactory.cs @@ -264,7 +264,10 @@ public static Type CreateType([NotNull] IList properties, bool ilgeneratorToString.Emit(OpCodes.Pop); } - if (createParameterCtor) + // Only create the default and with params constructor when there are any params. + // Otherwise default constructor is not needed because it matches the default + // one provided by the runtime when no constructor is present + if (createParameterCtor && names.Any()) { // .ctor default ConstructorBuilder constructorDef = tb.DefineConstructor(MethodAttributes.Public | MethodAttributes.HideBySig, CallingConventions.HasThis, EmptyTypes); diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs index a4341b54..f884bda1 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs @@ -1531,11 +1531,13 @@ Expression ParseMemberAccess(Type type, Expression instance) return Expression.Dynamic(new DynamicGetMemberBinder(id), type, instance); } #endif - - MethodInfo indexerMethod = instance.Type.GetMethod("get_Item", new[] { typeof(string) }); - if (indexerMethod != null) + if (!_parsingConfig.DisableMemberAccessToIndexAccessorFallback) { - return Expression.Call(instance, indexerMethod, Expression.Constant(id)); + MethodInfo indexerMethod = instance.Type.GetMethod("get_Item", new[] { typeof(string) }); + if (indexerMethod != null) + { + return Expression.Call(instance, indexerMethod, Expression.Constant(id)); + } } if (_textParser.CurrentToken.Id == TokenId.Lambda && _it.Type == type) diff --git a/src/System.Linq.Dynamic.Core/ParsingConfig.cs b/src/System.Linq.Dynamic.Core/ParsingConfig.cs index 2a910139..504e3417 100644 --- a/src/System.Linq.Dynamic.Core/ParsingConfig.cs +++ b/src/System.Linq.Dynamic.Core/ParsingConfig.cs @@ -103,5 +103,12 @@ public IExpressionPromoter ExpressionPromoter /// Renames the (Typed)ParameterExpression empty Name to a the correct supplied name from `it`. Default value is false. /// public bool RenameParameterExpression { get; set; } = false; + + /// + /// By default when a member is not found in a type and the type has a string based index accessor it will be parsed as an index accessor. Use + /// this flag to disable this behaviour and have parsing fail when parsing an expression + /// where a member access on a non existing member happens. Default value is false. + /// + public bool DisableMemberAccessToIndexAccessorFallback { get; set; } = false; } } diff --git a/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.Select.cs b/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.Select.cs index b62ed965..e443f858 100644 --- a/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.Select.cs +++ b/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.Select.cs @@ -1,5 +1,7 @@ using System.Collections; +using System.Linq.Dynamic.Core.Exceptions; using System.Linq.Dynamic.Core.Tests.Helpers.Entities; +using MongoDB.Bson; #if EFCORE using Microsoft.EntityFrameworkCore; #else @@ -48,6 +50,42 @@ public void Entities_Select_SingleColumn() Assert.Equal(expected, test); } + [Fact] + public void Entities_Select_EmptyObject() + { + ParsingConfig config = ParsingConfig.Default; + config.EvaluateGroupByAtDatabase = true; + + //Arrange + PopulateTestData(5, 0); + + var expected = _context.Blogs.Select(x => new {}).ToList(); + + //Act + var test = _context.Blogs.GroupBy(config, "BlogId", "new()").Select("new()").ToList(); + + //Assert + Assert.Equal(expected.ToJson(), test.ToJson()); + } + + [Fact] + public void Entities_Select_BrokenObject() + { + ParsingConfig config = ParsingConfig.Default; + config.DisableMemberAccessToIndexAccessorFallback = false; + + // Silently creates something that will later fail on materialization + var test = _context.Blogs.Select(config, "new(~.BlogId)"); + test = test.Select(config, "new(nonexistentproperty as howcanthiswork)"); + + // Will fail when creating the expression + config.DisableMemberAccessToIndexAccessorFallback = true; + Assert.ThrowsAny(() => + { + test = test.Select(config, "new(nonexistentproperty as howcanthiswork)"); + }); + } + [Fact] public void Entities_Select_MultipleColumn() { @@ -108,4 +146,4 @@ public void Entities_Select_BlogAndPosts() } } } -} \ No newline at end of file +}