< Summary - Code Coverage

Information
Class: Plainquire.Filter.EntityFilter<T>
Assembly: Plainquire.Filter
File(s): /home/runner/work/plainquire/plainquire/Plainquire.Filter/Plainquire.Filter/Filters/EntityFilter.cs
Tag: 74_23635074410
Line coverage
91%
Covered lines: 34
Uncovered lines: 3
Coverable lines: 37
Total lines: 507
Line coverage: 91.8%
Branch coverage
75%
Covered branches: 15
Total branches: 20
Branch coverage: 75%
Method coverage
100%
Covered methods: 21
Total methods: 21
Method coverage: 100%

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor()100%11100%
.ctor(...)100%11100%
IsEmpty(...)100%44100%
GetPropertyFilterSyntax(...)100%11100%
GetPropertyFilterValues(...)100%11100%
GetNestedFilter(...)100%11100%
GetNestedFilter(...)100%11100%
Add(...)100%11100%
AddNested(...)50%2275%
AddNested(...)100%22100%
Replace(...)100%11100%
ReplaceNested(...)50%2275%
ReplaceNested(...)50%2275%
Remove(...)100%11100%
Clear()100%11100%
Clone()100%11100%
Cast()100%11100%
CreateFilter(...)100%11100%
op_Implicit(...)50%22100%
op_Implicit(...)50%22100%
ToString()100%44100%

File(s)

/home/runner/work/plainquire/plainquire/Plainquire.Filter/Plainquire.Filter/Filters/EntityFilter.cs

#LineLine coverage
 1using Plainquire.Filter.Abstractions;
 2using Plainquire.Filter.JsonConverters;
 3using Plainquire.Filter.PropertyFilterExpressions;
 4using System;
 5using System.Collections.Generic;
 6using System.Diagnostics;
 7using System.Diagnostics.CodeAnalysis;
 8using System.Linq;
 9using System.Linq.Expressions;
 10using System.Reflection;
 11using System.Text.Json;
 12using System.Text.Json.Serialization;
 13
 14namespace Plainquire.Filter;
 15
 16/// <summary>
 17/// Hub to create filter expressions for <typeparamref name="TEntity"/> with fluent API.
 18/// </summary>
 19/// <typeparam name="TEntity">The type to be filtered.</typeparam>
 20[JsonConverter(typeof(EntityFilterConverter.Factory))]
 21[DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")]
 22public class EntityFilter<TEntity> : EntityFilter
 23{
 24    /// <summary>
 25    /// Initializes a new instance of the <see cref="EntityFilter{TEntity}"/> class.
 26    /// </summary>
 1386627    public EntityFilter() { }
 28
 29    /// <summary>
 30    /// Initializes a new instance of the <see cref="EntityFilter{TEntity}"/> class.
 31    /// </summary>
 32    /// <param name="configuration">The configuration to use.</param>
 33    public EntityFilter(FilterConfiguration configuration)
 1987034        : base(configuration) { }
 35
 36    /// <summary>
 37    /// Indicates whether the filter for the specified property is empty.
 38    /// </summary>
 39    /// <typeparam name="TProperty">The type of the property to be filtered.</typeparam>
 40    /// <param name="property">The property to get the information for.</param>
 41    public bool IsEmpty<TProperty>(Expression<Func<TEntity, TProperty?>> property)
 542        => GetPropertyFilterValuesInternal(property)?.All(value => value.IsEmpty) ?? true;
 43
 44    /// <summary>
 45    /// Gets the filter syntax for the given <paramref name="property"/>.
 46    /// </summary>
 47    /// <typeparam name="TProperty">The type of the property to be filtered.</typeparam>
 48    /// <param name="property">The property to get the filter for.</param>
 49    public string? GetPropertyFilterSyntax<TProperty>(Expression<Func<TEntity, TProperty?>> property)
 250        => GetPropertyFilterSyntaxInternal(property);
 51
 52    /// <summary>
 53    /// Get the filters applied to the given property.
 54    /// </summary>
 55    /// <typeparam name="TProperty">The type of the property to be filtered.</typeparam>
 56    /// <param name="property">The property to get the filter for.</param>
 57    public ValueFilter[]? GetPropertyFilterValues<TProperty>(Expression<Func<TEntity, TProperty?>> property)
 258        => GetPropertyFilterValuesInternal(property);
 59
 60    /// <summary>
 61    /// Gets the <see cref="EntityFilter{TProperty}"/> for the given nested class <typeparamref name="TProperty"/>.
 62    /// </summary>
 63    /// <typeparam name="TProperty">The type of the property to be filtered.</typeparam>
 64    /// <param name="property">The property to get the filter for.</param>
 65    public EntityFilter<TProperty>? GetNestedFilter<TProperty>(Expression<Func<TEntity, TProperty?>> property)
 166        => GetNestedFilterInternal(property);
 67
 68    /// <summary>
 69    /// Gets the <see cref="EntityFilter{TProperty}"/> for the given nested list <typeparamref name="TList"/>.
 70    /// </summary>
 71    /// <typeparam name="TList">The type of the list of <typeparamref name="TProperty"/>.</typeparam>
 72    /// <typeparam name="TProperty">The type of the property to be filtered.</typeparam>
 73    /// <param name="property">The property to get the filter for.</param>
 74    public EntityFilter<TProperty>? GetNestedFilter<TList, TProperty>(Expression<Func<TEntity, TList?>> property)
 75        where TList : IEnumerable<TProperty>
 176        => GetNestedFilterInternal<TEntity, TList, TProperty>(property);
 77
 78    /// <summary>
 79    /// Adds a filter for the given property. Existing filters for the same property are preserved.
 80    /// </summary>
 81    /// <typeparam name="TProperty">The type of the property to be filtered.</typeparam>
 82    /// <param name="property">The property to filter.</param>
 83    /// <param name="filters">The filters to use.</param>
 84    public EntityFilter<TEntity> Add<TProperty>(Expression<Func<TEntity, TProperty?>> property, params ValueFilter[]? fi
 85    {
 11486        AddInternal(property, filters);
 11387        return this;
 88    }
 89
 90    /// <summary>
 91    /// Adds a nested filter for the given property. Existing filters for the same property are preserved.
 92    /// </summary>
 93    /// <typeparam name="TProperty">The type of the property to be filtered.</typeparam>
 94    /// <param name="property">The property to filter.</param>
 95    /// <param name="nestedFilter">The nested class filter.</param>
 96    public EntityFilter<TEntity> AddNested<TProperty>(Expression<Func<TEntity, TProperty?>> property, EntityFilter<TProp
 97    {
 298        if (nestedFilter == null)
 099            return this;
 100
 2101        AddNestedInternal(property, nestedFilter);
 2102        return this;
 103    }
 104
 105    /// <summary>
 106    /// Adds a nested filter for the given enumerable property. Existing filters for the same property are preserved.
 107    /// </summary>
 108    /// <typeparam name="TProperty">The type of the property to be filtered.</typeparam>
 109    /// <typeparam name="TNested">The nested type to be filtered.</typeparam>
 110    /// <param name="property">The property to filter. Must implement <see cref="IEnumerable{T}"/>.</param>
 111    /// <param name="nestedFilter">The nested class filter.</param>
 112    public EntityFilter<TEntity> AddNested<TProperty, TNested>(Expression<Func<TEntity, TProperty?>> property, EntityFil
 113        where TProperty : IEnumerable<TNested>
 114    {
 7115        if (nestedFilter == null)
 6116            return this;
 117
 1118        AddNestedInternal(property, nestedFilter);
 1119        return this;
 120    }
 121
 122    /// <summary>
 123    /// Replaces the filter for the given property. Existing filters for the same property are removed.
 124    /// </summary>
 125    /// <typeparam name="TProperty">The type of the property to be filtered.</typeparam>
 126    /// <param name="property">The property to filter.</param>
 127    /// <param name="filters">The filters to use.</param>
 128    public EntityFilter<TEntity> Replace<TProperty>(Expression<Func<TEntity, TProperty?>> property, params ValueFilter[]
 129    {
 10057130        ReplaceInternal(property, filters);
 10057131        return this;
 132    }
 133
 134    /// <summary>
 135    /// Replaces the nested filter for the given property. Existing filters for the same property are removed.
 136    /// </summary>
 137    /// <typeparam name="TProperty">The type of the property to be filtered.</typeparam>
 138    /// <param name="property">The property to filter.</param>
 139    /// <param name="nestedFilter">The nested class filter.</param>
 140    public EntityFilter<TEntity> ReplaceNested<TProperty>(Expression<Func<TEntity, TProperty?>> property, EntityFilter<T
 141    {
 13142        if (nestedFilter == null)
 0143            return Remove(property);
 144
 13145        ReplaceNestedInternal(property, nestedFilter);
 13146        return this;
 147    }
 148
 149    /// <summary>
 150    /// Replaces the nested filter for the given enumerable property. Existing filters for the same property are removed
 151    /// </summary>
 152    /// <typeparam name="TProperty">The type of the property to be filtered.</typeparam>
 153    /// <typeparam name="TNested">The nested type to be filtered.</typeparam>
 154    /// <param name="property">The property to filter. Must implement <see cref="IEnumerable{T}"/>.</param>
 155    /// <param name="nestedFilter">The nested class filter.</param>
 156    public EntityFilter<TEntity> ReplaceNested<TProperty, TNested>(Expression<Func<TEntity, TProperty?>> property, Entit
 157        where TProperty : IEnumerable<TNested>
 158    {
 15159        if (nestedFilter == null)
 0160            return Remove(property);
 161
 15162        ReplaceNestedInternal(property, nestedFilter);
 15163        return this;
 164    }
 165
 166    /// <summary>
 167    /// Remove all filters for the specified property.
 168    /// </summary>
 169    /// <typeparam name="TProperty">The type of the property to be filtered.</typeparam>
 170    /// <param name="property">The property to remove all filters for.</param>
 171    public EntityFilter<TEntity> Remove<TProperty>(Expression<Func<TEntity, TProperty?>> property)
 172    {
 11173        RemoveInternal(property);
 11174        return this;
 175    }
 176
 177    /// <summary>
 178    /// Removes all filters of all properties.
 179    /// </summary>
 180    public new EntityFilter<TEntity> Clear()
 181    {
 1182        base.Clear();
 1183        return this;
 184    }
 185
 186    /// <summary>
 187    /// Creates a deep clone of this filter.
 188    /// </summary>
 189    public new EntityFilter<TEntity> Clone()
 3352190        => JsonSerializer.Deserialize<EntityFilter<TEntity>>(JsonSerializer.Serialize(this))!;
 191
 192    /// <summary>
 193    /// Casts this filter to a different type (by creating a deep clone).
 194    /// Filtered properties are matched by type (check if assignable) and name (case-sensitive).
 195    /// </summary>
 196    /// <typeparam name="TDestination">The type of the destination entity to filter.</typeparam>
 197    public EntityFilter<TDestination> Cast<TDestination>()
 8198        => this.Cast<TEntity, TDestination>();
 199
 200    /// <summary>
 201    /// Creates the filter expression. Returns <c>null</c> when filter is empty.
 202    /// </summary>
 203    /// <param name="interceptor">An interceptor to manipulate the generated filters.</param>
 204    /// <param name="useAsCompiledExpression">Whether the generated expression will be compiled later. Used to determine
 205    public Expression<Func<TEntity, bool>>? CreateFilter(IFilterInterceptor? interceptor = null, bool useAsCompiledExpre
 18157206        => CreateFilter<TEntity>(interceptor, useAsCompiledExpression);
 207
 208    /// <summary>
 209    /// Performs an implicit conversion from <see cref="EntityFilter{TEntity}"/> to <see cref="Expression{TDelegate}"/> 
 210    /// </summary>
 211    /// <param name="filter">The filter to convert.</param>
 212    public static implicit operator Expression<Func<TEntity, bool>>(EntityFilter<TEntity> filter)
 1213        => filter.CreateFilter(useAsCompiledExpression: false) ?? (x => true);
 214
 215    /// <summary>
 216    /// Performs an implicit conversion from <see cref="EntityFilter{TEntity}"/> to <see cref="Func{T, TResult}"/>.
 217    /// </summary>
 218    /// <param name="filter">The filter to convert.</param>
 219    public static implicit operator Func<TEntity, bool>(EntityFilter<TEntity> filter)
 8220        => (filter.CreateFilter(useAsCompiledExpression: true) ?? (x => true)).Compile();
 221
 222    /// <inheritdoc />
 223    public override string ToString()
 38224        => CreateFilter()?.ToString() ?? string.Empty;
 225
 226    [ExcludeFromCodeCoverage]
 227    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
 228    private string DebuggerDisplay => CreateFilter()?.ToString() ?? "<EMPTY>";
 229}
 230
 231/// <inheritdoc cref="EntityFilter{TEntity}" />
 232[JsonConverter(typeof(EntityFilterConverter))]
 233public class EntityFilter : ICloneable
 234{
 235    private static readonly MethodInfo _createFilterMethod = typeof(EntityFilter).GetMethods(BindingFlags.Instance | Bin
 236
 237    internal List<PropertyFilter> PropertyFilters;
 238    internal List<NestedFilter> NestedFilters;
 239
 240    /// <summary>
 241    /// Gets or sets the default configuration. Can be used to set a system-wide configuration.
 242    /// </summary>
 243    public FilterConfiguration? Configuration { get; internal set; }
 244
 245    /// <summary>
 246    /// Initializes a new instance of the <see cref="EntityFilter"/> class.
 247    /// </summary>
 248    public EntityFilter()
 249    {
 250        PropertyFilters = [];
 251        NestedFilters = [];
 252    }
 253
 254    /// <summary>
 255    /// Initializes a new instance of the <see cref="EntityFilter"/> class.
 256    /// </summary>
 257    /// <param name="configuration">The configuration to use.</param>
 258    public EntityFilter(FilterConfiguration configuration)
 259        : this()
 260        => Configuration = configuration;
 261
 262    /// <inheritdoc />
 263    public object Clone()
 264        => JsonSerializer.Deserialize<EntityFilter>(JsonSerializer.Serialize(this))!;
 265
 266    /// <summary>
 267    /// Indicates whether this filter is empty.
 268    /// </summary>
 269    public bool IsEmpty() => !PropertyFilters.Any() && !NestedFilters.Any();
 270
 271    /// <summary>
 272    /// Indicates whether the specified property filter is empty.
 273    /// </summary>
 274    /// <typeparam name="TEntity">The type to be filtered.</typeparam>
 275    /// <typeparam name="TProperty">The type of the property to be filtered.</typeparam>
 276    /// <param name="property">The property to get the information for.</param>
 277    public bool IsEmpty<TEntity, TProperty>(Expression<Func<TEntity, TProperty?>> property)
 278        => GetPropertyFilterValuesInternal(property)?.All(value => value.IsEmpty) ?? true;
 279
 280    /// <inheritdoc cref="EntityFilter{TEntity}.Clear" />
 281    public void Clear()
 282        => PropertyFilters.Clear();
 283
 284    /// <summary>
 285    /// Gets the filter syntax for the given <typeparamref name="TProperty"/>.
 286    /// </summary>
 287    /// <typeparam name="TEntity">The type to be filtered.</typeparam>
 288    /// <typeparam name="TProperty">The type of the property to be filtered.</typeparam>
 289    /// <param name="property">The property to get the filter for.</param>
 290    protected string? GetPropertyFilterSyntaxInternal<TEntity, TProperty>(Expression<Func<TEntity, TProperty?>> property
 291    {
 292        var propertyName = property.GetPropertyName();
 293        var propertyFilter = PropertyFilters.FirstOrDefault(x => x.PropertyName.EqualsOrdinal(propertyName));
 294        return ValueFilterExtensions.ToString(propertyFilter?.ValueFilters);
 295    }
 296
 297    /// <summary>
 298    /// Get the filters applied to the given property.
 299    /// </summary>
 300    /// <typeparam name="TEntity">The type to be filtered.</typeparam>
 301    /// <typeparam name="TProperty">The type of the property to be filtered.</typeparam>
 302    /// <param name="property">The property to get the filter for.</param>
 303    protected ValueFilter[]? GetPropertyFilterValuesInternal<TEntity, TProperty>(Expression<Func<TEntity, TProperty?>> p
 304    {
 305        var propertyName = property.GetPropertyName();
 306        return PropertyFilters.FirstOrDefault(predicate => predicate.PropertyName.EqualsOrdinal(propertyName))?.ValueFil
 307    }
 308
 309    /// <summary>
 310    /// Gets the <see cref="EntityFilter{TProperty}"/> for the given nested class <typeparamref name="TProperty"/>.
 311    /// </summary>
 312    /// <typeparam name="TEntity">The type to be filtered.</typeparam>
 313    /// <typeparam name="TProperty">The type of the property to be filtered.</typeparam>
 314    /// <param name="property">The property to get the filter for.</param>
 315    protected EntityFilter<TProperty>? GetNestedFilterInternal<TEntity, TProperty>(Expression<Func<TEntity, TProperty?>>
 316    {
 317        var propertyName = property.GetPropertyName();
 318        return (EntityFilter<TProperty>?)NestedFilters.FirstOrDefault(x => x.PropertyName.EqualsOrdinal(propertyName))?.
 319    }
 320
 321    /// <summary>
 322    /// Gets the <see cref="EntityFilter{TProperty}"/> for the given nested list <typeparamref name="TList"/>.
 323    /// </summary>
 324    /// <typeparam name="TEntity">The type to be filtered.</typeparam>
 325    /// <typeparam name="TList">The type of the list of <typeparamref name="TProperty"/>.</typeparam>
 326    /// <typeparam name="TProperty">The type of the property to be filtered.</typeparam>
 327    /// <param name="property">The property to get the filter for.</param>
 328    protected EntityFilter<TProperty>? GetNestedFilterInternal<TEntity, TList, TProperty>(Expression<Func<TEntity, TList
 329        where TList : IEnumerable<TProperty>
 330    {
 331        var propertyName = property.GetPropertyName();
 332        return (EntityFilter<TProperty>?)NestedFilters.FirstOrDefault(x => x.PropertyName.EqualsOrdinal(propertyName))?.
 333    }
 334
 335    /// <summary>
 336    /// Adds a filter for the given property. Existing filters for the same property are preserved.
 337    /// </summary>
 338    /// <typeparam name="TEntity">The type to be filtered.</typeparam>
 339    /// <typeparam name="TProperty">The type of the property to be filtered.</typeparam>
 340    /// <param name="property">The property to filter.</param>
 341    /// <param name="valueFilters">The filters to use.</param>
 342    protected void AddInternal<TEntity, TProperty>(Expression<Func<TEntity, TProperty?>> property, ValueFilter[]? valueF
 343    {
 344        var propertyName = property.GetPropertyName();
 345        PropertyFilters.Add(new PropertyFilter(propertyName, valueFilters));
 346    }
 347
 348    /// <summary>
 349    /// Adds a nested filter for the given property. Existing filters for the same property are preserved.
 350    /// </summary>
 351    /// <typeparam name="TEntity">The type to be filtered.</typeparam>
 352    /// <typeparam name="TProperty">The type of the property to be filtered.</typeparam>
 353    /// <typeparam name="TNested">The nested type to be filtered.</typeparam>
 354    /// <param name="property">The property to filter.</param>
 355    /// <param name="nestedFilter">The nested class filter.</param>
 356    protected void AddNestedInternal<TEntity, TProperty, TNested>(Expression<Func<TEntity, TProperty?>> property, Entity
 357    {
 358        var propertyName = property.GetPropertyName();
 359        NestedFilters.Add(new NestedFilter(propertyName, nestedFilter));
 360    }
 361
 362    /// <summary>
 363    /// Replaces the filter for the given property using the default filter operator. Existing filters for the same prop
 364    /// </summary>
 365    /// <typeparam name="TEntity">The type to be filtered.</typeparam>
 366    /// <typeparam name="TProperty">The type of the property to be filtered.</typeparam>
 367    /// <param name="property">The property to filter.</param>
 368    /// <param name="valueFilters">The filters to use.</param>
 369    protected void ReplaceInternal<TEntity, TProperty>(Expression<Func<TEntity, TProperty?>> property, ValueFilter[]? va
 370    {
 371        var propertyName = property.GetPropertyName();
 372        PropertyFilters.RemoveAll(x => x.PropertyName.EqualsOrdinal(propertyName));
 373        PropertyFilters.Add(new PropertyFilter(propertyName, valueFilters));
 374    }
 375
 376    /// <summary>
 377    /// Replaces the nested filter for the given property. Existing filters for the same property are removed.
 378    /// </summary>
 379    /// <typeparam name="TEntity">The type to be filtered.</typeparam>
 380    /// <typeparam name="TProperty">The type of the property to be filtered.</typeparam>
 381    /// <typeparam name="TNested">The nested type to be filtered.</typeparam>
 382    /// <param name="property">The property to filter.</param>
 383    /// <param name="nestedFilter">The nested class filter.</param>
 384    protected void ReplaceNestedInternal<TEntity, TProperty, TNested>(Expression<Func<TEntity, TProperty?>> property, En
 385    {
 386        var propertyName = property.GetPropertyName();
 387        NestedFilters.RemoveAll(x => x.PropertyName.EqualsOrdinal(propertyName));
 388        NestedFilters.Add(new NestedFilter(propertyName, nestedFilter));
 389    }
 390
 391    /// <inheritdoc cref="EntityFilter{TEntity}.Remove{TProperty}(Expression{Func{TEntity, TProperty}})" />
 392    protected void RemoveInternal<TEntity, TProperty>(Expression<Func<TEntity, TProperty?>> property)
 393    {
 394        var propertyName = property.GetPropertyName();
 395        PropertyFilters.RemoveAll(x => x.PropertyName.EqualsOrdinal(propertyName));
 396    }
 397
 398    /// <inheritdoc cref="EntityFilter{TEntity}.CreateFilter" />
 399    protected internal Expression<Func<TEntity, bool>>? CreateFilter<TEntity>(IFilterInterceptor? interceptor, bool useA
 400    {
 401        var configuration = Configuration ?? FilterConfiguration.Default ?? new FilterConfiguration();
 402        interceptor ??= IFilterInterceptor.Default;
 403
 404        var properties = typeof(TEntity).GetProperties();
 405        var useConditionalAccess = UseConditionalAccess(configuration, useAsCompiledExpression);
 406
 407        var propertyFilters = GetPropertyFilters<TEntity>(properties, interceptor, configuration);
 408        var nestedObjectFilters = GetNestedObjectFilters<TEntity>(properties, useConditionalAccess, interceptor, useAsCo
 409        var nestedListsFilters = CreateNestedListFilters<TEntity>(properties, useConditionalAccess, interceptor, useAsCo
 410
 411        return propertyFilters
 412            .Concat(nestedObjectFilters)
 413            .Concat(nestedListsFilters)
 414            .CombineWithConditionalAnd();
 415    }
 416
 417    private List<Expression<Func<TEntity, bool>>?> GetPropertyFilters<TEntity>(PropertyInfo[] properties, IFilterInterce
 418        => properties
 419            .Reverse()
 420            .Join(
 421                PropertyFilters,
 422                x => x.Name,
 423                x => x.PropertyName,
 424                (propertyInfo, propertyFilter) => new { Property = propertyInfo, propertyFilter.ValueFilters },
 425                StringComparer.Ordinal
 426            )
 427            .Select(x =>
 428                interceptor?.CreatePropertyFilter<TEntity>(x.Property, x.ValueFilters, configuration)
 429                ?? PropertyFilterExpression.CreateFilter<TEntity>(x.Property, x.ValueFilters, configuration, interceptor
 430            )
 431            .ToList();
 432
 433    private List<Expression<Func<TEntity, bool>>?> GetNestedObjectFilters<TEntity>(PropertyInfo[] properties, bool useCo
 434    {
 435        return properties
 436            .Reverse()
 437            .Where(x => !x.PropertyType.IsGenericIEnumerable())
 438            .Join(
 439                NestedFilters,
 440                x => x.Name,
 441                x => x.PropertyName,
 442                (propertyInfo, nestedFilter) => new { Property = propertyInfo, nestedFilter.EntityFilter },
 443                StringComparer.Ordinal
 444            )
 445            .Select(x =>
 446            {
 447                var createFilterExpression = _createFilterMethod.MakeGenericMethod(x.Property.PropertyType);
 448                var nestedFilterExpression = (LambdaExpression?)createFilterExpression.Invoke(x.EntityFilter, [intercept
 449                if (nestedFilterExpression == null)
 450                    return null;
 451
 452                var propertySelector = typeof(TEntity).CreatePropertySelector(x.Property.Name);
 453                var propertyMatchesNested = (Expression<Func<TEntity, bool>>)nestedFilterExpression.ReplaceParameter(pro
 454
 455                if (!useConditionalAccess)
 456                    return propertyMatchesNested;
 457
 458                var propertyIsNotNull = propertySelector.IsNotNull(x.Property.PropertyType);
 459                var propertyIsNotNullLambda = propertySelector.CreateLambda<TEntity, bool>(propertyIsNotNull);
 460
 461                var filterExpression = new[] { propertyIsNotNullLambda, propertyMatchesNested }.CombineWithConditionalAn
 462                return filterExpression;
 463            })
 464            .ToList();
 465    }
 466
 467    private List<Expression<Func<TEntity, bool>>?> CreateNestedListFilters<TEntity>(PropertyInfo[] properties, bool useC
 468        => properties
 469            .Reverse()
 470            .Where(x => x.PropertyType.IsGenericIEnumerable())
 471            .Join(
 472                NestedFilters,
 473                x => x.Name,
 474                x => x.PropertyName,
 475                (propertyInfo, nestedFilter) => new { Property = propertyInfo, nestedFilter.EntityFilter },
 476                StringComparer.Ordinal
 477            )
 478            .Select(x =>
 479            {
 480                var propertyType = x.Property.PropertyType.GetGenericArguments()[0];
 481                var createFilterExpression = _createFilterMethod.MakeGenericMethod(propertyType);
 482                var nestedFilterExpression = (LambdaExpression?)createFilterExpression.Invoke(x.EntityFilter, [intercept
 483                if (nestedFilterExpression == null)
 484                    return null;
 485
 486                var propertySelector = typeof(TEntity).CreatePropertySelector(x.Property.Name);
 487                var propertyHasAnyNested = (Expression<Func<TEntity, bool>>)propertySelector.EnumerableAny(propertyType,
 488
 489                if (!useConditionalAccess)
 490                    return propertyHasAnyNested;
 491
 492                var propertyIsNotNull = propertySelector.IsNotNull(x.Property.PropertyType);
 493                var propertyIsNotNullLambda = propertySelector.CreateLambda<TEntity, bool>(propertyIsNotNull);
 494
 495                var filterExpression = new[] { propertyIsNotNullLambda, propertyHasAnyNested }.CombineWithConditionalAnd
 496                return filterExpression;
 497            })
 498            .ToList();
 499
 500    private static bool UseConditionalAccess(FilterConfiguration configuration, bool usedAsCompiledExpression)
 501        => configuration.UseConditionalAccess switch
 502        {
 503            FilterConditionalAccess.Always => true,
 504            FilterConditionalAccess.Never => false,
 505            _ => usedAsCompiledExpression
 506        };
 507}

Methods/Properties

.ctor()
.ctor(Plainquire.Filter.Abstractions.FilterConfiguration)
IsEmpty(System.Linq.Expressions.Expression`1<System.Func`2<TEntity,TProperty>>)
GetPropertyFilterSyntax(System.Linq.Expressions.Expression`1<System.Func`2<TEntity,TProperty>>)
GetPropertyFilterValues(System.Linq.Expressions.Expression`1<System.Func`2<TEntity,TProperty>>)
GetNestedFilter(System.Linq.Expressions.Expression`1<System.Func`2<TEntity,TProperty>>)
GetNestedFilter(System.Linq.Expressions.Expression`1<System.Func`2<TEntity,TList>>)
Add(System.Linq.Expressions.Expression`1<System.Func`2<TEntity,TProperty>>,Plainquire.Filter.ValueFilter[])
AddNested(System.Linq.Expressions.Expression`1<System.Func`2<TEntity,TProperty>>,Plainquire.Filter.EntityFilter`1<TProperty>)
AddNested(System.Linq.Expressions.Expression`1<System.Func`2<TEntity,TProperty>>,Plainquire.Filter.EntityFilter`1<TNested>)
Replace(System.Linq.Expressions.Expression`1<System.Func`2<TEntity,TProperty>>,Plainquire.Filter.ValueFilter[])
ReplaceNested(System.Linq.Expressions.Expression`1<System.Func`2<TEntity,TProperty>>,Plainquire.Filter.EntityFilter`1<TProperty>)
ReplaceNested(System.Linq.Expressions.Expression`1<System.Func`2<TEntity,TProperty>>,Plainquire.Filter.EntityFilter`1<TNested>)
Remove(System.Linq.Expressions.Expression`1<System.Func`2<TEntity,TProperty>>)
Clear()
Clone()
Cast()
CreateFilter(Plainquire.Filter.IFilterInterceptor,System.Boolean)
op_Implicit(Plainquire.Filter.EntityFilter`1<TEntity>)
op_Implicit(Plainquire.Filter.EntityFilter`1<TEntity>)
ToString()