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