简体   繁体   中英

How to change IObservable Func predicates at runtime in C#

I'm new to dynamic data and in general the reactive extension world and I'm currently facing a following problem, where I'd like to change the IObservable<Func<T,bool>> predicates at runtime by using the dynamic data package and thus the reactive extensions in .NET (C#).

Considering the following situation, I have a DataGrid with some columns of type integer , lets say A,B,C . Furthermore, there is a filter UI where the user can add multiple filters, like A == 6 or a combination of filter expressions, like A == 7 || A == 3 || B == 5 , etc. So basically my method returning a Func<T, bool> delegate looks like this:

private Func<T, bool> FilterOnA(string id)
{
    return n => n.Id == int.Parse(id);
}

And the Filter method call in the data pipeline:

    // sourceList is used to fill the ReadOnlyObservableCollection<T> while receiving data from an event pattern

    sourceList.Connect()                        // SourceList<T>
              .Filter(filterViewModel.FilterOnA)
              .Bind(out _itemsBinding)          // private ReadOnlyObservableCollection<T>
              .DisposeMany()
              .Subscribe();

As I mentioned above, the user shall be able to add/remove/modify and more importanlty combine the filter expressions all together.

Since the dynamic's data Filter method is taking a Func<T,bool> or an IObservable<Func<T,bool>> , one possible solution might look like this:

public IObservable<Func<T,bool>> Filter1 {get;} 
public IObservable<Func<T,bool>> Filter2 {get;}
public IObservable<Func<T,bool>> Filter3 {get;}
public IObservable<Func<T,bool>> FilterX {get;}

public IObservable<Func<T,bool>> AllFiltersCombined => Filter1.CombineLatest(Filter2,Filter3,FilterX, (f1,f2,f3,fx) => AggregatePredicatesAnd(f1,f2,f3,fx));


public static Func<T,Bool> AggregatePredicatesAnd(params Func<T,bool>[] predicates) 
{
    return predicates.Aggregate<Func<T,bool>>((fa,fb) => (T t) => fa(t) && fb(t));
}

Now, my problem is, how to write this in a more generic way? How to combine for eg 0 to n Filters? And what is about different filter types, eg a combination of A <= 7 && A != 5 ?

You could use something like the ApplyFilters operation shown here:

public static class Extensions
{
    public static List<T> Apply<T>(this List<T> list, Action<List<T>> action)
    {
        action(list);

        return list;
    }

    public static IObservable<T> ApplyFilters<T>(this IObservable<T> source, IObservable<Func<T, bool>> AddFilter, IObservable<Func<T, bool>> RemoveFilter)
    {
        // Project AddFilter to a func that adds a filter to a list of filters
        var adding = AddFilter.Select(func => (Action<List<Func<T, bool>>>)(list => list.Add(func)));

        // Project RemoveFilter to a func that removes a filter from a list of filters
        var removing = RemoveFilter.Select(func => (Action<List<Func<T, bool>>>)(list => list.Remove(func)));

        // Return an observable that...
        return Observable
            // ... merges the add and remove operations ...
            .Merge(adding, removing)
            // ... and applies them to an initially empty list ...
            .Scan(new List<Func<T, bool>>(), (list, change) => list.Apply(change))
            // ... and project every list change to a new observable
            // by applying all operations to the source observable ...
            .Select(list => list.Aggregate(source, (s, f) => s.Where(f)))
            // ... and finally subscribing to the new observable
            .Switch();
    }
}

public class ViewModel
{
    public IObservable<Item> _source;

    public IObservable<Func<Item, bool>> _addFilter;

    public IObservable<Func<Item, bool>> _removeFilter;

    public ViewModel()
    {
        FilteredItems = _source.ApplyFilters(_addFilter, _removeFilter);
    }

    public IObservable<Item> FilteredItems { get; }
}

public class Item  {  }

Limitations:

  1. No consideration has been made regarding Func<T, bool> equivalence. You may need to strongly type each filter func to ensure it's able to be added / removed from the internal list of filters correctly.

  2. No consideration has been made regarding grouping && and || operations (ie (A == 7 && B == "Hello") || C == -1) ). If this is important you will definitely need to strongly type the filters (per 1) and add a group identifer. You could then use a GroupBy on the List prior to performing an Aggregate on the observable.

In contrast to ibeebs answer, I've also discovered another possible solution. In order to build possible combinations of filters, one might use the Dynamic Expressions lib: https://github.com/zzzprojects/System.Linq.Dynamic/wiki/Dynamic-Expressions and create expressions like this one:

Expression<Func<T, bool>> e2 = DynamicExpression.ParseLambda<T, bool>("Id == 7 or Id == 9");

or more complex ones.

Since I've mentioned in my question that the User is able to create the filters via UI, it is not a big deal then to build string expressions like "Id == 7 or Id == 9" .

Such Expression can be simply compiled to a Func<T, bool> .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM