1

Given the following domain model

public class Person
{
  public string Name { get; set; }
  public int Age { get; set; }
  public List<Car> Cars { get; set; }
}
public class Car
{
    public int Year { get; set; }
    public string Make { get; set; }
}

Is it possible to write a rule that performs an action on all cars newer than 2016 owned by people under a the age of 30? I'm doing this while only inserting Person objects as facts.

Person p1 = new Person("Jim", 31);
p1.Cars = GetCars(4);
Person p2 = new Person("Bob", 29);
p2.Cars = GetCars(4);

session.Insert(p1);
session.Insert(p2);

I've tried something like this. I'm guessing I could get it to work if I added a reference in the Car back to the Person that owns it, but my actual use case would make this difficult. I am hoping I am just missing something.

public class CarTest : Rule
{
  public override void Define()
  {
    Person person = null;           
    IEnumerable<Car> cars = null;

    When()
      .Match<Person>(() => person, p => p.Age < 30)
      .Query(() => cars, x => x
         .Match<Car>(c => c == person.Cars.Find(f=> f.Make == c.Make && f.Year == c.Year), c => c.Year > 2016)
         .Collect()
         .Where(p => p.Any()));
    Then()
      .Do(ctx => DoSomethingWithNewCarsThatBelongToYoungPeople(cars));

  }

  private static void DoSomethingWithNewCarsThatBelongToYoungPeople(IEnumerable<Car> cars)
  {
     foreach (var car in cars)
     {
        //Do Something
     }
  }
}
jalley
  • 105
  • 1
  • 5

1 Answers1

3

The best way to handle aggregations over complex matches with joins is to break this up into two rules and use forward chaining.

The first rule matches a given young person with their cars that are considered new. It then yields a new fact that wraps the results.

public class YoungPersonWithNewCarRule : Rule
{
    public override void Define()
    {
        Person person = null;
        IEnumerable<Car> cars = null;

        When()
            .Match(() => person, p => p.Age < 30)
            .Let(() => cars, () => person.Cars.Where(c => c.Year > 2016))
            .Having(() => cars.Any());
        Then()
            .Yield(ctx => new YoungPersonWithNewCar(person, cars));
    }
}

public class YoungPersonWithNewCar
{
    private readonly Car[] _cars;

    public Person Person { get; }
    public IEnumerable<Car> Cars => _cars;

    public YoungPersonWithNewCar(Person person, IEnumerable<Car> cars)
    {
        _cars = cars.ToArray();
        Person = person;
    }
}

The second rule matches the facts produced by the first rule and aggregates them into a collection.

public class YoungPeopleWithNewCarsHandlingRule : Rule
{
    public override void Define()
    {
        IEnumerable<YoungPersonWithNewCar> youngPeopleWithNewCars = null;

        When()
            .Query(() => youngPeopleWithNewCars, q => q
                .Match<YoungPersonWithNewCar>()
                .Collect()
                .Where(c => c.Any()));
        Then()
            .Do(ctx => DoSomethingWithNewCarsThatBelongToYoungPeople(youngPeopleWithNewCars));
    }

    private void DoSomethingWithNewCarsThatBelongToYoungPeople(IEnumerable<YoungPersonWithNewCar> youngPeopleWithNewCars)
    {
        //
    }
}
Sergiy Nikolayev
  • 702
  • 4
  • 13