diff --git a/src-console/ConsoleApp_net10/Program.cs b/src-console/ConsoleApp_net10/Program.cs index 6108ceb0..822dfb0c 100644 --- a/src-console/ConsoleApp_net10/Program.cs +++ b/src-console/ConsoleApp_net10/Program.cs @@ -38,10 +38,24 @@ public class GroupedSalesData public int GroupLevel { get; set; } } +class MyEntity +{ + // Factory method to create a list of MyEntity objects from a given list of ids + public static IEnumerable CreateList(IEnumerable ids) + { + foreach (var id in ids) yield return new MyEntity { Id = id }; + } + + public int Id { get; set; } +} + class Program { static void Main(string[] args) { + Issue987(); + return; + Issue918(); return; @@ -72,6 +86,52 @@ static void Main(string[] args) Dynamic(); } + private static void Issue987() + { + var list = new List(); + for (int i = 0; i < 10000; i++) + list.Add(new MyEntity { Id = i }); + + var test1 = list.AsQueryable() + .Where("Id in (9495, 9496, 9498, 9500, 9501, 9503, 9505, 9508, 9509, 9510, 9511, 9514, 9515, 9517, 9518, 9519, 9520, 9521, 9523, 9524, 9525, 9526, 9527, 9528, 9529, 9530, 9531, 9532, 9533, 9534, 9535, 9536, 9538, 9539, 9540, 9541, 9542, 9543, 9544, 9545, 9546, 9547, 9548, 9549, 9550, 9552, 9554, 9556, 9557, 9558, 9559, 9560, 9561, 9562, 9563, 9565, 9567, 9569, 9570, 9575, 9576, 9577, 9578, 9579, 9580, 9581, 9582, 9583, 9584, 9585, 9586, 9587, 9588, 9589, 9590, 9591, 9592, 9593, 9594, 9595, 9596, 9597, 9598, 9599, 9600, 9601, 9602, 9603, 9604, 9605, 9606, 9607, 9608, 9609, 9610, 9611, 9612, 9613, 9614, 9615, 9616, 9617, 9618, 9619, 9620, 9621, 9622, 9623, 9624, 9625, 9626, 9627, 9628, 9629)") + .ToList(); + + Console.WriteLine("Number of elements : " + test1.Count); + + //the list of ids that were actually used in our application and resulted in the discovery of this bug + var originalIdList = new List() { 9495, 9496, 9498, 9500, 9501, 9503, 9505, 9508, 9509, 9510, 9511, 9514, 9515, 9517, 9518, 9519, 9520, 9521, 9523, 9524, 9525, 9526, 9527, 9528, 9529, 9530, 9531, 9532, 9533, 9534, 9535, 9536, 9538, 9539, 9540, 9541, 9542, 9543, 9544, 9545, 9546, 9547, 9548, 9549, 9550, 9552, 9554, 9556, 9557, 9558, 9559, 9560, 9561, 9562, 9563, 9565, 9567, 9569, 9570, 9575, 9576, 9577, 9578, 9579, 9580, 9581, 9582, 9583, 9584, 9585, 9586, 9587, 9588, 9589, 9590, 9591, 9592, 9593, 9594, 9595, 9596, 9597, 9598, 9599, 9600, 9601, 9602, 9603, 9604, 9605, 9606, 9607, 9608, 9609, 9610, 9611, 9612, 9613, 9614, 9615, 9616, 9617, 9618, 9619, 9620, 9621, 9622, 9623, 9624, 9625, 9626, 9627, 9628, 9629 }; + //list of ids also starting at 9495, but without gaps + var adjacentIdList = Enumerable.Range(9495, 114); + //original list starting at Id1 + var originalIdListStartingAt1 = originalIdList.Select(id => id - 9494); + //list with gaps of 1 + var listWithGapsOf1 = Enumerable.Range(1, 114).Select(id => id * 2); + //list with gaps of 2 + var listWithGapsOf2 = Enumerable.Range(1, 114).Select(id => id * 3); + //list with gaps of 3 + var listWithGapsOf3 = Enumerable.Range(1, 114).Select(id => id * 4); + //list with gaps of 4 + var listWithGapsOf4 = Enumerable.Range(1, 114).Select(id => id * 5); + + //list of 10.000 entities , with ids starting at 0 + var entityList = MyEntity.CreateList(Enumerable.Range(1, 10_000)); + + //filter the list of entities by the list of ids using dynamic linq and write the number of elements in the filtered list to the console + static void Filter(IEnumerable entities, IEnumerable ids) + { + var filtered = entities.AsQueryable().Where($"Id in ({string.Join(',', ids)})").ToList(); + Console.WriteLine("Number of elements : " + filtered.Count); + } + + Filter(entityList, originalIdList); + Filter(entityList, adjacentIdList); + Filter(entityList, originalIdListStartingAt1); + Filter(entityList, listWithGapsOf1); + Filter(entityList, listWithGapsOf2); + Filter(entityList, listWithGapsOf3); + Filter(entityList, listWithGapsOf4); + } + private static void Issue918() { var persons = new DataTable(); diff --git a/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs b/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs index 9f1a5772..e15ecdc8 100644 --- a/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs +++ b/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs @@ -24,7 +24,7 @@ namespace System.Linq.Dynamic.Core [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")] public static class DynamicQueryableExtensions { -#if !(SILVERLIGHT) +#if !SILVERLIGHT private static readonly TraceSource TraceSource = new(nameof(DynamicQueryableExtensions)); #endif @@ -34,7 +34,7 @@ private static Expression OptimizeExpression(Expression expression) { var optimized = ExtensibilityPoint.QueryOptimizer(expression); -#if !(SILVERLIGHT) +#if !SILVERLIGHT if (optimized != expression) { TraceSource.TraceEvent(TraceEventType.Verbose, 0, "Expression before : {0}", expression); @@ -2094,7 +2094,7 @@ public static IQueryable SelectMany( string collectionParameterName, string resultParameterName, object?[]? collectionSelectorArgs = null, - params object[]? resultSelectorArgs) + params object?[]? resultSelectorArgs) { Check.NotNull(source); Check.NotNull(config); @@ -2682,7 +2682,6 @@ public static IQueryable Where(this IQueryable source, ParsingConfig config, str bool createParameterCtor = SupportsLinqToObjects(config, source); LambdaExpression lambda = DynamicExpressionParser.ParseLambda(config, createParameterCtor, source.ElementType, null, predicate, args); - var optimized = OptimizeExpression(Expression.Call(typeof(Queryable), nameof(Queryable.Where), [source.ElementType], source.Expression, Expression.Quote(lambda))); return source.Provider.CreateQuery(optimized); } diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs index 2b830a1e..29223675 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs @@ -156,6 +156,37 @@ public Expression GenerateNotEqual(Expression left, Expression right) return Expression.NotEqual(left, right); } + public Expression GenerateBinaryOrElseTree(IList expressions) + { + Check.NotNullOrEmpty(expressions); + + if (expressions.Count == 1) + { + return expressions[0]; + } + + var currentLevel = new List(expressions); + while (currentLevel.Count > 1) + { + var nextLevel = new List((currentLevel.Count + 1) / 2); + for (var i = 0; i < currentLevel.Count; i += 2) + { + if (i + 1 < currentLevel.Count) + { + nextLevel.Add(Expression.OrElse(currentLevel[i], currentLevel[i + 1])); + } + else + { + nextLevel.Add(currentLevel[i]); + } + } + + currentLevel = nextLevel; + } + + return currentLevel[0]; + } + public Expression GenerateGreaterThan(Expression left, Expression right) { TryConvertTypes(ref left, ref right); diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs index d93cfde4..2dd76e97 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs @@ -367,6 +367,12 @@ private Expression ParseIn() if (_textParser.CurrentToken.Id == TokenId.OpenParen) // literals (or other inline list) { + var values = new List(); + var comparisons = new List(); + Expression? containsLeft = null; + string? containsLeftText = null; + var canUseContains = true; + while (_textParser.CurrentToken.Id != TokenId.CloseParen) { _textParser.NextToken(); @@ -393,13 +399,26 @@ private Expression ParseIn() CheckAndPromoteOperands(typeof(IEqualitySignatures), TokenId.DoubleEqual, "==", ref left, ref right, token.Pos); } - if (accumulate.Type != typeof(bool)) + var equalsExpression = _expressionHelper.GenerateEqual(left, right); + comparisons.Add(equalsExpression); + + if (canUseContains && equalsExpression is BinaryExpression binaryExpression && binaryExpression.NodeType == ExpressionType.Equal) { - accumulate = _expressionHelper.GenerateEqual(left, right); + containsLeft ??= binaryExpression.Left; + containsLeftText ??= binaryExpression.Left.ToString(); + + if (containsLeft.Type != binaryExpression.Left.Type || !string.Equals(containsLeftText, binaryExpression.Left.ToString(), StringComparison.Ordinal) || binaryExpression.Right.Type != containsLeft.Type) + { + canUseContains = false; + } + else + { + values.Add(binaryExpression.Right); + } } else { - accumulate = Expression.OrElse(accumulate, _expressionHelper.GenerateEqual(left, right)); + canUseContains = false; } if (_textParser.CurrentToken.Id == TokenId.End) @@ -408,6 +427,17 @@ private Expression ParseIn() } } + if (canUseContains && containsLeft != null) + { + var typeArgs = new[] { containsLeft.Type }; + var args = new Expression[] { Expression.NewArrayInit(containsLeft.Type, values), containsLeft }; + accumulate = Expression.Call(typeof(Enumerable), nameof(Enumerable.Contains), typeArgs, args); + } + else + { + accumulate = _expressionHelper.GenerateBinaryOrElseTree(comparisons); + } + // Since this started with an open paren, make sure to move off the close _textParser.NextToken(); } @@ -1514,7 +1544,7 @@ private Expression ParseNew() { if (!propertyNames.Add(propName!)) { - throw ParseError(exprPos, Res.DuplicateIdentifier, propName); + throw ParseError(exprPos, Res.DuplicateIdentifier, propName!); } properties.Add(new DynamicProperty(propName!, expr.Type)); diff --git a/src/System.Linq.Dynamic.Core/Parser/IExpressionHelper.cs b/src/System.Linq.Dynamic.Core/Parser/IExpressionHelper.cs index 4e52949b..fecf495c 100644 --- a/src/System.Linq.Dynamic.Core/Parser/IExpressionHelper.cs +++ b/src/System.Linq.Dynamic.Core/Parser/IExpressionHelper.cs @@ -1,4 +1,5 @@ -using System.Diagnostics.CodeAnalysis; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; namespace System.Linq.Dynamic.Core.Parser; @@ -21,6 +22,8 @@ internal interface IExpressionHelper Expression GenerateNotEqual(Expression left, Expression right); + Expression GenerateBinaryOrElseTree(IList expressions); + Expression GenerateStringConcat(Expression left, Expression right); Expression GenerateSubtract(Expression left, Expression right); diff --git a/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodData.cs b/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodData.cs index b73f8666..27e35161 100644 --- a/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodData.cs +++ b/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodData.cs @@ -1,12 +1,13 @@ using System.Linq.Expressions; using System.Reflection; -namespace System.Linq.Dynamic.Core.Parser.SupportedMethods +namespace System.Linq.Dynamic.Core.Parser.SupportedMethods; + +internal class MethodData { - internal class MethodData - { - public MethodBase MethodBase { get; set; } - public ParameterInfo[] Parameters { get; set; } - public Expression[] Args { get; set; } - } -} + public MethodBase MethodBase { get; set; } + + public ParameterInfo[] Parameters { get; set; } + + public Expression[] Args { get; set; } +} \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/Validation/Check.cs b/src/System.Linq.Dynamic.Core/Validation/Check.cs index e0bbb32b..741e040a 100644 --- a/src/System.Linq.Dynamic.Core/Validation/Check.cs +++ b/src/System.Linq.Dynamic.Core/Validation/Check.cs @@ -10,7 +10,7 @@ internal static class Check { private const string ParsingConfigError = "The ParsingConfig should be provided as first argument to this method."; - public static object?[]? Args(object?[]? args, [CallerArgumentExpression("args")] string? parameterName = null) + public static object?[]? Args(object?[]? args, [CallerArgumentExpression(nameof(args))] string? parameterName = null) { if (args?.Any(a => a is ParsingConfig) == true) { @@ -20,7 +20,7 @@ internal static class Check return args; } - public static T Condition(T value, Predicate predicate, [CallerArgumentExpression("value")] string? parameterName = null) + public static T Condition(T value, Predicate predicate, [CallerArgumentExpression(nameof(value))] string? parameterName = null) { NotNull(predicate); @@ -34,7 +34,7 @@ public static T Condition(T value, Predicate predicate, [CallerArgumentExp return value; } - public static T NotNull(T value, [CallerArgumentExpression("value")] string? parameterName = null) + public static T NotNull(T value, [CallerArgumentExpression(nameof(value))] string? parameterName = null) { if (value is null) { @@ -59,7 +59,7 @@ public static T NotNull(T value, string parameterName, string propertyName) return value; } - public static IEnumerable NotNullOrEmpty(IEnumerable value, [CallerArgumentExpression("value")] string? parameterName = null) + public static IEnumerable NotNullOrEmpty(IEnumerable value, [CallerArgumentExpression(nameof(value))] string? parameterName = null) { IEnumerable result = NotNull(value, parameterName); @@ -75,10 +75,10 @@ public static IEnumerable NotNullOrEmpty(IEnumerable value, [CallerArgu return result; } - public static string NotEmpty(string? value, [CallerArgumentExpression("value")] string? parameterName = null) => + public static string NotEmpty(string? value, [CallerArgumentExpression(nameof(value))] string? parameterName = null) => NotNullOrWhiteSpace(value, parameterName); - public static string NotNullOrEmpty(string? value, [CallerArgumentExpression("value")] string? parameterName = null) + public static string NotNullOrEmpty(string? value, [CallerArgumentExpression(nameof(value))] string? parameterName = null) { if (value is null) { @@ -95,7 +95,7 @@ public static string NotNullOrEmpty(string? value, [CallerArgumentExpression("va return value; } - public static string NotNullOrWhiteSpace(string? value, [CallerArgumentExpression("value")] string? parameterName = null) + public static string NotNullOrWhiteSpace(string? value, [CallerArgumentExpression(nameof(value))] string? parameterName = null) { if (value is null) { @@ -112,7 +112,7 @@ public static string NotNullOrWhiteSpace(string? value, [CallerArgumentExpressio return value; } - public static IEnumerable HasNoNulls(IEnumerable value, [CallerArgumentExpression("value")] string? parameterName = null) + public static IEnumerable HasNoNulls(IEnumerable value, [CallerArgumentExpression(nameof(value))] string? parameterName = null) { if (value is null) { diff --git a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs index 5ad3338d..57b759ec 100644 --- a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs @@ -87,7 +87,7 @@ private class ComplexParseLambda1Result { public int? Age; public int TotalIncome; - public string Name; + public string? Name; } [DynamicLinqType] diff --git a/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.In.cs b/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.In.cs index 76a52796..12e5301c 100644 --- a/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.In.cs +++ b/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.In.cs @@ -1,4 +1,7 @@ -#if EFCORE +using System.Linq.Dynamic.Core.Tests.Helpers.Entities; + +#if EFCORE +using System.Collections.Generic; using Microsoft.EntityFrameworkCore; #else using System.Data.Entity; @@ -25,4 +28,40 @@ public void Entities_Where_In_And() // Assert Assert.Equal(expected, test); } + + [Fact] + public void Entities_Where_In_DifferentTypes() + { + // Arrange + var expected = _context.Blogs.Include(b => b.Posts).Where(b => new long[] { 1000, 1001, 1002 }.Contains(b.BlogLongId)).ToArray(); + + // Act + var test = _context.Blogs.Include(b => b.Posts).Where(@"BlogLongId in (1000, 1001, 1002)").ToArray(); + + // Assert + Assert.Equal(expected, test); + } + + [Fact] + public void Entities_Where_In_Issue987() + { + // Arrange + for (int i = 0; i < 10000; i++) + { + var blogText = new BlogText + { + Id = i + }; + _context.BlogTexts.Add(blogText); + } + _context.SaveChanges(); + + // Act + var test = _context.BlogTexts + .Where("Id in (9495, 9496, 9498, 9500, 9501, 9503, 9505, 9508, 9509, 9510, 9511, 9514, 9515, 9517, 9518, 9519, 9520, 9521, 9523, 9524, 9525, 9526, 9527, 9528, 9529, 9530, 9531, 9532, 9533, 9534, 9535, 9536, 9538, 9539, 9540, 9541, 9542, 9543, 9544, 9545, 9546, 9547, 9548, 9549, 9550, 9552, 9554, 9556, 9557, 9558, 9559, 9560, 9561, 9562, 9563, 9565, 9567, 9569, 9570, 9575, 9576, 9577, 9578, 9579, 9580, 9581, 9582, 9583, 9584, 9585, 9586, 9587, 9588, 9589, 9590, 9591, 9592, 9593, 9594, 9595, 9596, 9597, 9598, 9599, 9600, 9601, 9602, 9603, 9604, 9605, 9606, 9607, 9608, 9609, 9610, 9611, 9612, 9613, 9614, 9615, 9616, 9617, 9618, 9619, 9620, 9621, 9622, 9623, 9624, 9625, 9626, 9627, 9628, 9629)") + .ToList(); + + // Assert + Assert.Equal(114, test.Count); + } } \ No newline at end of file diff --git a/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.TakeWhile.cs b/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.TakeWhile.cs index 77ce308c..d0f72779 100644 --- a/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.TakeWhile.cs +++ b/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.TakeWhile.cs @@ -9,9 +9,6 @@ public partial class EntitiesTests [Fact(Skip = "not supported")] public void Entities_TakeWhile() { - // Arrange - const int total = 33; - // Act var expected = _context.Blogs.OrderBy(b => b.BlogId).TakeWhile(b => b.BlogId > 5).ToArray(); var result = _context.Blogs.OrderBy("BlogId").TakeWhile("b.BlogId > 5").ToDynamicArray(); diff --git a/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.cs b/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.cs index 96af723a..11b19731 100644 --- a/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.cs @@ -50,6 +50,7 @@ private void InternalPopulateTestData() X = i.ToString(), Name = "Blog" + (i + 1), BlogId = 1000 + i, + BlogLongId = int.MaxValue + i, Created = DateTime.Now.AddDays(-Rnd.Next(0, 100)) }; @@ -61,6 +62,7 @@ private void InternalPopulateTestData() var post = new Post { PostId = 10000 + i * 10 + j, + PostLongId = int.MaxValue + i * 10 + j, Blog = blog, Title = $"Blog {i + 1} - Post {j + 1}", Content = "My Content", @@ -83,10 +85,10 @@ private void InternalPopulateTestData() }; _context.Blogs.Add(singleBlog); - _context.Blogs.Add(new Blog { BlogId = 2000, X = "0", Name = "blog a", Created = DateTime.Now }); - _context.Blogs.Add(new Blog { BlogId = 2001, X = "0", Name = "blog b", Created = DateTime.Now }); - _context.Blogs.Add(new Blog { BlogId = 3000, X = "0", Name = "Blog1", Created = DateTime.Now, NullableInt = null }); - _context.Blogs.Add(new Blog { BlogId = 3001, X = "0", Name = "Blog2", Created = DateTime.Now, NullableInt = 5 }); + _context.Blogs.Add(new Blog { BlogId = 2000, BlogLongId = (long)int.MaxValue + 2000, X = "0", Name = "blog a", Created = DateTime.Now }); + _context.Blogs.Add(new Blog { BlogId = 2001, BlogLongId = (long)int.MaxValue + 2001, X = "0", Name = "blog b", Created = DateTime.Now }); + _context.Blogs.Add(new Blog { BlogId = 3000, BlogLongId = (long)int.MaxValue + 3000, X = "0", Name = "Blog1", Created = DateTime.Now, NullableInt = null }); + _context.Blogs.Add(new Blog { BlogId = 3001, BlogLongId = (long)int.MaxValue + 3001, X = "0", Name = "Blog2", Created = DateTime.Now, NullableInt = 5 }); _context.SaveChanges(); } diff --git a/test/System.Linq.Dynamic.Core.Tests/Helpers/Entities/Blog.cs b/test/System.Linq.Dynamic.Core.Tests/Helpers/Entities/Blog.cs index e9ad2dd1..d6db3911 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Helpers/Entities/Blog.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Helpers/Entities/Blog.cs @@ -10,6 +10,8 @@ public class Blog [DatabaseGenerated(DatabaseGeneratedOption.None)] public int BlogId { get; set; } + public long BlogLongId { get; set; } + public string? X { get; set; } public string Name { get; set; } diff --git a/test/System.Linq.Dynamic.Core.Tests/Helpers/Entities/BlogContext.cs b/test/System.Linq.Dynamic.Core.Tests/Helpers/Entities/BlogContext.cs index 0d4cfb85..2702f2ac 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Helpers/Entities/BlogContext.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Helpers/Entities/BlogContext.cs @@ -28,6 +28,8 @@ public void EnableLogging() public DbSet Blogs { get; set; } public DbSet Posts { get; set; } + + public DbSet BlogTexts { get; set; } } #else [DbConfigurationType(typeof(CodeConfig))] @@ -50,6 +52,8 @@ public BlogContext(string nameOrConnectionString) : base(nameOrConnectionString) public DbSet Blogs { get; set; } public DbSet Posts { get; set; } + + public DbSet BlogTexts { get; set; } } public class CodeConfig : DbConfiguration diff --git a/test/System.Linq.Dynamic.Core.Tests/Helpers/Entities/BlogText.cs b/test/System.Linq.Dynamic.Core.Tests/Helpers/Entities/BlogText.cs new file mode 100644 index 00000000..6d3412d6 --- /dev/null +++ b/test/System.Linq.Dynamic.Core.Tests/Helpers/Entities/BlogText.cs @@ -0,0 +1,13 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace System.Linq.Dynamic.Core.Tests.Helpers.Entities; + +public class BlogText +{ + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.None)] + public int Id { get; set; } + + public string? Text { get; set; } +} \ No newline at end of file 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 5883ab4a..929f9c44 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Helpers/Entities/Post.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Helpers/Entities/Post.cs @@ -9,6 +9,8 @@ public class Post [DatabaseGenerated(DatabaseGeneratedOption.None)] public int PostId { get; set; } + public long PostLongId { get; set; } + public string Title { get; set; } public string Content { get; set; } diff --git a/test/System.Linq.Dynamic.Core.Tests/Helpers/Models/BooleanVariable.cs b/test/System.Linq.Dynamic.Core.Tests/Helpers/Models/BooleanVariable.cs index 00516d11..8492e1b3 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Helpers/Models/BooleanVariable.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Helpers/Models/BooleanVariable.cs @@ -1,15 +1,10 @@ namespace System.Linq.Dynamic.Core.Tests.Helpers.Models; -public readonly struct BooleanVariable : IEquatable, IEquatable, IConvertible +public readonly struct BooleanVariable(bool innerValue) : IEquatable, IEquatable, IConvertible { - private readonly bool _innerValue; + private readonly bool _innerValue = innerValue; - public BooleanVariable(bool innerValue) - { - _innerValue = innerValue; - } - - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is bool val && Equals(val); } @@ -31,7 +26,7 @@ public override int GetHashCode() public static implicit operator bool(BooleanVariable v) => v._innerValue; - public static implicit operator BooleanVariable(bool b) => new BooleanVariable(b); + public static implicit operator BooleanVariable(bool b) => new(b); public static bool operator true(BooleanVariable v) => v._innerValue; @@ -64,82 +59,82 @@ public TypeCode GetTypeCode() return _innerValue.GetTypeCode(); } - public bool ToBoolean(IFormatProvider provider) + public bool ToBoolean(IFormatProvider? provider) { return ((IConvertible)_innerValue).ToBoolean(provider); } - public byte ToByte(IFormatProvider provider) + public byte ToByte(IFormatProvider? provider) { return ((IConvertible)_innerValue).ToByte(provider); } - public char ToChar(IFormatProvider provider) + public char ToChar(IFormatProvider? provider) { return ((IConvertible)_innerValue).ToChar(provider); } - public DateTime ToDateTime(IFormatProvider provider) + public DateTime ToDateTime(IFormatProvider? provider) { return ((IConvertible)_innerValue).ToDateTime(provider); } - public decimal ToDecimal(IFormatProvider provider) + public decimal ToDecimal(IFormatProvider? provider) { return ((IConvertible)_innerValue).ToDecimal(provider); } - public double ToDouble(IFormatProvider provider) + public double ToDouble(IFormatProvider? provider) { return ((IConvertible)_innerValue).ToDouble(provider); } - public short ToInt16(IFormatProvider provider) + public short ToInt16(IFormatProvider? provider) { return ((IConvertible)_innerValue).ToInt16(provider); } - public int ToInt32(IFormatProvider provider) + public int ToInt32(IFormatProvider? provider) { return ((IConvertible)_innerValue).ToInt32(provider); } - public long ToInt64(IFormatProvider provider) + public long ToInt64(IFormatProvider? provider) { return ((IConvertible)_innerValue).ToInt64(provider); } - public sbyte ToSByte(IFormatProvider provider) + public sbyte ToSByte(IFormatProvider? provider) { return ((IConvertible)_innerValue).ToSByte(provider); } - public float ToSingle(IFormatProvider provider) + public float ToSingle(IFormatProvider? provider) { return ((IConvertible)_innerValue).ToSingle(provider); } - public string ToString(IFormatProvider provider) + public string ToString(IFormatProvider? provider) { return _innerValue.ToString(provider); } - public object ToType(Type conversionType, IFormatProvider provider) + public object ToType(Type conversionType, IFormatProvider? provider) { return ((IConvertible)_innerValue).ToType(conversionType, provider); } - public ushort ToUInt16(IFormatProvider provider) + public ushort ToUInt16(IFormatProvider? provider) { return ((IConvertible)_innerValue).ToUInt16(provider); } - public uint ToUInt32(IFormatProvider provider) + public uint ToUInt32(IFormatProvider? provider) { return ((IConvertible)_innerValue).ToUInt32(provider); } - public ulong ToUInt64(IFormatProvider provider) + public ulong ToUInt64(IFormatProvider? provider) { return ((IConvertible)_innerValue).ToUInt64(provider); } diff --git a/test/System.Linq.Dynamic.Core.Tests/Helpers/Models/SnowflakeId.cs b/test/System.Linq.Dynamic.Core.Tests/Helpers/Models/SnowflakeId.cs index 659785b5..deff6937 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Helpers/Models/SnowflakeId.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Helpers/Models/SnowflakeId.cs @@ -1,57 +1,51 @@ -namespace System.Linq.Dynamic.Core.Tests.Helpers.Models +namespace System.Linq.Dynamic.Core.Tests.Helpers.Models; + +public readonly struct SnowflakeId(ulong value) : IEquatable { - public struct SnowflakeId : IEquatable - { - public bool Equals(SnowflakeId other) - { - return Value == other.Value; - } - - public override bool Equals(object obj) - { - return obj is SnowflakeId other && Equals(other); - } - - public override int GetHashCode() - { - return Value.GetHashCode(); - } - - public static bool operator ==(SnowflakeId left, SnowflakeId right) - { - return left.Equals(right); - } - - public static bool operator !=(SnowflakeId left, SnowflakeId right) - { - return !left.Equals(right); - } - - public static bool operator ==(SnowflakeId left, int right) - { - return (int)left.Value == right; - } - - public static bool operator !=(SnowflakeId left, int right) - { - return (int)left.Value != right; - } - - public static bool operator ==(SnowflakeId left, ulong right) - { - return left.Value == right; - } - - public static bool operator !=(SnowflakeId left, ulong right) - { - return left.Value != right; - } - - public ulong Value { get; } - - public SnowflakeId(ulong value) - { - Value = value; - } - } -} + public readonly bool Equals(SnowflakeId other) + { + return Value == other.Value; + } + + public override bool Equals(object? obj) + { + return obj is SnowflakeId other && Equals(other); + } + + public override int GetHashCode() + { + return Value.GetHashCode(); + } + + public static bool operator ==(SnowflakeId left, SnowflakeId right) + { + return left.Equals(right); + } + + public static bool operator !=(SnowflakeId left, SnowflakeId right) + { + return !left.Equals(right); + } + + public static bool operator ==(SnowflakeId left, int right) + { + return (int)left.Value == right; + } + + public static bool operator !=(SnowflakeId left, int right) + { + return (int)left.Value != right; + } + + public static bool operator ==(SnowflakeId left, ulong right) + { + return left.Value == right; + } + + public static bool operator !=(SnowflakeId left, ulong right) + { + return left.Value != right; + } + + public ulong Value { get; } = value; +} \ No newline at end of file diff --git a/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionHelperTests.cs b/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionHelperTests.cs index 41fa5d18..0bbdcd84 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionHelperTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionHelperTests.cs @@ -1,8 +1,6 @@ -using FluentAssertions; -using NFluent; -using System.Collections; -using System.Linq.Dynamic.Core.Parser; +using System.Linq.Dynamic.Core.Parser; using System.Linq.Expressions; +using FluentAssertions; using Xunit; namespace System.Linq.Dynamic.Core.Tests.Parser; @@ -33,8 +31,8 @@ public void ExpressionHelper_WrapConstantExpression_false() expressionHelper.WrapConstantExpression(ref expression); // Assert - Check.That(expression).IsInstanceOf(); - Check.That(expression.ToString()).Equals("\"test\""); + Assert.IsType(expression); + Assert.Equal("\"test\"", expression.ToString()); } [Fact] @@ -54,8 +52,8 @@ public void ExpressionHelper_WrapNullableConstantExpression_false() expressionHelper.WrapConstantExpression(ref expression); // Assert - Check.That(expression).IsInstanceOf(); - Check.That(expression.ToString()).Equals("42"); + Assert.IsType(expression); + Assert.Equal("42", expression.ToString()); } [Fact] @@ -76,8 +74,8 @@ public void ExpressionHelper_WrapConstantExpression_true() expressionHelper.WrapConstantExpression(ref expression); // Assert - Check.That(expression.GetType().FullName).Equals("System.Linq.Expressions.PropertyExpression"); - Check.That(expression.ToString()).Equals("value(System.Linq.Dynamic.Core.Parser.WrappedValue`1[System.String]).Value"); + Assert.Equal("System.Linq.Expressions.PropertyExpression", expression.GetType().FullName); + Assert.Equal("value(System.Linq.Dynamic.Core.Parser.WrappedValue`1[System.String]).Value", expression.ToString()); } [Fact] @@ -98,8 +96,8 @@ public void ExpressionHelper_WrapNullableConstantExpression_true() expressionHelper.WrapConstantExpression(ref expression); // Assert - Check.That(expression.GetType().FullName).Equals("System.Linq.Expressions.PropertyExpression"); - Check.That(expression.ToString()).Equals("value(System.Linq.Dynamic.Core.Parser.WrappedValue`1[System.Int32]).Value"); + Assert.Equal("System.Linq.Expressions.PropertyExpression", expression.GetType().FullName); + Assert.Equal("value(System.Linq.Dynamic.Core.Parser.WrappedValue`1[System.Int32]).Value", expression.ToString()); } [Fact] @@ -109,11 +107,11 @@ public void ExpressionHelper_OptimizeStringForEqualityIfPossible_Guid() string guidAsString = Guid.NewGuid().ToString(); // Act - Expression result = _sut.OptimizeStringForEqualityIfPossible(guidAsString, typeof(Guid)); + var result = _sut.OptimizeStringForEqualityIfPossible(guidAsString, typeof(Guid)); // Assert - Check.That(result).IsInstanceOf(); - Check.That(result.ToString()).Equals(guidAsString); + var ce = Assert.IsType(result); + ce.ToString().Equals(guidAsString); } [Fact] @@ -123,10 +121,10 @@ public void ExpressionHelper_OptimizeStringForEqualityIfPossible_Guid_Invalid() string guidAsString = "x"; // Act - Expression result = _sut.OptimizeStringForEqualityIfPossible(guidAsString, typeof(Guid)); + var result = _sut.OptimizeStringForEqualityIfPossible(guidAsString, typeof(Guid)); // Assert - Check.That(result).IsNull(); + Assert.Null(result); } [Fact] @@ -139,8 +137,8 @@ public void ExpressionHelper_TryGenerateAndAlsoNotNullExpression_Nested3NonNulla bool result = _sut.TryGenerateAndAlsoNotNullExpression(expression, true, out Expression generatedExpression); // Assert - Check.That(result).IsTrue(); - Check.That(generatedExpression.ToString()).IsEqualTo("((((x != null) AndAlso (x.Relation1 != null)) AndAlso (x.Relation1.Relation2 != null)) AndAlso (x => x.Relation1.Relation2.Id != null))"); + Assert.True(result); + Assert.Equal("((((x != null) AndAlso (x.Relation1 != null)) AndAlso (x.Relation1.Relation2 != null)) AndAlso (x => x.Relation1.Relation2.Id != null))", generatedExpression.ToString()); } [Fact] @@ -159,8 +157,8 @@ public void ExpressionHelper_TryGenerateAndAlsoNotNullExpression_Nested3NonNulla bool result = expressionHelper.TryGenerateAndAlsoNotNullExpression(expression, true, out Expression generatedExpression); // Assert - Check.That(result).IsTrue(); - Check.That(generatedExpression.ToString()).IsEqualTo("((((x != null) AndAlso (x.Relation1 != null)) AndAlso (x.Relation1.Relation2 != null)) AndAlso (x => x.Relation1.Relation2.Id != null))"); + Assert.True(result); + Assert.Equal("((((x != null) AndAlso (x.Relation1 != null)) AndAlso (x.Relation1.Relation2 != null)) AndAlso (x => x.Relation1.Relation2.Id != null))", generatedExpression.ToString()); } [Fact] @@ -173,8 +171,8 @@ public void ExpressionHelper_TryGenerateAndAlsoNotNullExpression_Nested1Nullable bool result = _sut.TryGenerateAndAlsoNotNullExpression(expression, true, out Expression generatedExpression); // Assert - Check.That(result).IsTrue(); - Check.That(generatedExpression.ToString()).IsEqualTo("((x != null) AndAlso (x => x.IdNullable != null))"); + Assert.True(result); + Assert.Equal("((x != null) AndAlso (x => x.IdNullable != null))", generatedExpression.ToString()); } [Fact] @@ -187,8 +185,8 @@ public void ExpressionHelper_TryGenerateAndAlsoNotNullExpression_Nested1Nullable bool result = _sut.TryGenerateAndAlsoNotNullExpression(expression, true, out Expression generatedExpression); // Assert - Check.That(result).IsTrue(); - Check.That(generatedExpression.ToString()).IsEqualTo("((x != null) AndAlso (x => x.S != null))"); + Assert.True(result); + Assert.Equal("((x != null) AndAlso (x => x.S != null))", generatedExpression.ToString()); } [Fact] @@ -201,8 +199,8 @@ public void ExpressionHelper_TryGenerateAndAlsoNotNullExpression_Nested3Nullable bool result = _sut.TryGenerateAndAlsoNotNullExpression(expression, false, out Expression generatedExpression); // Assert - Check.That(result).IsTrue(); - Check.That(generatedExpression.ToString()).IsEqualTo("(((x != null) AndAlso (x.Relation1 != null)) AndAlso (x.Relation1.Relation2 != null))"); + Assert.True(result); + Assert.Equal("(((x != null) AndAlso (x.Relation1 != null)) AndAlso (x.Relation1.Relation2 != null))", generatedExpression.ToString()); } [Fact] @@ -215,8 +213,8 @@ public void ExpressionHelper_TryGenerateAndAlsoNotNullExpression_Nested3Nullable bool result = _sut.TryGenerateAndAlsoNotNullExpression(expression, true, out Expression generatedExpression); // Assert - Check.That(result).IsTrue(); - Check.That(generatedExpression.ToString()).IsEqualTo("((((x != null) AndAlso (x.Relation1 != null)) AndAlso (x.Relation1.Relation2 != null)) AndAlso (x => x.Relation1.Relation2.IdNullable != null))"); + Assert.True(result); + Assert.Equal("((((x != null) AndAlso (x.Relation1 != null)) AndAlso (x.Relation1.Relation2 != null)) AndAlso (x => x.Relation1.Relation2.IdNullable != null))", generatedExpression.ToString()); } [Fact] @@ -247,9 +245,9 @@ public void ConvertAnyArrayToObjectArray_ShouldConvertIntArrayToObjectArray() expression.Should().NotBeNull(); var lambdaExpressionCompiled = Expression.Lambda(expression).Compile(); - var result = (object[]) lambdaExpressionCompiled.DynamicInvoke(); + var result = (object[])lambdaExpressionCompiled.DynamicInvoke(); - result.Should().HaveCount(array.Length).And.ContainInOrder((object) 1, (object) 2, (object) 3); + result.Should().HaveCount(array.Length).And.ContainInOrder((object)1, (object)2, (object)3); } class Item @@ -261,6 +259,7 @@ class Item class Relation1 { public int Id { get; set; } + public Relation2 Relation2 { get; set; } } @@ -270,6 +269,50 @@ class Relation2 public int? IdNullable { get; set; } - public string S { get; set; } + public string S { get; set; } = string.Empty; + } + + [Fact] + public void GenerateBinaryOrElseTree_With7Expressions() + { + // Arrange + // Build 7 equality comparisons: "it == 1", "it == 2", ... "it == 7" + var parameter = Expression.Parameter(typeof(int), "it"); + var comparisons = Enumerable.Range(1, 7) + .Select(i => (Expression)Expression.Equal(parameter, Expression.Constant(i))) + .ToList(); + + // Act + var result = _sut.GenerateBinaryOrElseTree(comparisons); + + // Assert - tree structure + // With 7 inputs the balanced binary tree should have depth ceil(log2(7)) = 3 + // Round 1 (7 nodes): (1||2), (3||4), (5||6), 7 => 4 nodes + // Round 2 (4 nodes): ((1||2)||(3||4)), ((5||6)||7) => 2 nodes + // Round 3 (2 nodes): (((1||2)||(3||4))||((5||6)||7)) => 1 node + result.Should().NotBeNull(); + result.NodeType.Should().Be(ExpressionType.OrElse); + + // Compile and verify correctness: lambda should return true for values 1-7, false otherwise + var lambda = Expression.Lambda>(result, parameter); + var compiled = lambda.Compile(); + + for (var i = 1; i <= 7; i++) + { + compiled(i).Should().BeTrue(because: $"value {i} is in the list"); + } + + compiled(0).Should().BeFalse(because: "0 is not in the list"); + compiled(8).Should().BeFalse(because: "8 is not in the list"); + + // Verify the tree is balanced: no OrElse node should have a depth difference > 1 + // by checking that the expression is not a degenerate left-linear chain: + // a left-linear chain would look like ((((((a||b)||c)||d)||e)||f)||g) + // the balanced tree root's left child should itself be an OrElse of two OrElse nodes + var rootOrElse = (BinaryExpression)result; + rootOrElse.Left.NodeType.Should().Be(ExpressionType.OrElse, + because: "a balanced tree root's left child should be an OrElse"); + rootOrElse.Right.NodeType.Should().Be(ExpressionType.OrElse, + because: "a balanced tree root's right child should be an OrElse"); } } \ No newline at end of file diff --git a/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.cs b/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.cs index 8ab3ff09..8e826518 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.cs @@ -3,6 +3,7 @@ using System.Linq.Dynamic.Core.Exceptions; using System.Linq.Dynamic.Core.Parser; using System.Linq.Dynamic.Core.Tests.Entities; +using System.Linq.Dynamic.Core.Tests.Helpers.Models; using System.Linq.Expressions; using FluentAssertions; using Moq; @@ -271,7 +272,7 @@ public void Parse_ParseMultipleInOperators() var parsedExpression = sut.Parse(null).ToString(); // Assert - Check.That(parsedExpression).Equals("(((((x.MainCompanyId == 1) OrElse (x.MainCompanyId == 2)) AndAlso ((x.Name == \"A\") OrElse (x.Name == \"B\"))) AndAlso x.Name.Contains(y)) AndAlso x.Name.Contains(z))"); + Check.That(parsedExpression).Equals("(((new [] {1, 2}.Contains(x.MainCompanyId) AndAlso new [] {\"A\", \"B\"}.Contains(x.Name)) AndAlso x.Name.Contains(y)) AndAlso x.Name.Contains(z))"); } [Fact] @@ -285,10 +286,27 @@ public void Parse_ParseMultipleInAndNotInOperators() var parsedExpression = sut.Parse(null).ToString(); // Assert - Check.That(parsedExpression).Equals("(((((x.MainCompanyId == 1) OrElse (x.MainCompanyId == 2)) AndAlso Not(((x.Name == \"A\") OrElse (x.Name == \"B\")))) AndAlso x.Name.Contains(y)) AndAlso Not(x.Name.Contains(z)))"); + Check.That(parsedExpression).Equals("(((new [] {1, 2}.Contains(x.MainCompanyId) AndAlso Not(new [] {\"A\", \"B\"}.Contains(x.Name))) AndAlso x.Name.Contains(y)) AndAlso Not(x.Name.Contains(z)))"); } + [Fact] + public void Parse_In_FallsBackTo_OrElse_When_ContainsCannotBeUsed() + { + // Arrange + var values = new[] { new SnowflakeId(1), new SnowflakeId(100), new SnowflakeId(5) }.AsQueryable(); + + // Act + var query = values.Where("it in (100, 5UL)"); + var expressionText = query.Expression.ToString(); + var result = query.ToArray(); + + // Assert + Assert.Contains("OrElse", expressionText); + Assert.DoesNotContain(".Contains(", expressionText); + Assert.Equal([new SnowflakeId(100), new SnowflakeId(5)], result); + } + [Fact] public void Parse_ParseMultipleInAndNotInAndNot_InOperators() { @@ -300,7 +318,7 @@ public void Parse_ParseMultipleInAndNotInAndNot_InOperators() var parsedExpression = sut.Parse(null).ToString(); // Assert - Check.That(parsedExpression).Equals("(((((((x.MainCompanyId == 1) OrElse (x.MainCompanyId == 2)) AndAlso Not(((x.MainCompanyId == 3) OrElse (x.MainCompanyId == 4)))) AndAlso Not(((x.Name == \"A\") OrElse (x.Name == \"B\")))) AndAlso x.Name.Contains(y)) AndAlso Not(x.Name.Contains(z))) AndAlso Not(x.Name.Contains(s)))"); + Check.That(parsedExpression).Equals("(((((new [] {1, 2}.Contains(x.MainCompanyId) AndAlso Not(new [] {3, 4}.Contains(x.MainCompanyId))) AndAlso Not(new [] {\"A\", \"B\"}.Contains(x.Name))) AndAlso x.Name.Contains(y)) AndAlso Not(x.Name.Contains(z))) AndAlso Not(x.Name.Contains(s)))"); } [Fact] @@ -321,7 +339,7 @@ public void Parse_ParseInWrappedInParenthesis() public void Parse_CastActingOnIt() { // Arrange - var parameters = new[] { ParameterExpressionHelper.CreateParameterExpression(typeof(User), "u") }; + var parameters = new[] { ParameterExpressionHelper.CreateParameterExpression(typeof(System.Linq.Dynamic.Core.Tests.Entities.User), "u") }; var sut = new ExpressionParser(parameters, "DisplayName.Any(int(it) > 109)", null, null); // Act diff --git a/test/System.Linq.Dynamic.Core.Tests/ParsingConfigTests.cs b/test/System.Linq.Dynamic.Core.Tests/ParsingConfigTests.cs index 00f3d20c..de71c1b0 100644 --- a/test/System.Linq.Dynamic.Core.Tests/ParsingConfigTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/ParsingConfigTests.cs @@ -1,43 +1,38 @@ -using NFluent; -using Xunit; +using Xunit; -namespace System.Linq.Dynamic.Core.Tests +namespace System.Linq.Dynamic.Core.Tests; + +public class ParsingConfigTests { - public class ParsingConfigTests + class TestQueryableAnalyzer : IQueryableAnalyzer { - class TestQueryableAnalyzer : IQueryableAnalyzer + public bool SupportsLinqToObjects(IQueryable query, IQueryProvider? provider = null) { - public bool SupportsLinqToObjects(IQueryable query, IQueryProvider provider) - { - return true; - } + return true; } + } - [Fact] - public void ParsingConfig_QueryableAnalyzer_Set_Null() - { - // Assign - var config = ParsingConfig.Default; - - // Act - config.QueryableAnalyzer = null; + [Fact] + public void ParsingConfig_QueryableAnalyzer_Set_Null() + { + // Assign + var config = ParsingConfig.Default; - // Assert - Check.That(config.QueryableAnalyzer).IsNotNull(); - } + // Assert + Assert.NotNull(config.QueryableAnalyzer); + } - [Fact] - public void ParsingConfig_QueryableAnalyzer_Set_Custom() - { - // Assign - var config = ParsingConfig.Default; - var analyzer = new TestQueryableAnalyzer(); + [Fact] + public void ParsingConfig_QueryableAnalyzer_Set_Custom() + { + // Assign + var config = ParsingConfig.Default; + var analyzer = new TestQueryableAnalyzer(); - // Act - config.QueryableAnalyzer = analyzer; + // Act + config.QueryableAnalyzer = analyzer; - // Assert - Check.That(config.QueryableAnalyzer).IsEqualTo(analyzer); - } + // Assert + Assert.Equal(analyzer, config.QueryableAnalyzer); } -} +} \ No newline at end of file diff --git a/version.xml b/version.xml index 9fefde31..cbb266f6 100644 --- a/version.xml +++ b/version.xml @@ -1,5 +1,5 @@ - 3 + 4-preview-01 \ No newline at end of file