Groups keyboard shortcuts have been updated
Dismiss
See shortcuts

Second rule using interface rather than class causes exception.

42 views
Skip to first unread message

Ross McPhee

unread,
Sep 24, 2020, 12:12:26 PM9/24/20
to NRules Users
I have created a simple sample showing this error on .Net fiddle: https://dotnetfiddle.net/nrJ8qm

If I address the facts (Shift) in a rule as interfaces (IShift) it works for one rule. As soon as I duplicate the rule the compilation throws an error and the inner exception claims:

System.NotImplementedException: (shift.EndTime - shift.StartTime).TotalHours  

If I toggle the comment between line 66&67 it will start working again.

Source code for convenience:

using NRules;
using NRules.Fluent;
using NRules.Fluent.Dsl;
using System;
using System.Reflection;

namespace NRuleSample
{
class Program
    {
        static void Main(string[] args)
        {
            try
            {
                var repository = new RuleRepository();
                repository.Load(x => x.From(Assembly.GetExecutingAssembly()));

                var session = repository.Compile().CreateSession();

                Shift shift = new Shift { StartTime = new DateTime(2020, 9, 24, 6, 0, 0), EndTime = new DateTime(2020, 9, 24, 17, 0, 0) };

                session.Insert(shift);
                session.Fire();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }

            Console.WriteLine("Hello World!");
        }
    }

    public interface IShift
    {
        public DateTime StartTime { get; set; }
        public DateTime EndTime { get; set; }
    }

    public class Shift : IShift
    {
        public DateTime StartTime { get; set; }
        public DateTime EndTime { get; set; }
    }

    public class LongShiftRule1 : Rule
    {
        public override void Define()
        {
            IShift shift = null;

            When()
                .Match<IShift>(() => shift, shf => (shf.EndTime - shf.StartTime).TotalHours > 10);
            
            Then()
                .Do(ctx =>
                Console.WriteLine($"[LongShiftRule1] IShift is a long shift  {shift}"));
        }
    }

    public class LongShiftRule2 : Rule
    {
        public override void Define()
        {
            IShift shift = null; // This line makes it throw NotImplemented
            //Shift shift = null;  // This line makes it work

            When()
                .Match<IShift>(() => shift, shf => (shf.EndTime - shf.StartTime).TotalHours > 10);

            Then()
                .Do(ctx =>
                Console.WriteLine($"[LongShiftRule2] Shift is a long shift  {shift}"));
        }
    }
}

Advice, insights and solution all gratefully received.

Thank you.

Ross McPhee

Sergiy Nikolayev

unread,
Sep 27, 2020, 5:50:57 PM9/27/20
to NRules Users
Hi Ross. Thank you for reporting this. It's a bug in expression comparison in NRules. I logged a bug on GitHub and fixed it: https://github.com/NRules/NRules/issues/239
Also, this has nothing to do with an interface vs a class. This happens when expressions are the same (so if both use an iface or both use a class, the error will occur), because the engine is comparing the expressions, and hits this unsupported case.

As a workaround, you can move the logic of calculating the shift's total hours into the domain object itself.

    public class Shift
    {
        public DateTime StartTime { get; set; }
        public DateTime EndTime { get; set; }

        public double TotalHours()
        {
            return (EndTime - StartTime).TotalHours;
        }
    }

    public class LongShiftRule1 : Rule
    {
        public override void Define()
        {
            Shift shift = null;

            When()
                .Match(() => shift, shf => shf.TotalHours() > 10);

            Then()
                .Do(ctx =>  Console.WriteLine($"[{ctx.Rule.Name}] shift is a long shift {shift}"));
        }
    }

    public class LongShiftRule2 : Rule
    {
        public override void Define()
        {
            Shift shift = null;

            When()
                .Match(() => shift, shf => shf.TotalHours() > 10);

            Then()
                .Do(ctx =>  Console.WriteLine($"[{ctx.Rule.Name}] shift is a long shift {shift}"));
        }
    }

Ross McPhee

unread,
Sep 28, 2020, 4:58:59 AM9/28/20
to NRules Users
Thanks for such a quick response.
Reply all
Reply to author
Forward
0 new messages