runxc1 / MicroRuleEngine

A .Net Rule Engine for dynamically evaluating business rules compiled on the fly.
MIT License
186 stars 74 forks source link

[Feature Request] Add the ability to filter and aggregate IEnumerables before comparing to a value #31

Open RubberChickenParadise opened 3 years ago

RubberChickenParadise commented 3 years ago

In one of my recent projects I need to dynamically code similar to the following:

var woot = new Foo
           {
               Bar = new List<Bar>
                     {
                         new Bar {Baz = 1m, Type = "A"}, new Bar {Baz = 1m, Type = "A"}, new Bar {Baz = 1m, Type = "A"},
                         new Bar {Baz = 1m, Type = "A"}, new Bar {Baz = 1m, Type = "B"},
                     }
           };

if (woot.Bar.Where(x => x.Type == "A")
        .Sum(x => x.Baz) > 5)
{
    //Do stuff
}

Please add functionality that will enable filtering and aggregating an IEnumerable before comparing to the greater than 5.

RubberChickenParadise commented 3 years ago

I have been working on this and came up with the following new Rule structure.

[DataContract]
public class Selector
{
    [DataMember] public string MemberName { get; set; }
    [DataMember] public string Operator   { get; set; }
}

[DataContract]
public class Rule : Selector
{
    public Rule()
    {
        Inputs = Enumerable.Empty<object>();
    }

    [DataMember] public object TargetValue { get; set; }

    [DataMember] public Rule                EnumerableFilter      { get; set; }
    [DataMember] public Selector            EnumerableValueExpression { get; set; }
    [DataMember] public IList<Rule>         Rules           { get; set; }
    [DataMember] public IEnumerable<object> Inputs          { get; set; }

// All the rest of the stuff

}

with the rule structure of:

new Rule
{
    MemberName  = "Items",
    EnumerableFilter = new Rule
                       {
                           MemberName = "ItemCode",
                           Operator = "StartsWith",
                           Inputs = new []{"M"}
                       },
    EnumerableVauleExpression = new Selector
                                {
                                    MemberName = "Cost",
                                    Operator   = "Sum"
                                },
    Operator    = "GreaterThan",
    TargetValue = 5
};

when targeting an order that is in the unit tests:

new Order()
{
  OrderId = 1,
  Customer = new Customer()
             {
                 FirstName = "John",
                 LastName  = "Doe",
                 Country = new Country()
                           {
                               CountryCode = "AUS"
                           }
             },
  Items = new List<Item>()
          {
              new Item { ItemCode = "MM23", Cost =5.25M},
              new Item { ItemCode = "MM23", Cost =6.25M},
              new Item { ItemCode = "LD45", Cost =5.25M},
              new Item { ItemCode = "Test", Cost =3.33M},
          },
  Codes = new List<int>()
          {
              555,
              321,
              243
          },
  Total     = 13.83m,
  OrderDate = new DateTime(1776, 7, 4),
  Status    = Status.Open

};

What is everyone else's thoughts on this?

cocowalla commented 2 years ago

Would be nice if EnumerableFilter would work with generic methods, such as to filter a collection by type with .OfType<SomeType>?

RubberChickenParadise commented 2 years ago

@cocowalla

Would be nice if EnumerableFilter would work with generic methods, such as to filter a collection by type with .OfType<SomeType>?

The EnumerableFilter Works by using the linq Where() and passing an IExpression<Func<T,bool>> to filter the collection then the value selector to pass an Expression<Func<T>> to a method such as Sum But you could use Count Or Any Just the same.

The way I would look at solving this would be to extend Rule To check for a type based on the fully qualified name of the type. I’m not sure if Rule supports that already or not.