Skip to content

Commit

Permalink
Adding Except and Union descriptors (#42)
Browse files Browse the repository at this point in the history
* Adding Except and Union descriptors

* Release notes.
  • Loading branch information
BlaiseD committed Oct 14, 2023
1 parent 9d968ed commit 0a69d3b
Show file tree
Hide file tree
Showing 12 changed files with 219 additions and 16 deletions.
2 changes: 1 addition & 1 deletion LogicBuilder.Data/LogicBuilder.Data.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<Authors>BlaiseD</Authors>
<Description>LogicBuilder.Data includes the base class for all data objects in the applications data stack.</Description>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<PackageReleaseNotes>Supporting AutoMapper v12.</PackageReleaseNotes>
<PackageReleaseNotes>Supporting Except and Union operators.</PackageReleaseNotes>
<PackageTags>crud operations</PackageTags>
<Copyright>Copyright © BPS 2017</Copyright>
<RepositoryUrl>https://github.com/BlaiseD/LogicBuilder.DataComponents</RepositoryUrl>
Expand Down
2 changes: 1 addition & 1 deletion LogicBuilder.Domain/LogicBuilder.Domain.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<Authors>BlaiseD</Authors>
<Description>LogicBuilder.Domain includes the base class for all DTOs in the data stack;</Description>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<PackageReleaseNotes>Supporting AutoMapper v12.</PackageReleaseNotes>
<PackageReleaseNotes>Supporting Except and Union operators.</PackageReleaseNotes>
<PackageTags>crud operations logic-builder</PackageTags>
<Copyright>Copyright © BPS 2017</Copyright>
<RepositoryUrl>https://github.com/BlaiseD/LogicBuilder.DataComponents</RepositoryUrl>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
using AutoMapper;
using AutoMapper.Extensions.ExpressionMapping;
using LogicBuilder.EntityFrameworkCore.SqlServer.Mapping;
using LogicBuilder.EntityFrameworkCore.SqlServer.Tests.Data;
using LogicBuilder.Expressions.Utils.ExpressionBuilder.Lambda;
using LogicBuilder.Expressions.Utils.ExpressionDescriptors;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Xunit;

namespace LogicBuilder.EntityFrameworkCore.SqlServer.Tests
{
public class CollectionExpressionTests
{
public CollectionExpressionTests()
{
Initialize();
}

#region Fields
private IServiceProvider serviceProvider;
private static readonly string parameterName = "$it";
#endregion Fields

[Fact]
public void ConcatDescriptorWorks()
{
//act
var descriptor = new ConcatDescriptor
(
new MemberSelectorDescriptor("AlternateAddresses", new ParameterDescriptor(parameterName)),
new ConstantDescriptor(new Address[] { new Address { City = "Seattle" }, new Address { City = "Portland" } })
);
var expression = GetExpression<Product, IEnumerable<Address>>(descriptor);
var result = RunExpression
(
expression,
new Product { AlternateAddresses = new[] { new Address { City = "Redmond" }, new Address { City = "Seattle" } } }
);

AssertExpressionStringIsCorrect(expression, "$it => $it.AlternateAddresses.Concat(LogicBuilder.EntityFrameworkCore.SqlServer.Tests.Data.Address[])");
Assert.Equal(4, result.Count());
}

[Fact]
public void ExceptDescriptorWorks()
{
//act
var descriptor = new ExceptDescriptor
(
new MemberSelectorDescriptor("AlternateAddresses", new ParameterDescriptor(parameterName)),
new ConstantDescriptor(new Address[] { new Address { City = "Seattle" }, new Address { City = "Portland" } })
);
var expression = GetExpression<Product, IEnumerable<Address>>(descriptor);
var result = RunExpression
(
expression,
new Product { AlternateAddresses = new[] { new Address { City = "Redmond" }, new Address { City = "Seattle" } } }
);

AssertExpressionStringIsCorrect(expression, "$it => $it.AlternateAddresses.Except(LogicBuilder.EntityFrameworkCore.SqlServer.Tests.Data.Address[])");
Assert.Single(result);
Assert.Equal(new Address { City = "Redmond" }, result.Single());
}

[Fact]
public void UnionDescriptorWorks()
{
//act
var descriptor = new UnionDescriptor
(
new MemberSelectorDescriptor("AlternateAddresses", new ParameterDescriptor(parameterName)),
new ConstantDescriptor(new Address[] { new Address { City = "Seattle" }, new Address { City = "Portland" } })
);
var expression = GetExpression<Product, IEnumerable<Address>>(descriptor);
var result = RunExpression
(
expression,
new Product { AlternateAddresses = new[] { new Address { City = "Redmond" }, new Address { City = "Seattle" } } }
);

AssertExpressionStringIsCorrect(expression, "$it => $it.AlternateAddresses.Union(LogicBuilder.EntityFrameworkCore.SqlServer.Tests.Data.Address[])");
Assert.Equal(3, result.Count());
}

static MapperConfiguration MapperConfiguration;
private void Initialize()
{
MapperConfiguration ??= new MapperConfiguration(cfg =>
{
cfg.AddExpressionMapping();
cfg.AddProfile<ExpressionOperatorsMappingProfile>();
});

serviceProvider = new ServiceCollection()
.AddSingleton<AutoMapper.IConfigurationProvider>
(
MapperConfiguration
)
.AddTransient<IMapper>(sp => new Mapper(sp.GetRequiredService<AutoMapper.IConfigurationProvider>(), sp.GetService))
.BuildServiceProvider();
}

private static IDictionary<string, ParameterExpression> GetParameters()
=> new Dictionary<string, ParameterExpression>();

private Expression<Func<T, TResult>> GetExpression<T, TResult>(IExpressionDescriptor filterBody, string parameterName = "$it")
{
IMapper mapper = serviceProvider.GetRequiredService<IMapper>();

return (Expression<Func<T, TResult>>)mapper.Map<SelectorLambdaOperator>
(
new SelectorLambdaDescriptor
(
filterBody,
typeof(T),
typeof(TResult),
parameterName
),
opts => opts.Items["parameters"] = GetParameters()
).Build();
}

private static TResult RunExpression<T, TResult>(Expression<Func<T, TResult>> filter, T instance)
=> filter.Compile().Invoke(instance);

private static void AssertExpressionStringIsCorrect(Expression expression, string expected)
{
AssertStringIsCorrect(ExpressionStringBuilder.ToString(expression));

void AssertStringIsCorrect(string resultExpression)
=> Assert.True
(
expected == resultExpression,
$"Expected expression '{expected}' but the deserializer produced '{resultExpression}'"
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,27 @@ public class Address
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }

public override bool Equals(object obj)
{
if (obj == null)
return false;

if (ReferenceEquals(this, obj)) return true;

if (obj.GetType() != GetType()) return false;

Address other = (Address)obj;
return other.AddressID == AddressID
&& other.City == City
&& other.State == State
&& other.ZipCode == ZipCode;
}

public override int GetHashCode()
{
return AddressID.GetHashCode();
}
}

public class DataTypes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<Authors>BlaiseD</Authors>
<Description>Given an EF Core DBContext, LogicBuilder.EntityFrameworkCore.SqlServer uses AutoMapper configurations to support CRUD operations using the DTO objects.</Description>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<PackageReleaseNotes>Declaring unused classes obsolete.</PackageReleaseNotes>
<PackageReleaseNotes>Supporting Except and Union operators.</PackageReleaseNotes>
<PackageTags>crud operations efcore</PackageTags>
<Copyright>Copyright © BPS 2017</Copyright>
<RepositoryUrl>https://github.com/BlaiseD/LogicBuilder.DataComponents</RepositoryUrl>
Expand All @@ -22,9 +22,9 @@

<ItemGroup>
<PackageReference Include="AutoMapper.Extensions.ExpressionMapping" Version="[6.0.4,7.0.0)" />
<PackageReference Include="LogicBuilder.Data" Version="5.0.10" />
<PackageReference Include="LogicBuilder.Domain" Version="5.0.10" />
<PackageReference Include="LogicBuilder.Expressions.Utils" Version="5.0.10" />
<PackageReference Include="LogicBuilder.Data" Version="5.0.11-alpha.0.2" />
<PackageReference Include="LogicBuilder.Domain" Version="5.0.11-alpha.0.2" />
<PackageReference Include="LogicBuilder.Expressions.Utils" Version="5.0.11-alpha.0.2" />
<PackageReference Include="MinVer" Version="2.5.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ public ExpressionOperatorsMappingProfile()
CreateMap<DivideBinaryDescriptor, DivideBinaryOperator>();
CreateMap<EndsWithDescriptor, EndsWithOperator>();
CreateMap<EqualsBinaryDescriptor, EqualsBinaryOperator>();
CreateMap<FilterLambdaDescriptor, FilterLambdaOperator>()
CreateMap<ExceptDescriptor, ExceptOperator>();
CreateMap<FilterLambdaDescriptor, FilterLambdaOperator>()
.ConstructUsing
(
(src, context) => new FilterLambdaOperator
Expand Down Expand Up @@ -371,7 +372,8 @@ public ExpressionOperatorsMappingProfile()
CreateMap<TotalSecondsDescriptor, TotalSecondsOperator>();
CreateMap<ToUpperDescriptor, ToUpperOperator>();
CreateMap<TrimDescriptor, TrimOperator>();
CreateMap<WhereDescriptor, WhereOperator>()
CreateMap<UnionDescriptor, UnionOperator>();
CreateMap<WhereDescriptor, WhereOperator>()
.ConstructUsing
(
(src, context) => new WhereOperator
Expand Down Expand Up @@ -417,7 +419,8 @@ public ExpressionOperatorsMappingProfile()
.Include<DivideBinaryDescriptor, DivideBinaryOperator>()
.Include<EndsWithDescriptor, EndsWithOperator>()
.Include<EqualsBinaryDescriptor, EqualsBinaryOperator>()
.Include<FilterLambdaDescriptor, FilterLambdaOperator>()
.Include<ExceptDescriptor, ExceptOperator>()
.Include<FilterLambdaDescriptor, FilterLambdaOperator>()
.Include<FirstDescriptor, FirstOperator>()
.Include<FirstOrDefaultDescriptor, FirstOrDefaultOperator>()
.Include<FloorDescriptor, FloorOperator>()
Expand Down Expand Up @@ -474,7 +477,8 @@ public ExpressionOperatorsMappingProfile()
.Include<TotalSecondsDescriptor, TotalSecondsOperator>()
.Include<ToUpperDescriptor, ToUpperOperator>()
.Include<TrimDescriptor, TrimOperator>()
.Include<WhereDescriptor, WhereOperator>()
.Include<UnionDescriptor, UnionOperator>()
.Include<WhereDescriptor, WhereOperator>()
.Include<YearDescriptor, YearOperator>();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<Authors>BlaiseD</Authors>
<Description>Augments LogicBuilder.Expressions.Utils with LINQ queries specific to EF Core.</Description>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<PackageReleaseNotes>Declaring unused classes obsolete.</PackageReleaseNotes>
<PackageReleaseNotes>Supporting Except and Union operators.</PackageReleaseNotes>
<PackageTags>linq expressions efcore</PackageTags>
<Copyright>Copyright © BPS 2018</Copyright>
<RepositoryUrl>https://github.com/BlaiseD/LogicBuilder.DataComponents</RepositoryUrl>
Expand All @@ -21,7 +21,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="LogicBuilder.Expressions.Utils" Version="5.0.10" />
<PackageReference Include="LogicBuilder.Expressions.Utils" Version="5.0.11-alpha.0.2" />
<PackageReference Include="MinVer" Version="2.5.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<Authors>BlaiseD</Authors>
<Description>Creates CRUD related LINQ queries from data.</Description>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<PackageReleaseNotes>Declaring unused classes obsolete.</PackageReleaseNotes>
<PackageReleaseNotes>Supporting Except and Union operators.</PackageReleaseNotes>
<PackageTags>linq expressions</PackageTags>
<Copyright>Copyright © BPS 2018</Copyright>
<RepositoryUrl>https://github.com/BlaiseD/LogicBuilder.DataComponents</RepositoryUrl>
Expand All @@ -21,7 +21,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="LogicBuilder.Structures" Version="5.0.10" />
<PackageReference Include="LogicBuilder.Structures" Version="5.0.11-alpha.0.2" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.3.0" />
<PackageReference Include="Microsoft.OData.Edm" Version="7.9.4" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<Authors>BlaiseD</Authors>
<Description>Extensions to create IQueryable expressions from Telerik's DataSourceRequest class. The expressions can then be executed against an IQueryable. This package depends on Telerik.UI.for.AspNet.Core but has not been created by Telerik/Progress.</Description>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<PackageReleaseNotes>Kendo UI expressions only - Allow paging expression to be executed separately from the grouping expression.</PackageReleaseNotes>
<PackageReleaseNotes>Supporting Except and Union operators.</PackageReleaseNotes>
<PackageTags>efcore kendo</PackageTags>
<Copyright>Copyright © BPS 2018</Copyright>
<RepositoryUrl>https://github.com/BlaiseD/LogicBuilder.DataComponents</RepositoryUrl>
Expand Down
18 changes: 18 additions & 0 deletions LogicBuilder.Structures/ExpressionDescriptors/ExceptDescriptor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace LogicBuilder.Expressions.Utils.ExpressionDescriptors
{
public class ExceptDescriptor : IExpressionDescriptor
{
public ExceptDescriptor()
{
}

public ExceptDescriptor(IExpressionDescriptor left, IExpressionDescriptor right)
{
Left = left;
Right = right;
}

public IExpressionDescriptor Left { get; set; }
public IExpressionDescriptor Right { get; set; }
}
}
18 changes: 18 additions & 0 deletions LogicBuilder.Structures/ExpressionDescriptors/UnionDescriptor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace LogicBuilder.Expressions.Utils.ExpressionDescriptors
{
public class UnionDescriptor : IExpressionDescriptor
{
public UnionDescriptor()
{
}

public UnionDescriptor(IExpressionDescriptor left, IExpressionDescriptor right)
{
Left = left;
Right = right;
}

public IExpressionDescriptor Left { get; set; }
public IExpressionDescriptor Right { get; set; }
}
}
2 changes: 1 addition & 1 deletion LogicBuilder.Structures/LogicBuilder.Structures.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<Authors>BlaiseD</Authors>
<Description>LogicBuilder.Structures includes classes used by multiple components in the LogicBuilder data stack.</Description>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<PackageReleaseNotes>Declaring unused classes obsolete.</PackageReleaseNotes>
<PackageReleaseNotes>Supporting Except and Union operators.</PackageReleaseNotes>
<PackageTags>logicbuilder</PackageTags>
<Copyright>Copyright © BPS 2019</Copyright>
<RepositoryUrl>https://github.com/BlaiseD/LogicBuilder.DataComponents</RepositoryUrl>
Expand Down

0 comments on commit 0a69d3b

Please sign in to comment.