(feature): New expression extension methods
This commit is contained in:
parent
f0dc409be9
commit
01ad1ddb8e
@ -6,14 +6,15 @@ using MaksIT.Core.Extensions;
|
||||
namespace MaksIT.Core.Tests.Extensions;
|
||||
|
||||
public class ExpressionExtensionsTests {
|
||||
|
||||
[Fact]
|
||||
public void CombineWith_ShouldCombineTwoPredicates() {
|
||||
public void AndAlso_ShouldCombineTwoPredicatesWithAndCondition() {
|
||||
// Arrange
|
||||
Expression<Func<TestEntity, bool>> firstPredicate = x => x.Age > 18;
|
||||
Expression<Func<TestEntity, bool>> secondPredicate = x => (x.Name ?? "").StartsWith("A");
|
||||
|
||||
// Act
|
||||
var combinedPredicate = firstPredicate.CombineWith(secondPredicate);
|
||||
var combinedPredicate = firstPredicate.AndAlso(secondPredicate);
|
||||
var compiledPredicate = combinedPredicate.Compile();
|
||||
|
||||
// Assert
|
||||
@ -22,10 +23,98 @@ public class ExpressionExtensionsTests {
|
||||
Assert.False(compiledPredicate(new TestEntity { Age = 20, Name = "Bob" }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OrElse_ShouldCombineTwoPredicatesWithOrCondition() {
|
||||
// Arrange
|
||||
Expression<Func<TestEntity, bool>> firstPredicate = x => x.Age > 18;
|
||||
Expression<Func<TestEntity, bool>> secondPredicate = x => (x.Name ?? "").StartsWith("A");
|
||||
|
||||
// Act
|
||||
var combinedPredicate = firstPredicate.OrElse(secondPredicate);
|
||||
var compiledPredicate = combinedPredicate.Compile();
|
||||
|
||||
// Assert
|
||||
Assert.True(compiledPredicate(new TestEntity { Age = 20, Name = "Alice" }));
|
||||
Assert.True(compiledPredicate(new TestEntity { Age = 17, Name = "Alice" }));
|
||||
Assert.True(compiledPredicate(new TestEntity { Age = 20, Name = "Bob" }));
|
||||
Assert.False(compiledPredicate(new TestEntity { Age = 17, Name = "Bob" }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Not_ShouldNegatePredicate() {
|
||||
// Arrange
|
||||
Expression<Func<TestEntity, bool>> predicate = x => x.Age > 18;
|
||||
|
||||
// Act
|
||||
var negatedPredicate = predicate.Not();
|
||||
var compiledPredicate = negatedPredicate.Compile();
|
||||
|
||||
// Assert
|
||||
Assert.False(compiledPredicate(new TestEntity { Age = 20 }));
|
||||
Assert.True(compiledPredicate(new TestEntity { Age = 17 }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AndAlso_ShouldHandleNullValues() {
|
||||
// Arrange
|
||||
Expression<Func<TestEntity, bool>> firstPredicate = x => x.Name != null;
|
||||
Expression<Func<TestEntity, bool>> secondPredicate = x => (x.Name ?? "").Length > 3;
|
||||
|
||||
// Act
|
||||
var combinedPredicate = firstPredicate.AndAlso(secondPredicate);
|
||||
var compiledPredicate = combinedPredicate.Compile();
|
||||
|
||||
// Assert
|
||||
Assert.False(compiledPredicate(new TestEntity { Name = null }));
|
||||
Assert.True(compiledPredicate(new TestEntity { Name = "John" }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Not_ShouldThrowExceptionForNullExpression() {
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => ExpressionExtensions.Not<TestEntity>(null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AndAlso_ShouldThrowExceptionForNullExpression() {
|
||||
// Arrange
|
||||
Expression<Func<TestEntity, bool>> firstPredicate = null!;
|
||||
Expression<Func<TestEntity, bool>> secondPredicate = x => x.Age > 18;
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => firstPredicate.AndAlso(secondPredicate));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OrElse_ShouldThrowExceptionForNullExpression() {
|
||||
// Arrange
|
||||
Expression<Func<TestEntity, bool>> firstPredicate = x => x.Age > 18;
|
||||
Expression<Func<TestEntity, bool>> secondPredicate = null!;
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => firstPredicate.OrElse(secondPredicate));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Batch_ShouldDivideCollectionIntoBatchesOfGivenSize() {
|
||||
// Arrange
|
||||
var source = Enumerable.Range(1, 10);
|
||||
int batchSize = 3;
|
||||
|
||||
// Act
|
||||
var batches = source.Batch(batchSize).ToList();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(4, batches.Count);
|
||||
Assert.Equal(new List<int> { 1, 2, 3 }, batches[0]);
|
||||
Assert.Equal(new List<int> { 4, 5, 6 }, batches[1]);
|
||||
Assert.Equal(new List<int> { 7, 8, 9 }, batches[2]);
|
||||
Assert.Equal(new List<int> { 10 }, batches[3]);
|
||||
}
|
||||
|
||||
private class TestEntity {
|
||||
public Guid Id { get; set; }
|
||||
public int Age { get; set; }
|
||||
public string? Name { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -10,13 +10,13 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.3">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
|
||||
namespace MaksIT.Core.Extensions;
|
||||
|
||||
public static class ExpressionExtensions {
|
||||
|
||||
public static Expression<Func<T, bool>> CombineWith<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second) {
|
||||
public static Expression<Func<T, bool>> AndAlso<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second) {
|
||||
ArgumentNullException.ThrowIfNull(first);
|
||||
ArgumentNullException.ThrowIfNull(second);
|
||||
|
||||
var parameter = first.Parameters[0];
|
||||
var visitor = new SubstituteParameterVisitor(second.Parameters[0], parameter);
|
||||
var secondBody = visitor.Visit(second.Body);
|
||||
@ -17,6 +17,27 @@ public static class ExpressionExtensions {
|
||||
return Expression.Lambda<Func<T, bool>>(combinedBody, parameter);
|
||||
}
|
||||
|
||||
public static Expression<Func<T, bool>> OrElse<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second) {
|
||||
ArgumentNullException.ThrowIfNull(first);
|
||||
ArgumentNullException.ThrowIfNull(second);
|
||||
|
||||
var parameter = first.Parameters[0];
|
||||
var visitor = new SubstituteParameterVisitor(second.Parameters[0], parameter);
|
||||
var secondBody = visitor.Visit(second.Body);
|
||||
var combinedBody = Expression.OrElse(first.Body, secondBody);
|
||||
|
||||
return Expression.Lambda<Func<T, bool>>(combinedBody, parameter);
|
||||
}
|
||||
|
||||
public static Expression<Func<T, bool>> Not<T>(this Expression<Func<T, bool>> expression) {
|
||||
if (expression == null) throw new ArgumentNullException(nameof(expression));
|
||||
|
||||
var parameter = expression.Parameters[0];
|
||||
var body = Expression.Not(expression.Body);
|
||||
|
||||
return Expression.Lambda<Func<T, bool>>(body, parameter);
|
||||
}
|
||||
|
||||
private class SubstituteParameterVisitor : ExpressionVisitor {
|
||||
private readonly ParameterExpression _oldParameter;
|
||||
private readonly ParameterExpression _newParameter;
|
||||
@ -31,4 +52,21 @@ public static class ExpressionExtensions {
|
||||
return node == _oldParameter ? _newParameter : base.VisitParameter(node);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<List<T>> Batch<T>(this IEnumerable<T> source, int batchSize) {
|
||||
var batch = new List<T>(batchSize);
|
||||
foreach (var item in source) {
|
||||
batch.Add(item);
|
||||
if (batch.Count == batchSize) {
|
||||
yield return batch;
|
||||
batch = new List<T>(batchSize);
|
||||
}
|
||||
}
|
||||
if (batch.Any()) {
|
||||
yield return batch;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
<!-- NuGet package metadata -->
|
||||
<PackageId>MaksIT.Core</PackageId>
|
||||
<Version>1.3.4</Version>
|
||||
<Version>1.3.5</Version>
|
||||
<Authors>Maksym Sadovnychyy</Authors>
|
||||
<Company>MAKS-IT</Company>
|
||||
<Product>MaksIT.Core</Product>
|
||||
|
||||
@ -13,14 +13,18 @@ public class PagedRequest : RequestModelBase {
|
||||
public bool IsAscending { get; set; } = true;
|
||||
|
||||
public Expression<Func<T, bool>> BuildFilterExpression<T>() {
|
||||
if (string.IsNullOrWhiteSpace(Filters))
|
||||
return BuildFilterExpression<T>(Filters);
|
||||
}
|
||||
|
||||
public virtual Expression<Func<T, bool>> BuildFilterExpression<T>(string? filters) {
|
||||
if (string.IsNullOrWhiteSpace(filters))
|
||||
return x => true; // Returns an expression that doesn't filter anything.
|
||||
|
||||
// Get the type of T
|
||||
var type = typeof(T);
|
||||
|
||||
// Adjust Filters to make Contains, StartsWith, EndsWith, ==, and != case-insensitive
|
||||
string adjustedFilters = Filters;
|
||||
string adjustedFilters = filters;
|
||||
|
||||
// Regex to find property names and methods
|
||||
adjustedFilters = Regex.Replace(adjustedFilters, @"(\w+)\.(Contains|StartsWith|EndsWith)\(\""(.*?)\""\)", m => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user