Skip to content

Dynamic or Computed Filters #178

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
jaredcnance opened this issue Oct 24, 2017 · 2 comments
Closed

Dynamic or Computed Filters #178

jaredcnance opened this issue Oct 24, 2017 · 2 comments

Comments

@jaredcnance
Copy link
Contributor

jaredcnance commented Oct 24, 2017

A dynamic/computed filter can be defined based on a provided value:

?filter[was-active-on]=2017-10-24
[DynamicFilter("was-active-on")]
public bool WasActiveOn(DateTime date)
{
  // ... 
}

Requirements:

  • must return a boolean value
  • ...

Design/Random Thoughts:

  • limit reflection to startup if possible
  • can we get away from any runtime reflection if we require the method to be static (possibly extension method or one that accepts a paramter of type TResource)?
  • how to communicate the negative impact on application performance? i.e. EF will probably have to load all resources into memory and then apply the filter

@jaredcnance
Copy link
Contributor Author

jaredcnance commented Dec 1, 2017

Creating a naive implementation that supports the following:

public class FooEvent : Identifiable {  
  [DynamicFilter("was-active-on")]
  public bool WasActiveOn(DateTime date) => (date > StartedAt && date < EndedAt)
}

Seems simple but it may be properly translated by EF. However, this opens up the possibility for much more complex queries (i.e. anything can go in the WasActiveOn body) and could have unexpected issues.

Perhaps it would make more sense to offer some access to the IQueryable to be filtered. Filtering (and sorting for that matter) could be pulled out of the repository into something like:

interface IEntityFilter<TEntity> {
  IQueryable<TEntity> Filter(IQueryable<TEntity> entities, FilterQuery filterQuery);
}

This would allow more obvious implementations (less hiding of the fact that this is being passed to EF), through DI:

public class DefaultEntityFilter<TEntity> : IEntityFilter<TEntity> { /* ... */ }

public class FooFilter : DefaultEntityFilter<Foo>  { 
  public IQueryable<Foo> Filter(IQueryable<Foo> foos, FilterQuery filterQuery) {
     if(string.Equals(filterQuery.Attribute, "was-active-on", StringComparison.OrdinalIgnoreCase))
       return FilterWasActiveOn(foos, filterQuery);

     return base.Filter(foos, filterQuery);
  }
  // ...
}

@jaredcnance jaredcnance added this to the v3.0.0 milestone Dec 7, 2017
@jaredcnance jaredcnance modified the milestones: v3.0, 4.0 Sep 4, 2018
@jaredcnance
Copy link
Contributor Author

This should be resolved by ResourceDefinition.GetQueryFilters

/// <summary>
/// Define a set of custom query expressions that can be applied
/// instead of the default query behavior. A common use-case for this
/// is including related resources and filtering on them.
/// </summary>
/// <returns>
/// A set of custom queries that will be applied instead of the default
/// queries for the given key. Null will be returned if default behavior
/// is desired.
/// </returns>
/// <example>
/// <code>
/// protected override QueryFilters GetQueryFilters() => {
/// { "facility", (t, value) => t.Include(t => t.Tenant)
/// .Where(t => t.Facility == value) }
/// }
/// </code>
///
/// If the logic is simply too complex for an in-line expression, you can
/// delegate to a private method:
/// <code>
/// protected override QueryFilters GetQueryFilters()
/// => new QueryFilters {
/// { "is-active", FilterIsActive }
/// };
///
/// private IQueryable&lt;Model&gt; FilterIsActive(IQueryable&lt;Model&gt; query, string value)
/// {
/// // some complex logic goes here...
/// return query.Where(x => x.IsActive == computedValue);
/// }
/// </code>
/// </example>
public virtual QueryFilters GetQueryFilters() => null;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests

1 participant