Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build-and-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:
- master
env:
APP_NAME: DynamicExpression
VERSION: 10.0.6
VERSION: 10.0.7
NUGET_HOST: https://api.nuget.org/v3/index.json
NUGET_APIKEY: ${{ secrets.NUGET_APIKEY }}
jobs:
Expand Down
30 changes: 30 additions & 0 deletions .tests/Tests.DynamicExpression/CriteriaBuilderTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,36 @@ public void BuildWhenReferenceCollectionTest()
Assert.AreEqual("x.Orders.Any(i => ((i.Payment.Id != null) AndAlso (i.Payment.Id == \"value\")))", expression.Body.ToString());
}

[TestMethod]
public void BuildWhenNotEqualReferenceCollectionTest()
{
var criteriaExpression = new CriteriaExpression();
criteriaExpression.NotEqual("Orders[Payment.Id]", "value");

var expression = CriteriaBuilder.Build<Customer>(criteriaExpression);

Assert.IsNotNull(expression);
Assert.IsNotNull(expression.Compile());
Assert.AreEqual("Not(x.Orders.Any(i => ((i.Payment.Id != null) AndAlso (i.Payment.Id == \"value\"))))", expression.Body.ToString());
}

[TestMethod]
public void BuildWhenNotEqualReferenceCollectionLeavesOutMatchesTest()
{
var criteriaExpression = new CriteriaExpression();
criteriaExpression.NotEqual("Orders[Payment.Id]", "value");

var predicate = CriteriaBuilder.Build<Customer>(criteriaExpression).Compile();

var withMatch = new Customer { Orders = [new Order { Payment = new Payment { Id = "value" } }, new Order { Payment = new Payment { Id = "other" } }] };
var withoutMatch = new Customer { Orders = [new Order { Payment = new Payment { Id = "other" } }] };
var withoutOrders = new Customer { Orders = [] };

Assert.IsFalse(predicate(withMatch));
Assert.IsTrue(predicate(withoutMatch));
Assert.IsTrue(predicate(withoutOrders));
}

[TestMethod]
public void BuildWhenRefernceAndReferenceCollectionTest()
{
Expand Down
6 changes: 3 additions & 3 deletions .tests/Tests.DynamicExpression/Tests.DynamicExpression.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.10.1" />
<PackageReference Include="MSTest.TestAdapter" Version="4.5.1" />
<PackageReference Include="MSTest.TestFramework" Version="4.5.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.11.0" />
<PackageReference Include="MSTest.TestAdapter" Version="4.5.2" />
<PackageReference Include="MSTest.TestFramework" Version="4.5.2" />
</ItemGroup>

<ItemGroup>
Expand Down
40 changes: 38 additions & 2 deletions DynamicExpression/CriteriaBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,6 @@ private static Expression GetExpression(Expression parameter, Criteria criteria,
throw new ArgumentException($"Criteria for property '{name}' cannot be applied: operation '{operationType}' with value type '{value.Type.Name}' is not compatible with the property's type '{member.Type.Name}'.", ex);
}
}

private static Expression GetExpressionCore(Expression member, Expression value, Expression value2, OperationType operationType)
{
if (value.Type.IsEnum)
Expand Down Expand Up @@ -429,6 +428,27 @@ private static Expression GetExpressionCore(Expression member, Expression value,

throw new NotSupportedException($"'{operationType}' is not supported by '{value.Type}' ");
}
private static bool TryGetPositiveOperation(OperationType operationType, out OperationType positiveOperation)
{
switch (operationType)
{
case OperationType.NotEqual:
positiveOperation = OperationType.Equal;
return true;

case OperationType.NotIn:
positiveOperation = OperationType.In;
return true;

case OperationType.NotContains:
positiveOperation = OperationType.Contains;
return true;

default:
positiveOperation = operationType;
return false;
}
}
private static Expression BuildExpression(CriteriaExpression criteriaExpression, Expression parameter)
{
var prevLogicalType = LogicalType.And;
Expand Down Expand Up @@ -467,10 +487,26 @@ private static Expression BuildExpression(CriteriaExpression criteriaExpression,
var methodAny = typeof(Enumerable).GetRuntimeMethods().First(x => x.Name == "Any" && x.GetParameters().Length == 2).MakeGenericMethod(type);
var memberAny = CriteriaBuilder.GetMember(paramNested, baseName);
var parameterAny = Expression.Parameter(type, "i");
var expressionAny = CriteriaBuilder.GetExpression(parameterAny, criteria, name);
var isNegated = CriteriaBuilder.TryGetPositiveOperation(criteria.OperationType, out var positiveOperation);
var elementCriteria = isNegated
? new Criteria
{
Property = criteria.Property,
Value = criteria.Value,
Value2 = criteria.Value2,
LogicalType = criteria.LogicalType,
OperationType = positiveOperation
}
: criteria;
var expressionAny = CriteriaBuilder.GetExpression(parameterAny, elementCriteria, name);
var expr2 = Expression.Lambda(expressionAny, parameterAny);

innerExpression = Expression.Call(methodAny, memberAny, expr2);

if (isNegated)
{
innerExpression = Expression.Not(innerExpression);
}
}
else
{
Expand Down
3 changes: 2 additions & 1 deletion DynamicExpression/DynamicExpression.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageReleaseNotes>
- Updated NuGets
- Added NotIn expression
</PackageReleaseNotes>
<PackageLicense>https://raw.githubusercontent.com/vivet/DynamicExpression/master/LICENSE</PackageLicense>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
Expand Down Expand Up @@ -47,7 +48,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="10.0.401" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="10.0.403" PrivateAssets="All" />
<PackageReference Include="NetTopologySuite" Version="2.6.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="NetTopologySuite.IO.GeoJSON" Version="4.0.0" />
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ Note, that not all operations are valid on all data types, but it should be appa
* IsEmpty
* IsNotEmpty

A property on a collection is written in brackets, such as `Orders[Total]`, and the operation must match at least one element: `GreaterThan("Orders[Total]", 100)` is `x.Orders.Any(i => i.Total > 100)`.
The negated operations (NotEqual, NotIn and NotContains) on a collection mean that no element matches: `NotEqual("Orders[Id]", id)` is `!x.Orders.Any(i => i.Id == id)`.

#### Linq Extensions
The library comes with a few ```IQueryable<T>``` extension methods. They serve to convert the ```CriteriaExpression``` returned by the ```GetExpression()```, into a valid linq expression.

Expand Down
Loading