This example is a bit complicated since it takes a collection of facts, not a single fact, but the same principle applies. Also, note that if you want to have the changes you make affect subsequent processing of the rules, you need to update the fact in the rule engine. You can see this in the call from the example:
.Do(ctx => ctx.UpdateAll(orders));
For a single fact, you can call:
.Do(ctx => ctx.Update(myFact));
context.InsertAll(pagos);
but this generates this exception: System.OutOfMemoryException: Exception of type 'System.OutOfMemoryException' was thrown. Do you have any hint to deal with massive Fact insertion?
Here is a response @snikolayev gave on this topic last year. You can search the gitter to find it in context and see the surrounding conversation:
I don't think I can tell you exactly what the problem you are experiencing is caused by, because it very much depends on your rules, but I can generally describe why that could be happening, which can help you diagnose it. At the most general level, Rete algorithm (which powers the execution of the rules) sacrifices memory for speed. The way it does it, is it stores partial matches in memory. So, if you have a very large partial match, particularly a Cartesian Product of a set of facts, even if it is filtered down later in the graph, you can blow up the memory. An example is, say you have a 1000 facts of one kind, and a 1000 facts of another kind, and then you join them without a specific join condition, you are essentially creating a partial match of 1M of pairs (aka Cartesian Product). Here is a more specific example of another NRules user having a similar problem: NRules/NRules#148
catch all
for example: Catch all: If it is an incident raise a notification. More specific rule: When it is an incident concerning memory wait for 10 minutes before raising a notification. If the latter rule applies, don't evaluate the former. Otherwise i have to explicity exclude the case for the latter rule in the former which makes it kind of complex.
In pseudo code something like this
if (incident.Reason == "memory")
{
if (incident.Created < now.AddMinutes(-10))
{
// notify (specialized rule)
}
// don't notify (specialized rule)
}
else
{
// notify (catch all)
}
Not sure if this clears things up or makes it more confusing :see_no_evil:
@LunicLynx_twitter A pattern like this is definitely possible. One way you might do this is to retract your event facts when they're "handled" by a rule. Alternately, you could mark up your events when they're handled, so that your catch-all rule won't ever match on them.
Then you'd need some kind of gating factor to ensure that your higher specificity rule will always be able to act first. You could use time for this, as you described above, by tracking a "createdTime" in your fact, which you match on in your catch-all rule. Alternately, you could use the NRules agenda ordering mechanism to ensure that your catch-all rules will fire later than your higher-specificity ones.
One note about time in specific is that, if you want to use it, you'll need to have a Time Fact in your session, which you're updating at some interval to tell the rule engine to re-evaluate anything that's time dependent.
I have using this code in session 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);
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
}
}
}
this the code.pls help to solve this
When()
.Query(() => cars, x => x
.Match<Person>(p => p.Age < 30)
.SelectMany(p => p.Cars)
.Where(c => c.Year > 2016)
.Collect()
.Where(p => p.Any()));
I think in this case you are better off using forward chaining. You would need two rules for that. First, you have a rule similar to the one you had originally, where you match a single person along with their cars, and wrap the result in a new fact, which you yield from the right-hand side of the rule.
When()
.Match<Person>(() => person, p => p.Age < 30)
.Let(() => cars, () => person.Cars.Where(c => c.Year > 2016))
.Having(() => cars.Any());
Then()
.Yield(ctx => new YoungPersonWithNewCar(person, cars));
Then another rule matches these new facts and collects them.
When()
.Query(() => youngPeopleWithNewCars, q => q
.Match<YoungPersonWithNewCar>()
.Collect()
.Where(c => c.Any()));
Then()
.Do(ctx => DoSomethingWithNewCarsThatBelongToYoungPeople(youngPeopleWithNewCars));
@snikolayev I saw that you're doing some work on rete optimization, such as this commit here:
If it'd be helpful, I'd be happy to try a new version with our project and provide a comparison using the Unity 3D Engine's profiler again, as I did in this issue:
If there are two commits you'd like me to do an A/B comparison with, just let me know.
Or I can wait a bit if there's more you're planning to do.
@przemekwojcik Check out ISession.Events.LhsExpressionEvaluatedEvent. This event fires every time a When (LHS) expression is evaluated, and references all rules that contain this expression. You can monitor these events over time to see what is matching for a given rule.
In a rete net, you have to do a bit of legwork to figure out exactly which pattern matches cause a rule to fire because expressions can be shared between multiple rules, and their results are cached.
Therefore you need to build some tooling to do monitor and associate evaluations with a given rule firing.