Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lambda variable identifier treated as an unknown identifier #606

Open
BardezAnAvatar opened this issue May 15, 2024 · 9 comments
Open

Lambda variable identifier treated as an unknown identifier #606

BardezAnAvatar opened this issue May 15, 2024 · 9 comments

Comments

@BardezAnAvatar
Copy link

I've an interesting scenario that is driving me nuts:

I have a model that I am registering to RulesEngine via parameter (line) that consists of:
IReadOnlyList<string> Modifiers { get; }

Any expression that accesses this (in a .NET 6 project/solution) is encountering the following:

Exception while parsing expression `line.Modifiers.Any(l => new [] {"25"}.Contains(l))` - Unknown identifier 'l'

Oddly, in a separate solution that generates this expression, I am able to load the above without issue in my integration test project. Taking the same expression over to my other solution, however, yields the above exception when running RulesEngine. Both solutions use the exact same NuGet version of RulesEngine (5.0.3), using the same Workflow serialization/deserialization.

With tinkering, it seems that the specific reference of l that is a problem is on the left-hand side of the lambda, not the .Contains(l) statement.

@BardezAnAvatar
Copy link
Author

Root cause has been identified:

I have a (very) complex object called ClaimDto, which has its own implementations. One child object is the ClaimLine, as referenced above:

public interface IClaimLine
{
  [redacted]

  IReadOnlyList<string> Modifiers { get; }

  [redacted]
}

public class ClaimLine : IClaimLine
{
    [redacted]

    //The original property
    public ClaimLineModifiers Modifiers => _modifiers;

    //The interface's property
    IReadOnlyList<string> IClaimLine.Modifiers => _modifiers.Items().Select(c => c.Modifier).ToList();

    [redacted]
}

public class ClaimLineModifiers : GenericImmutableArray<ClaimLineModifier>
{
    [redacted]
}

public class ClaimLineModifier
{
    [redacted]

    public string Modifier {get; set;}

    [redacted]
}

It turns out that the RuleParameter does not take the type of the reference being passed around (IClaim/IClaimLine) into account, but instead uses the object's type when evaluating the rule.

I uncovered this when screwing around with the expression:

Exception while parsing expression `line.Modifiers.ToList().Any(l => new [] {"25"}.Contains(l))` - Methods on type 'GenericImmutableArray`1' are not accessible

what the [expletive]? why is line.Modifiers pinging as GenericImmutableArray??

I also tried by renaming my interface's Modifiers to Modifiers2 and referencing the property there; no such property when evaluating against Modifiers2 if it remained an explicit interface implementation.

I suppose my immediate solution would be to cast my line to IClaimLine inside of my expression.

@BardezAnAvatar
Copy link
Author

BardezAnAvatar commented May 15, 2024

Oddly, in a separate solution that generates this expression, I am able to load the above without issue in my integration test project. Taking the same expression over to my other solution, however, yields the above exception when running RulesEngine. Both solutions use the exact same NuGet version of RulesEngine (5.0.3), using the same Workflow serialization/deserialization

The reason that Solution A (source of the expression, with tests) worked was because Solution A only had references to the interface and used mock objects to test expressions in RulesEngine locally. Solution B consuming the expressions have the complicated object model, which has conflicting types between the interface's .Modifiers and the class' .Modifiers

@BardezAnAvatar
Copy link
Author

BardezAnAvatar commented May 16, 2024

I've got a resolution for my solution: I need to do the following for single-instance objects:

As(line, "<<<FullyQualifiedNamespace>>>.IClaimLine")

and for lists:

history.Cast("<<<FullyQualifiedNamespace>>>.IClaim")

I would raise that a suggested feature request would be to specify the type to evaluate a given object as for an additional RuleParameter.Create. This would allow the expected behavior of passing in an IClaim to evaluate as an IClaim rather than an instance with a complicated and messy legacy model implementation.

It looks like there is such a constructor on RuleParameter, but it is internal:

    internal RuleParameter(string name, Type type, object value = null)
    {
        Value = Utils.GetTypedObject(value);
        Init(name, type);
    }

@asulwer
Copy link

asulwer commented Jun 21, 2024

i made some changes to my fork which may affect this question

@maqduni
Copy link

maqduni commented Oct 10, 2024

@BardezAnAvatar, I'm having the same issue. I didn't quite get what you eventually did to resolve it. Thank you!

@BardezAnAvatar
Copy link
Author

I had to use the casting syntax for DynamicLINQ, to ensure that the objects are evaluated explicitly as interfaces and not the objects that tbey are. So, by casting to an interface, they use the interface properties, not the class properties.

@maqduni
Copy link

maqduni commented Oct 11, 2024

Where does this casting happen? Do you do it directly in this LINQ expression line.Modifiers.Any(l => new [] {"25"}.Contains(l))? If so, could you provide an example of how it's done?

@BardezAnAvatar
Copy link
Author

You use the As({var}, {type}) syntax wherever you reference the variable. You could probably create a scoped param at top level that does this once for you instead, creating a variable CastedVar that gets used instead of line.

@asulwer
Copy link

asulwer commented Oct 14, 2024

this fork is no longer maintained, please move your issue to a fork that is maintained

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants