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
10 changes: 7 additions & 3 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,9 @@ dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case

dotnet_naming_rule.private_or_internal_field_should_be__fieldname.severity = suggestion
dotnet_naming_rule.private_or_internal_field_should_be__fieldname.symbols = private_or_internal_field
dotnet_naming_rule.private_or_internal_field_should_be__fieldname.style = _fieldname
dotnet_naming_rule.private_field_should_be__fieldname.severity = suggestion
dotnet_naming_rule.private_field_should_be__fieldname.symbols = private_field
dotnet_naming_rule.private_field_should_be__fieldname.style = _fieldname

dotnet_naming_rule.public_field_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.public_field_should_be_pascal_case.symbols = public_field
Expand All @@ -122,6 +122,10 @@ dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =

dotnet_naming_symbols.private_field.applicable_kinds = field
dotnet_naming_symbols.private_field.applicable_accessibilities = private, private_protected
dotnet_naming_symbols.private_field.required_modifiers =

dotnet_naming_symbols.private_or_internal_field.applicable_kinds = field
dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private, private_protected
dotnet_naming_symbols.private_or_internal_field.required_modifiers =
Expand Down
7 changes: 7 additions & 0 deletions QuerySpecification.sln
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
setup-sqllocaldb.ps1 = setup-sqllocaldb.ps1
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuerySpecification.AutoDiscovery.Tests", "tests\QuerySpecification.AutoDiscovery.Tests\QuerySpecification.AutoDiscovery.Tests.csproj", "{154BA43D-0416-4840-85E1-B5ED34929E04}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -60,6 +62,10 @@ Global
{F25DE99A-0BE4-41C6-9D1C-570CCE6B0BB9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F25DE99A-0BE4-41C6-9D1C-570CCE6B0BB9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F25DE99A-0BE4-41C6-9D1C-570CCE6B0BB9}.Release|Any CPU.Build.0 = Release|Any CPU
{154BA43D-0416-4840-85E1-B5ED34929E04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{154BA43D-0416-4840-85E1-B5ED34929E04}.Debug|Any CPU.Build.0 = Debug|Any CPU
{154BA43D-0416-4840-85E1-B5ED34929E04}.Release|Any CPU.ActiveCfg = Release|Any CPU
{154BA43D-0416-4840-85E1-B5ED34929E04}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -68,6 +74,7 @@ Global
{BAFCF13F-05EF-4DEF-AD78-B5006CE86784} = {670F5387-F816-409B-8048-183F2608EDB3}
{F85A5DB8-DB84-430A-8DA1-43A613FBD0CC} = {670F5387-F816-409B-8048-183F2608EDB3}
{F25DE99A-0BE4-41C6-9D1C-570CCE6B0BB9} = {670F5387-F816-409B-8048-183F2608EDB3}
{154BA43D-0416-4840-85E1-B5ED34929E04} = {670F5387-F816-409B-8048-183F2608EDB3}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E5CD485E-D269-41DE-A144-EEC734F5B893}
Expand Down
1 change: 1 addition & 0 deletions exclusion.dic
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ aaaab
aaaaab
axza
Compilable
netstandard
5 changes: 5 additions & 0 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@
<PackageIcon>pozitronicon.png</PackageIcon>
</PropertyGroup>

<PropertyGroup>
<!--Missing XML comment-->
<!--<NoWarn>1591</NoWarn>-->
</PropertyGroup>

<ItemGroup>
<None Include="../../pozitronicon.png" Pack="true" PackagePath="\" Visible="false" />
<None Include="../../readme-nuget.md" Pack="true" PackagePath="\" Visible="false" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
/// <summary>
/// Evaluator to apply AsNoTracking to the query if the specification has AsNoTracking set to true.
/// </summary>
[EvaluatorDiscovery(Order = 100)]
public sealed class AsNoTrackingEvaluator : IEvaluator
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
/// <summary>
/// Evaluator to apply AsNoTracking to the query if the specification has AsNoTracking set to true.
/// </summary>
[EvaluatorDiscovery(Order = 110)]
public sealed class AsNoTrackingWithIdentityResolutionEvaluator : IEvaluator
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
/// <summary>
/// Evaluator to apply AsSplitQuery to the query if the specification has AsSplitQuery set to true.
/// </summary>
[EvaluatorDiscovery(Order = 90)]
public sealed class AsSplitQueryEvaluator : IEvaluator
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
/// <summary>
/// Evaluator to apply AsTracking to the query if the specification has AsTracking set to true.
/// </summary>
[EvaluatorDiscovery(Order = 120)]
public sealed class AsTrackingEvaluator : IEvaluator
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
/// <summary>
/// Evaluator to apply IgnoreAutoIncludes to the query if the specification has IgnoreAutoIncludes set to true.
/// </summary>
[EvaluatorDiscovery(Order = 70)]
public sealed class IgnoreAutoIncludesEvaluator : IEvaluator
{

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
/// <summary>
/// Evaluator to apply IgnoreQueryFilters to the query if the specification has IgnoreQueryFilters set to true.
/// </summary>
[EvaluatorDiscovery(Order = 80)]
public sealed class IgnoreQueryFiltersEvaluator : IEvaluator
{

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Microsoft.EntityFrameworkCore.Query;
using System.Collections;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Reflection;
Expand All @@ -9,6 +8,7 @@ namespace Pozitron.QuerySpecification;
/// <summary>
/// Evaluates a specification to include navigation properties.
/// </summary>
[EvaluatorDiscovery(Order = 40)]
public sealed class IncludeEvaluator : IEvaluator
{
private static readonly MethodInfo _includeMethodInfo = typeof(EntityFrameworkQueryableExtensions)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
/// <summary>
/// Evaluates a specification to include navigation properties specified by string paths.
/// </summary>
[EvaluatorDiscovery(Order = 30)]
public sealed class IncludeStringEvaluator : IEvaluator
{

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ This was the previous implementation. We're trying to avoid allocations of LikeE
/// <summary>
/// Evaluates a specification to apply "like" expressions for filtering.
/// </summary>
[EvaluatorDiscovery(Order = 20)]
public sealed class LikeEvaluator : IEvaluator
{
private LikeEvaluator() { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
/// <summary>
/// Evaluator to apply the query tags to the query.
/// </summary>
[EvaluatorDiscovery(Order = 60)]
public sealed class QueryTagEvaluator : IEvaluator
{

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,23 @@ public class SpecificationEvaluator
/// </summary>
public SpecificationEvaluator()
{
Evaluators =
[
WhereEvaluator.Instance,
LikeEvaluator.Instance,
IncludeStringEvaluator.Instance,
IncludeEvaluator.Instance,
OrderEvaluator.Instance,
IgnoreQueryFiltersEvaluator.Instance,
AsNoTrackingEvaluator.Instance,
AsNoTrackingWithIdentityResolutionEvaluator.Instance,
AsTrackingEvaluator.Instance,
AsSplitQueryEvaluator.Instance,
IgnoreAutoIncludesEvaluator.Instance,
QueryTagEvaluator.Instance,
];
Evaluators = TypeDiscovery.IsAutoDiscoveryEnabled
? TypeDiscovery.GetEvaluators()
:
[
WhereEvaluator.Instance,
LikeEvaluator.Instance,
IncludeStringEvaluator.Instance,
IncludeEvaluator.Instance,
OrderEvaluator.Instance,
QueryTagEvaluator.Instance,
IgnoreAutoIncludesEvaluator.Instance,
IgnoreQueryFiltersEvaluator.Instance,
AsSplitQueryEvaluator.Instance,
AsNoTrackingEvaluator.Instance,
AsNoTrackingWithIdentityResolutionEvaluator.Instance,
AsTrackingEvaluator.Instance,
];
}

/// <summary>
Expand Down
40 changes: 40 additions & 0 deletions src/QuerySpecification/DiscoveryAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
namespace Pozitron.QuerySpecification;

/// <summary>
/// Specifies whether auto discovery for evaluators and validators is enabled.
/// </summary>
[AttributeUsage(AttributeTargets.Assembly)]
public sealed class SpecAutoDiscoveryAttribute : Attribute
{
}

/// <summary>
/// Specifies discovery options for evaluators and validators, such as the order and whether discovery is enabled.
/// </summary>
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public class DiscoveryAttribute : Attribute
{
/// <summary>
/// Gets the order in which the evaluator/validator should be applied. Lower values are applied first.
/// </summary>
public int Order { get; set; } = int.MaxValue;

/// <summary>
/// Gets a value indicating whether the evaluator/validator is discoverable.
/// </summary>
public bool Enable { get; set; } = true;
}

/// <summary>
/// Specifies discovery options for evaluators, such as the order and whether discovery is enabled.
/// </summary>
public sealed class EvaluatorDiscoveryAttribute : DiscoveryAttribute
{
}

/// <summary>
/// Specifies discovery options for validators, such as the order and whether discovery is enabled.
/// </summary>
public sealed class ValidatorDiscoveryAttribute : DiscoveryAttribute
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
/// <summary>
/// Represents an in-memory evaluator that processes a specification.
/// </summary>
public interface IInMemoryEvaluator
public interface IMemoryEvaluator
{
/// <summary>
/// Evaluates the given specification on the provided enumerable source.
Expand Down
5 changes: 3 additions & 2 deletions src/QuerySpecification/Evaluators/LikeMemoryEvaluator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ public IEnumerable<T> Evaluate<T>(IEnumerable<T> source, Specification<T> specif
This was the previous implementation. We're trying to avoid allocations of LikeExpressions, GroupBy and LINQ.
The new implementation preserves the behavior and reduces allocations drastically.
We've implemented a custom iterator. Also, instead of GroupBy, we have a single array sorted by group, and we slice it to get the groups.
For source of 1000 items, the allocations are reduced from 257.872 bytes to only 64 bytes (the cost of the iterator instance). Refer to LikeInMemoryEvaluatorBenchmark results.
For source of 1000 items, the allocations are reduced from 257.872 bytes to only 64 bytes (the cost of the iterator instance). Refer to LikeMemoryEvaluatorBenchmark results.
*/

/// <summary>
/// Represents an in-memory evaluator for "Like" expressions.
/// </summary>
public sealed class LikeMemoryEvaluator : IInMemoryEvaluator
[EvaluatorDiscovery(Order = 20)]
public sealed class LikeMemoryEvaluator : IMemoryEvaluator
{
/// <summary>
/// Gets the singleton instance of the <see cref="LikeMemoryEvaluator"/> class.
Expand Down
3 changes: 2 additions & 1 deletion src/QuerySpecification/Evaluators/OrderEvaluator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
/// <summary>
/// Represents an evaluator for order expressions.
/// </summary>
public sealed class OrderEvaluator : IEvaluator, IInMemoryEvaluator
[EvaluatorDiscovery(Order = 50)]
public sealed class OrderEvaluator : IEvaluator, IMemoryEvaluator
{
/// <summary>
/// Gets the singleton instance of the <see cref="OrderEvaluator"/> class.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,38 @@
/// <summary>
/// Evaluates specifications in memory.
/// </summary>
public class SpecificationInMemoryEvaluator
public class SpecificationMemoryEvaluator
{
/// <summary>
/// Gets the default instance of the <see cref="SpecificationInMemoryEvaluator"/> class.
/// Gets the default instance of the <see cref="SpecificationMemoryEvaluator"/> class.
/// </summary>
public static SpecificationInMemoryEvaluator Default = new();
public static SpecificationMemoryEvaluator Default = new();

/// <summary>
/// Gets the list of in-memory evaluators.
/// </summary>
protected List<IInMemoryEvaluator> Evaluators { get; }
protected List<IMemoryEvaluator> Evaluators { get; }

/// <summary>
/// Initializes a new instance of the <see cref="SpecificationInMemoryEvaluator"/> class.
/// Initializes a new instance of the <see cref="SpecificationMemoryEvaluator"/> class.
/// </summary>
public SpecificationInMemoryEvaluator()
public SpecificationMemoryEvaluator()
{
Evaluators =
[
WhereEvaluator.Instance,
OrderEvaluator.Instance,
LikeMemoryEvaluator.Instance,
];
Evaluators = TypeDiscovery.IsAutoDiscoveryEnabled
? TypeDiscovery.GetMemoryEvaluators()
:
[
WhereEvaluator.Instance,
LikeMemoryEvaluator.Instance,
OrderEvaluator.Instance,
];
}

/// <summary>
/// Initializes a new instance of the <see cref="SpecificationInMemoryEvaluator"/> class with the specified evaluators.
/// Initializes a new instance of the <see cref="SpecificationMemoryEvaluator"/> class with the specified evaluators.
/// </summary>
/// <param name="evaluators">The in-memory evaluators to use.</param>
public SpecificationInMemoryEvaluator(IEnumerable<IInMemoryEvaluator> evaluators)
public SpecificationMemoryEvaluator(IEnumerable<IMemoryEvaluator> evaluators)
{
Evaluators = evaluators.ToList();
}
Expand Down
3 changes: 2 additions & 1 deletion src/QuerySpecification/Evaluators/WhereEvaluator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
/// <summary>
/// Represents an evaluator for where expressions.
/// </summary>
public sealed class WhereEvaluator : IEvaluator, IInMemoryEvaluator
[EvaluatorDiscovery(Order = 10)]
public sealed class WhereEvaluator : IEvaluator, IMemoryEvaluator
{
/// <summary>
/// Gets the singleton instance of the <see cref="WhereEvaluator"/> class.
Expand Down
Loading