diff --git a/System.Linq.Dynamic.Core.sln.DotSettings b/System.Linq.Dynamic.Core.sln.DotSettings index 766d5f30..efa46be9 100644 --- a/System.Linq.Dynamic.Core.sln.DotSettings +++ b/System.Linq.Dynamic.Core.sln.DotSettings @@ -1,5 +1,6 @@  EF + IIF IL UTC WASM diff --git a/src/System.Linq.Dynamic.Core/AnyOfTypes/AnyOfTypes.g.cs b/src/System.Linq.Dynamic.Core/AnyOfTypes/AnyOfTypes.g.cs index 62400494..046c0ca1 100644 --- a/src/System.Linq.Dynamic.Core/AnyOfTypes/AnyOfTypes.g.cs +++ b/src/System.Linq.Dynamic.Core/AnyOfTypes/AnyOfTypes.g.cs @@ -11,6 +11,6 @@ namespace AnyOfTypes { internal enum AnyOfType { - Undefined = 0, First, Second + Undefined = 0, First, Second, Third } } \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/AnyOfTypes/AnyOf_2.g.cs b/src/System.Linq.Dynamic.Core/AnyOfTypes/AnyOf_2.g.cs index c2a62b99..bb08aafe 100644 --- a/src/System.Linq.Dynamic.Core/AnyOfTypes/AnyOf_2.g.cs +++ b/src/System.Linq.Dynamic.Core/AnyOfTypes/AnyOf_2.g.cs @@ -7,6 +7,8 @@ // //------------------------------------------------------------------------------ +#pragma warning disable CS1591 + using System; using System.Diagnostics; using System.Collections.Generic; @@ -14,7 +16,7 @@ namespace AnyOfTypes { [DebuggerDisplay("{_thisType}, AnyOfType = {_currentType}; Type = {_currentValueType?.Name}; Value = '{ToString()}'")] - internal struct AnyOf + internal struct AnyOf : IEquatable> { private readonly string _thisType => $"AnyOf<{typeof(TFirst).Name}, {typeof(TSecond).Name}>"; private readonly int _numberOfTypes; @@ -124,23 +126,23 @@ public override int GetHashCode() return HashCodeCalculator.GetHashCode(fields); } - private bool Equals(AnyOf other) + public bool Equals(AnyOf other) { return _currentType == other._currentType && _numberOfTypes == other._numberOfTypes && EqualityComparer.Default.Equals(_currentValue, other._currentValue) && - EqualityComparer.Default.Equals(_first, other._first) && - EqualityComparer.Default.Equals(_second, other._second); + EqualityComparer.Default.Equals(_first, other._first) && + EqualityComparer.Default.Equals(_second, other._second); } public static bool operator ==(AnyOf obj1, AnyOf obj2) { - return obj1.Equals(obj2); + return EqualityComparer>.Default.Equals(obj1, obj2); } public static bool operator !=(AnyOf obj1, AnyOf obj2) { - return !obj1.Equals(obj2); + return !(obj1 == obj2); } public override bool Equals(object obj) diff --git a/src/System.Linq.Dynamic.Core/AnyOfTypes/AnyOf_3.g.cs b/src/System.Linq.Dynamic.Core/AnyOfTypes/AnyOf_3.g.cs new file mode 100644 index 00000000..9ff928da --- /dev/null +++ b/src/System.Linq.Dynamic.Core/AnyOfTypes/AnyOf_3.g.cs @@ -0,0 +1,189 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/StefH/AnyOf. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +#pragma warning disable CS1591 + +using System; +using System.Diagnostics; +using System.Collections.Generic; + +namespace AnyOfTypes +{ + [DebuggerDisplay("{_thisType}, AnyOfType = {_currentType}; Type = {_currentValueType?.Name}; Value = '{ToString()}'")] + internal struct AnyOf : IEquatable> + { + private readonly string _thisType => $"AnyOf<{typeof(TFirst).Name}, {typeof(TSecond).Name}, {typeof(TThird).Name}>"; + private readonly int _numberOfTypes; + private readonly object _currentValue; + private readonly Type _currentValueType; + private readonly AnyOfType _currentType; + + private readonly TFirst _first; + private readonly TSecond _second; + private readonly TThird _third; + + public readonly AnyOfType[] AnyOfTypes => new[] { AnyOfType.First, AnyOfType.Second, AnyOfType.Third }; + public readonly Type[] Types => new[] { typeof(TFirst), typeof(TSecond), typeof(TThird) }; + public bool IsUndefined => _currentType == AnyOfType.Undefined; + public bool IsFirst => _currentType == AnyOfType.First; + public bool IsSecond => _currentType == AnyOfType.Second; + public bool IsThird => _currentType == AnyOfType.Third; + + public static implicit operator AnyOf(TFirst value) => new AnyOf(value); + + public static implicit operator TFirst(AnyOf @this) => @this.First; + + public AnyOf(TFirst value) + { + _numberOfTypes = 3; + _currentType = AnyOfType.First; + _currentValue = value; + _currentValueType = typeof(TFirst); + _first = value; + _second = default; + _third = default; + } + + public TFirst First + { + get + { + Validate(AnyOfType.First); + return _first; + } + } + + public static implicit operator AnyOf(TSecond value) => new AnyOf(value); + + public static implicit operator TSecond(AnyOf @this) => @this.Second; + + public AnyOf(TSecond value) + { + _numberOfTypes = 3; + _currentType = AnyOfType.Second; + _currentValue = value; + _currentValueType = typeof(TSecond); + _second = value; + _first = default; + _third = default; + } + + public TSecond Second + { + get + { + Validate(AnyOfType.Second); + return _second; + } + } + + public static implicit operator AnyOf(TThird value) => new AnyOf(value); + + public static implicit operator TThird(AnyOf @this) => @this.Third; + + public AnyOf(TThird value) + { + _numberOfTypes = 3; + _currentType = AnyOfType.Third; + _currentValue = value; + _currentValueType = typeof(TThird); + _third = value; + _first = default; + _second = default; + } + + public TThird Third + { + get + { + Validate(AnyOfType.Third); + return _third; + } + } + + private void Validate(AnyOfType desiredType) + { + if (desiredType != _currentType) + { + throw new InvalidOperationException($"Attempting to get {desiredType} when {_currentType} is set"); + } + } + + public AnyOfType CurrentType + { + get + { + return _currentType; + } + } + + public object CurrentValue + { + get + { + return _currentValue; + } + } + + public Type CurrentValueType + { + get + { + return _currentValueType; + } + } + + public override int GetHashCode() + { + var fields = new object[] + { + _numberOfTypes, + _currentValue, + _currentType, + _first, + _second, + _third, + typeof(TFirst), + typeof(TSecond), + typeof(TThird), + }; + return HashCodeCalculator.GetHashCode(fields); + } + + public bool Equals(AnyOf other) + { + return _currentType == other._currentType && + _numberOfTypes == other._numberOfTypes && + EqualityComparer.Default.Equals(_currentValue, other._currentValue) && + EqualityComparer.Default.Equals(_first, other._first) && + EqualityComparer.Default.Equals(_second, other._second) && + EqualityComparer.Default.Equals(_third, other._third); + } + + public static bool operator ==(AnyOf obj1, AnyOf obj2) + { + return EqualityComparer>.Default.Equals(obj1, obj2); + } + + public static bool operator !=(AnyOf obj1, AnyOf obj2) + { + return !(obj1 == obj2); + } + + public override bool Equals(object obj) + { + return obj is AnyOf o && Equals(o); + } + + public override string ToString() + { + return IsUndefined ? null : $"{_currentValue}"; + } + } +} \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs b/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs index e2f7abd5..1c5af49a 100644 --- a/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs +++ b/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs @@ -1564,21 +1564,21 @@ internal static IOrderedQueryable InternalOrderBy(IQueryable source, ParsingConf { Check.NotNull(source); Check.NotNull(config); - Check.NotEmpty(ordering, nameof(ordering)); + Check.NotEmpty(ordering); - ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(source.ElementType, string.Empty, config.RenameEmptyParameterExpressionNames) }; - ExpressionParser parser = new ExpressionParser(parameters, ordering, args, config); - IList dynamicOrderings = parser.ParseOrdering(); + ParameterExpression[] parameters = [ParameterExpressionHelper.CreateParameterExpression(source.ElementType, string.Empty, config.RenameEmptyParameterExpressionNames)]; + var parser = new ExpressionParser(parameters, ordering, args, config, true); + var dynamicOrderings = parser.ParseOrdering(); - Expression queryExpr = source.Expression; + var queryExpr = source.Expression; - foreach (DynamicOrdering dynamicOrdering in dynamicOrderings) + foreach (var dynamicOrdering in dynamicOrderings) { if (comparer == null) { queryExpr = Expression.Call( typeof(Queryable), dynamicOrdering.MethodName, - new[] { source.ElementType, dynamicOrdering.Selector.Type }, + [source.ElementType, dynamicOrdering.Selector.Type], queryExpr, Expression.Quote(Expression.Lambda(dynamicOrdering.Selector, parameters))); } else @@ -1602,7 +1602,7 @@ internal static IOrderedQueryable InternalOrderBy(IQueryable source, ParsingConf queryExpr = Expression.Call( typeof(Queryable), dynamicOrdering.MethodName, - new[] { source.ElementType, dynamicOrdering.Selector.Type }, + [source.ElementType, dynamicOrdering.Selector.Type], queryExpr, Expression.Quote(Expression.Lambda(dynamicOrdering.Selector, parameters)), constant); } diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs index f4a60528..4f7a598e 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs @@ -22,7 +22,7 @@ namespace System.Linq.Dynamic.Core.Parser; /// public class ExpressionParser { - private static readonly string[] OutKeywords = { "out", "$out" }; + private static readonly string[] OutKeywords = ["out", "$out"]; private const string DiscardVariable = "_"; private const string MethodOrderBy = nameof(Queryable.OrderBy); @@ -41,6 +41,7 @@ public class ExpressionParser private readonly ITypeConverterFactory _typeConverterFactory; private readonly Dictionary _internals = new(); private readonly Dictionary _symbols; + private readonly bool _usedForOrderBy; private IDictionary? _externals; private ParameterExpression? _it; @@ -69,10 +70,12 @@ public class ExpressionParser /// The expression. /// The values. /// The parsing configuration. - public ExpressionParser(ParameterExpression[]? parameters, string expression, object?[]? values, ParsingConfig? parsingConfig) + /// Indicate that this instance will be used for parsing orderBy. Default value is false. + public ExpressionParser(ParameterExpression[]? parameters, string expression, object?[]? values, ParsingConfig? parsingConfig, bool usedForOrderBy = false) { - Check.NotEmpty(expression, nameof(expression)); + Check.NotEmpty(expression); + _usedForOrderBy = usedForOrderBy; _symbols = new Dictionary(parsingConfig is { IsCaseSensitive: true } ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase); _parsingConfig = parsingConfig ?? ParsingConfig.Default; @@ -98,7 +101,7 @@ public ExpressionParser(ParameterExpression[]? parameters, string expression, ob private void ProcessParameters(ParameterExpression[] parameters) { - foreach (ParameterExpression pe in parameters.Where(p => !string.IsNullOrEmpty(p.Name))) + foreach (var pe in parameters.Where(p => !string.IsNullOrEmpty(p.Name))) { AddSymbol(pe.Name!, pe); } @@ -201,8 +204,8 @@ internal IList ParseOrdering(bool forceThenBy = false) var orderings = new List(); while (true) { - Expression expr = ParseConditionalOperator(); - bool ascending = true; + var expr = ParseConditionalOperator(); + var ascending = true; if (TokenIdentifierIs("asc") || TokenIdentifierIs("ascending")) { _textParser.NextToken(); @@ -967,14 +970,13 @@ private Expression ParseIdentifier() { _textParser.ValidateToken(TokenId.Identifier); - var isValidKeyWord = _keywordsHelper.TryGetValue(_textParser.CurrentToken.Text, out var value); - - bool shouldPrioritizeType = true; + var isValidKeyWord = _keywordsHelper.TryGetValue(_textParser.CurrentToken.Text, out var keywordOrType); + var shouldPrioritizeType = true; - if (_parsingConfig.PrioritizePropertyOrFieldOverTheType && value is Type) + if (_parsingConfig.PrioritizePropertyOrFieldOverTheType && keywordOrType.IsThird) { - bool isPropertyOrField = _it != null && FindPropertyOrField(_it.Type, _textParser.CurrentToken.Text, false) != null; - bool hasSymbol = _symbols.ContainsKey(_textParser.CurrentToken.Text); + var isPropertyOrField = _it != null && FindPropertyOrField(_it.Type, _textParser.CurrentToken.Text, false) != null; + var hasSymbol = _symbols.ContainsKey(_textParser.CurrentToken.Text); if (isPropertyOrField || hasSymbol) { shouldPrioritizeType = false; @@ -983,57 +985,66 @@ private Expression ParseIdentifier() if (isValidKeyWord && shouldPrioritizeType) { - if (value is Type typeValue) + var keywordOrFunctionAllowed = !_usedForOrderBy || _usedForOrderBy && !_parsingConfig.RestrictOrderByToPropertyOrField; + if (!keywordOrFunctionAllowed) { - return ParseTypeAccess(typeValue, true); + throw ParseError(Res.UnknownPropertyOrField, _textParser.CurrentToken.Text, TypeHelper.GetTypeName(_it?.Type)); } - switch (value) + switch (keywordOrType.CurrentType) { - case KeywordsHelper.KEYWORD_IT: - case KeywordsHelper.SYMBOL_IT: - return ParseIt(); + case AnyOfType.First: + switch (keywordOrType.First) + { + case KeywordsHelper.KEYWORD_IT: + case KeywordsHelper.SYMBOL_IT: + return ParseIt(); - case KeywordsHelper.KEYWORD_PARENT: - case KeywordsHelper.SYMBOL_PARENT: - return ParseParent(); + case KeywordsHelper.KEYWORD_PARENT: + case KeywordsHelper.SYMBOL_PARENT: + return ParseParent(); - case KeywordsHelper.KEYWORD_ROOT: - case KeywordsHelper.SYMBOL_ROOT: - return ParseRoot(); + case KeywordsHelper.KEYWORD_ROOT: + case KeywordsHelper.SYMBOL_ROOT: + return ParseRoot(); - case KeywordsHelper.FUNCTION_IIF: - return ParseFunctionIif(); + case KeywordsHelper.FUNCTION_IIF: + return ParseFunctionIIF(); - case KeywordsHelper.FUNCTION_ISNULL: - return ParseFunctionIsNull(); + case KeywordsHelper.FUNCTION_ISNULL: + return ParseFunctionIsNull(); - case KeywordsHelper.FUNCTION_NEW: - if (_parsingConfig.DisallowNewKeyword) - { - throw ParseError(Res.NewOperatorIsNotAllowed); - } - return ParseNew(); + case KeywordsHelper.FUNCTION_NEW: + if (_parsingConfig.DisallowNewKeyword) + { + throw ParseError(Res.NewOperatorIsNotAllowed); + } + return ParseNew(); - case KeywordsHelper.FUNCTION_NULLPROPAGATION: - return ParseFunctionNullPropagation(); + case KeywordsHelper.FUNCTION_NULLPROPAGATION: + return ParseFunctionNullPropagation(); - case KeywordsHelper.FUNCTION_IS: - return ParseFunctionIs(); + case KeywordsHelper.FUNCTION_IS: + return ParseFunctionIs(); - case KeywordsHelper.FUNCTION_AS: - return ParseFunctionAs(); + case KeywordsHelper.FUNCTION_AS: + return ParseFunctionAs(); - case KeywordsHelper.FUNCTION_CAST: - return ParseFunctionCast(); - } + case KeywordsHelper.FUNCTION_CAST: + return ParseFunctionCast(); + } + break; - _textParser.NextToken(); + case AnyOfType.Second: + _textParser.NextToken(); + return keywordOrType.Second; - return (Expression)value; + case AnyOfType.Third: + return ParseTypeAccess(keywordOrType.Third, true); + } } - if (_symbols.TryGetValue(_textParser.CurrentToken.Text, out value) || + if (_symbols.TryGetValue(_textParser.CurrentToken.Text, out var value) || _externals != null && _externals.TryGetValue(_textParser.CurrentToken.Text, out value) || _internals.TryGetValue(_textParser.CurrentToken.Text, out value)) { @@ -1110,7 +1121,7 @@ private Expression ParseFunctionIsNull() } // iif(test, ifTrue, ifFalse) function - private Expression ParseFunctionIif() + private Expression ParseFunctionIIF() { int errorPos = _textParser.CurrentToken.Pos; _textParser.NextToken(); @@ -1118,7 +1129,7 @@ private Expression ParseFunctionIif() Expression[] args = ParseArgumentList(); if (args.Length != 3) { - throw ParseError(errorPos, Res.IifRequiresThreeArgs); + throw ParseError(errorPos, Res.IIFRequiresThreeArgs); } return GenerateConditional(args[0], args[1], args[2], false, errorPos); diff --git a/src/System.Linq.Dynamic.Core/Parser/IKeywordsHelper.cs b/src/System.Linq.Dynamic.Core/Parser/IKeywordsHelper.cs index ee60f36c..6fbd28bd 100644 --- a/src/System.Linq.Dynamic.Core/Parser/IKeywordsHelper.cs +++ b/src/System.Linq.Dynamic.Core/Parser/IKeywordsHelper.cs @@ -1,8 +1,9 @@ -using System.Diagnostics.CodeAnalysis; +using System.Linq.Expressions; +using AnyOfTypes; namespace System.Linq.Dynamic.Core.Parser; interface IKeywordsHelper { - bool TryGetValue(string name, [NotNullWhen(true)] out object? keyWordOrType); + bool TryGetValue(string name, out AnyOf keywordOrType); } \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/Parser/KeywordsHelper.cs b/src/System.Linq.Dynamic.Core/Parser/KeywordsHelper.cs index 2fa4ffdf..3ecccbd2 100644 --- a/src/System.Linq.Dynamic.Core/Parser/KeywordsHelper.cs +++ b/src/System.Linq.Dynamic.Core/Parser/KeywordsHelper.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Linq.Dynamic.Core.Validation; using System.Linq.Expressions; +using AnyOfTypes; namespace System.Linq.Dynamic.Core.Parser; @@ -26,7 +26,7 @@ internal class KeywordsHelper : IKeywordsHelper private readonly ParsingConfig _config; // Keywords are IgnoreCase - private readonly Dictionary _keywordMapping = new(StringComparer.OrdinalIgnoreCase) + private readonly Dictionary> _keywordMapping = new(StringComparer.OrdinalIgnoreCase) { { "true", Expression.Constant(true) }, { "false", Expression.Constant(false) }, @@ -46,10 +46,10 @@ internal class KeywordsHelper : IKeywordsHelper }; // PreDefined Types are not IgnoreCase - private static readonly Dictionary PreDefinedTypeMapping = new(); + private static readonly Dictionary PreDefinedTypeMapping = new(); // Custom DefinedTypes are not IgnoreCase - private readonly Dictionary _customTypeMapping = new(); + private readonly Dictionary _customTypeMapping = new(); static KeywordsHelper() { @@ -82,45 +82,45 @@ public KeywordsHelper(ParsingConfig config) } } - public bool TryGetValue(string name, [NotNullWhen(true)] out object? keyWordOrType) + public bool TryGetValue(string name, out AnyOf keywordOrType) { // 1. Try to get as keyword if (_keywordMapping.TryGetValue(name, out var keyWord)) { - keyWordOrType = keyWord; + keywordOrType = keyWord; return true; } // 2. Try to get as predefined shorttype ("bool", "char", ...) if (PredefinedTypesHelper.PredefinedTypesShorthands.TryGetValue(name, out var predefinedShortHandType)) { - keyWordOrType = predefinedShortHandType; + keywordOrType = predefinedShortHandType; return true; } // 3. Try to get as predefined type ("Boolean", "System.Boolean", ..., "DateTime", "System.DateTime", ...) if (PreDefinedTypeMapping.TryGetValue(name, out var predefinedType)) { - keyWordOrType = predefinedType; + keywordOrType = predefinedType; return true; } // 4. Try to get as an enum from the system namespace if (_config.SupportEnumerationsFromSystemNamespace && EnumerationsFromMscorlib.PredefinedEnumerationTypes.TryGetValue(name, out var predefinedEnumType)) { - keyWordOrType = predefinedEnumType; + keywordOrType = predefinedEnumType; return true; } // 5. Try to get as custom type if (_customTypeMapping.TryGetValue(name, out var customType)) { - keyWordOrType = customType; + keywordOrType = customType; return true; } // 6. Not found, return false - keyWordOrType = null; + keywordOrType = default; return false; } } \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/Parser/TypeFinder.cs b/src/System.Linq.Dynamic.Core/Parser/TypeFinder.cs index c3175069..0ee9f600 100644 --- a/src/System.Linq.Dynamic.Core/Parser/TypeFinder.cs +++ b/src/System.Linq.Dynamic.Core/Parser/TypeFinder.cs @@ -21,11 +21,11 @@ public TypeFinder(ParsingConfig parsingConfig, IKeywordsHelper keywordsHelper) { Check.NotEmpty(name); - _keywordsHelper.TryGetValue(name, out var type); + _keywordsHelper.TryGetValue(name, out var keywordOrType); - if (type is Type sameType) + if (keywordOrType.IsThird) { - return sameType; + return keywordOrType.Third; } if (expressions != null && TryResolveTypeUsingExpressions(name, expressions, out var resolvedType)) diff --git a/src/System.Linq.Dynamic.Core/Parser/TypeHelper.cs b/src/System.Linq.Dynamic.Core/Parser/TypeHelper.cs index 2cf80b2f..843732f7 100644 --- a/src/System.Linq.Dynamic.Core/Parser/TypeHelper.cs +++ b/src/System.Linq.Dynamic.Core/Parser/TypeHelper.cs @@ -414,9 +414,9 @@ public static string GetTypeName(Type? type) return "null"; } - Type baseType = GetNonNullableType(type); + var baseType = GetNonNullableType(type); - string name = baseType.Name; + var name = baseType.Name; if (type != baseType) { name += '?'; diff --git a/src/System.Linq.Dynamic.Core/ParsingConfig.cs b/src/System.Linq.Dynamic.Core/ParsingConfig.cs index cd58ef1a..43fc199a 100644 --- a/src/System.Linq.Dynamic.Core/ParsingConfig.cs +++ b/src/System.Linq.Dynamic.Core/ParsingConfig.cs @@ -280,4 +280,11 @@ public IQueryableAnalyzer QueryableAnalyzer /// Default value is StringLiteralParsingType.Default. /// public StringLiteralParsingType StringLiteralParsing { get; set; } = StringLiteralParsingType.Default; + + /// + /// When set to true, the parser will restrict the OrderBy method to only allow properties or fields. + /// + /// Default value is false. + /// + public bool RestrictOrderByToPropertyOrField { get; set; } = false; } \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/Res.cs b/src/System.Linq.Dynamic.Core/Res.cs index 4b074e08..246dd463 100644 --- a/src/System.Linq.Dynamic.Core/Res.cs +++ b/src/System.Linq.Dynamic.Core/Res.cs @@ -35,7 +35,7 @@ internal static class Res public const string IQueryableProviderNotAsync = "The provider for the source IQueryable doesn't implement IAsyncQueryProvider/IDbAsyncQueryProvider. Only providers that implement IAsyncQueryProvider/IDbAsyncQueryProvider can be used for Entity Framework asynchronous operations."; public const string IdentifierExpected = "Identifier expected"; public const string IdentifierImplementingInterfaceExpected = "Identifier implementing interface '{0}' expected"; - public const string IifRequiresThreeArgs = "The 'iif' function requires three arguments"; + public const string IIFRequiresThreeArgs = "The 'iif' function requires three arguments"; public const string IncompatibleOperand = "Operator '{0}' incompatible with operand type '{1}'"; public const string IncompatibleOperands = "Operator '{0}' incompatible with operand types '{1}' and '{2}'"; public const string IncompatibleTypes = "Types '{0}' and '{1}' are incompatible"; diff --git a/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.OrderBy.cs b/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.OrderBy.cs new file mode 100644 index 00000000..6355b188 --- /dev/null +++ b/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.OrderBy.cs @@ -0,0 +1,54 @@ +using System.Linq.Dynamic.Core.Exceptions; +using System.Linq.Dynamic.Core.Tests.Helpers.Entities; +using FluentAssertions; +using Xunit; + +namespace System.Linq.Dynamic.Core.Tests; + +public partial class EntitiesTests +{ + [Fact] + public void Entities_OrderBy_RestrictOrderByIsFalse() + { + // Act + var resultBlogs = _context.Blogs.OrderBy(b => true).ToArray(); + var dynamicResultBlogs = _context.Blogs.OrderBy("IIF(1 == 1, 1, 0)").ToDynamicArray(); + + // Assert + Assert.Equal(resultBlogs, dynamicResultBlogs); + } + + [Fact] + public void Entities_OrderBy_RestrictOrderByIsTrue_ValidExpressionShouldNotThrow() + { + // Arrange + var config = new ParsingConfig { RestrictOrderByToPropertyOrField = true }; + + // Act 1 + var resultBlogs = _context.Blogs.OrderBy(b => b.Name).ToArray(); + var dynamicResultBlogs = _context.Blogs.OrderBy(config, "Name").ToDynamicArray(); + + // Assert 1 + Assert.Equal(resultBlogs, dynamicResultBlogs); + + // Act 2 + var resultPosts = _context.Posts.OrderBy(p => p.Blog.Name).ToArray(); + var dynamicResultPosts = _context.Posts.OrderBy(config, "Blog.Name").ToDynamicArray(); + + // Assert 2 + Assert.Equal(resultPosts, dynamicResultPosts); + } + + [Fact] + public void Entities_OrderBy_RestrictOrderByIsTrue_InvalidExpressionShouldThrow() + { + // Arrange + var config = new ParsingConfig { RestrictOrderByToPropertyOrField = true }; + + // Act + Action action = () => _context.Blogs.OrderBy(config, "IIF(1 == 1, 1, 0)"); + + // Assert + action.Should().Throw().WithMessage("No property or field 'IIF' exists in type 'Blog'"); + } +} \ 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 724127d4..f1d3a428 100644 --- a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Select.cs +++ b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Select.cs @@ -429,7 +429,7 @@ public void Select_Dynamic_RenameParameterExpression_Is_True() Check.That(result).Equals("System.Int32[].Select(it => (it * it))"); } -#if NET461 || NET5_0 || NET6_0 || NET7_0 || NET8_0 +#if NET461 || NET5_0 || NET6_0 || NET7_0 || NET8_0 || NET9_0 [Fact(Skip = "Fails sometimes in GitHub CI build")] #else [Fact]