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
}
}
}
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)
{
//
}
}