question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Support nested patterns and joins within ForAllElement

See original GitHub issue

I’m trying to create a ”ForAll” rule using the RuleBuilder API where additional facts are joined onto the fact type used in the ForAll rule.

This can be achieved with Any/None rules by using the GroupBuilder, but the patterns collection on ForAll does not work in the same way. See below:

using NRules.IntegrationTests.TestAssets;
using NRules.RuleModel;
using NRules.RuleModel.Builders;
using System;
using System.Linq.Expressions;
using Xunit;

namespace NRules.IntegrationTests
{
    public class ThreeFactForAllRuleBuilderTest : BaseRuleTestFixture
    {
        [Fact]
        public void ThreeFacts()
        {
            //Arrange
            var builder = new RuleBuilder();
            builder.Name("Test Rule");

            var lhs = builder.LeftHandSide();
            var patternBuilder1 = lhs.Pattern(typeof(FactType1), "fact1");

            Expression<Func<FactType1, FactType2, bool>> fact1_2_Join = (fact1, fact2) => fact2.JoinProperty == fact1.TestProperty;
            
            // Adding this makes it work as a workaround, but exports a FactType2 pattern
            //lhs.Pattern(typeof(FactType2), "f2");
            //
            var patternBuilder2_Base = new PatternBuilder(typeof(FactType2), "fact2");
            patternBuilder2_Base.Condition(fact1_2_Join);

            var patternBuilder2_Actual = new PatternBuilder(typeof(FactType2), "f2"); // Use f2 to avoid collision of names, Fluent also uses different names for BasePattern and Pattern
            Expression<Func<FactType2, bool>> condition21 = f2 => f2.TestProperty.StartsWith("Valid");
            patternBuilder2_Actual.Condition(condition21);

            var patternBuilder3 = new PatternBuilder(typeof(FactType3), "fact3");
            Expression<Func<FactType2, FactType3, bool>> fact2_3_Join = (f2, fact3) => fact3.JoinProperty == f2.TestProperty;
            patternBuilder3.Condition(fact2_3_Join);

            var allBuilder = lhs.ForAll();
            allBuilder.BasePattern(patternBuilder2_Base);
            allBuilder.Pattern(patternBuilder2_Actual);
            allBuilder.Pattern(patternBuilder3); // Commenting out this line, the test passes

            Expression<Action<IContext, FactType1>> action = (context, fact1) => NoOp();
            builder.RightHandSide().Action(action);

            //Act
            var rule = builder.Build();
        }

        [Fact]
        public void ThreeFacts_WorksForExists()
        {
            //Arrange
            var builder = new RuleBuilder();
            builder.Name("Test Rule");

            var lhs = builder.LeftHandSide();
            var patternBuilder1 = lhs.Pattern(typeof(FactType1), "fact1");

            var patternBuilder2 = new PatternBuilder(typeof(FactType2), "fact2");
            Expression<Func<FactType1, FactType2, bool>> fact1_2_Join = (fact1, fact2) => fact2.JoinProperty == fact1.TestProperty;
            Expression<Func<FactType2, bool>> condition21 = fact2 => fact2.TestProperty.StartsWith("Valid");
            patternBuilder2.Condition(fact1_2_Join);
            patternBuilder2.Condition(condition21);

            var patternBuilder3 = new PatternBuilder(typeof(FactType3), "fact3");
            Expression<Func<FactType2, FactType3, bool>> fact2_3_Join = (fact2, fact3) => fact3.JoinProperty == fact2.TestProperty;
            patternBuilder3.Condition(fact2_3_Join);

            var groupBuilder = new GroupBuilder();
            groupBuilder.Pattern(patternBuilder2);
            groupBuilder.Pattern(patternBuilder3);

            var existsBuilder = lhs.Exists();
            existsBuilder.Group(groupBuilder);

            Expression<Action<IContext, FactType1>> action = (context, fact1) => NoOp();
            builder.RightHandSide().Action(action);

            //Act
            var rule = builder.Build();
        }

        public static void NoOp()
        {
        }

        protected override void SetUpRules()
        {
        }

        public class FactType1
        {
            public string TestProperty { get; set; }
        }

        public class FactType2
        {
            public string TestProperty { get; set; }
            public string JoinProperty { get; set; }
        }

        public class FactType3
        {
            public string TestProperty { get; set; }
            public string JoinProperty { get; set; }
        }
    }
}

From investigation, it appears that the ForAll patterns cannot “import” parameters from each other and require their dependent patterns to be added to the left hand side of the rule rather than just the ForAllBuilder.

I’m not sure if this is a bug, feature request or support issue. Am I using the ForAllPattern incorrectly and is there any reason Any/None take a group but ForAll cant?

Issue Analytics

  • State:open
  • Created 4 years ago
  • Comments:10 (10 by maintainers)

github_iconTop GitHub Comments

1reaction
snikolayevcommented, Dec 18, 2019

Hello, everything is great, thank you! Hope it’s all good on your side too. To be honest I haven’t worked on this feature. The refactoring I committed, and what I’m working on right now is geared towards performance. In particular, I’m making changes to facilitate the optimization of the Rete graph topology (e.g. only re-evaluating expressions when there are changes to facts that the expression depends on, which required me to refactor rule model) and adding real joins to the nodes (as opposed to evaluating conditions on cross-joins, which will require drastic changes to the rete algorithm). Any help is obviously greatly appreciated. I expect that this feature with ForAllElement will require a healthy amount of back and forth, and pretty invasive changes, likely including the rule model and maybe even the rete level. Happy to discuss more if you are considering to tackle it.

1reaction
snikolayevcommented, Jul 10, 2019

I’m not trying to imply it’s not a useful feature, just trying to understand if it’s a bug/regression or a new feature. This sounds like a new feature. With that, I’m trying to also separate figuring out what this feature should look like, from trying to unblock you by giving you a way to achieve what you want with the current implementation. If Items is a property of an order, one idea (and you can translate that to a RuleBuilder) would be to use LINQ All extension:

When()
    .Match<Customer>(() => customer)
    .Match<Order>(() => order, x => x.Customer == customer, x => x.Items.All(i => i.InStock));

If Items is not a property of an order, but instead required a join, you can use a query:

When()
    .Match<Customer>(() => customer)
    .Match<Order>(() => order, x => x.Customer == customer)
    .Query(() =>itemsInStock, q => q
        .Match<Item>(x => x.Order == order)
        .Collect()
        .Where(x => x.All(i => i.InStock))
    );

Would that work?

Read more comments on GitHub >

github_iconTop Results From Across the Web

How to perform joins and data denormalization with nested ...
BigQuery supports ANSI SQL join types. JOIN operations are performed on two items based on join conditions and join type. Items in the...
Read more >
JOIN FEATURES - NESTED JOINS
My idea is to join the feature layer with table 1 (ONE TO MANY: feature layer globalid - table 1 parentglobalid), to create...
Read more >
ReMoDeL Explained
Mapping, merging and updating transformations are supported. ... levels of indentation in figure 6, where nested nodes are introduced further to the right....
Read more >
Mathematical Markup Language (MathML) Version 2.0
In both the presentation and content markup examples, mathematical expressions are recursively decomposed into nested, simpler MathML elements specifying.
Read more >
LS-DYNA® Keyword User's Manual (Version 971)
GETTING STARTED.............................................................................................................................GS.1.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found