@snikolayev I'm experimenting with compiling and creating multiple sessions asynchronously in separate threads. In general, this is working well, but very infrequently I see a "Recursive type definition detected" exception during the process.
The stack trace where I get the failed to build rule definition exception:
at NRules.Fluent.RuleDefinitionFactory.Create (NRules.Fluent.Dsl.Rule rule) [0x0001b] in <685ed49952174053b3ab0832cc9b6e51>:0
at NRules.Fluent.RuleDefinitionFactory.Create (System.Collections.Generic.IEnumerable`1[T] rules) [0x00016] in <685ed49952174053b3ab0832cc9b6e51>:0
at NRules.Fluent.RuleLoadSpec.Load () [0x0001d] in <685ed49952174053b3ab0832cc9b6e51>:0
at NRules.Fluent.RuleRepository.Load (System.Action`1[T] specAction) [0x0002a] in <685ed49952174053b3ab0832cc9b6e51>:0
The underlying LINQ exception itself:
Recursive type definition detected:
at System.Linq.Expressions.Expression`1[TDelegate].Create (System.Linq.Expressions.Expression body, System.String name, System.Boolean tailCall, System.Collections.Generic.IReadOnlyList`1[T] parameters) [0x00025] in <fbb5ed17eb6e46c680000f8910ebb50c>:0
Any thoughts? Should it be threadsafe to do this? Perhaps I need to load my rule repositories synchronously, but then it's safe to compile them asynchronously?
Thanks!
@snikolayev Unfortunately, my log didn't include the type, but I believe it's a TypeLoadException
The rule sets I'm loading are orthogonal, but they do reference many of the same types.
@fkucuk You can have a right hand side ("Then") clause call an Action that is a method. Within that method, you can set fields, etc. You just need to be sure to pass any local variables into the method doing the operations.
See the PreferredCustomerDiscountRule.ApplyDiscount method here https://github.com/NRules/NRules/wiki/Getting-Started for a good example.
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.