Property Injection for NRules within ABP

允我心安 提交于 2021-01-07 02:37:54

问题


I am using ABP Boilerplate 6.0 and have integrated NRules with our platform.

  1. I am able to get the rule below to work, but the issue is that I cannot use the injected ‘_questionResponseRepository’ within the rule condition, because only after the rule criteria are met, does the resolution of dependencies take place.
  2. I would like to use ‘_questionResponseRepository’ to get from the database a list of keywords and use those words in the Rule match condition

The Calling code

public WasteManagementManager(                      
IRepository<WasteBirthCertificateBlock, long> wasteBirthCertificateBlockRepository,
              IRepository<WasteBirthCertificateChain, long> wasteBirthCertificateChainRepository,
              ISession nRuleSession,
              ISessionFactory nRuleSessionFactory,
              ILogger log

              )
        {
            _wasteBirthCertificateBlockRepository = wasteBirthCertificateBlockRepository;
            _wasteBirthCertificateChainRepository = wasteBirthCertificateChainRepository;
            _nRuleSession = nRuleSession;
            _nRuleSessionFactory = nRuleSessionFactory;
            _log = log;
        }

public void Trigger()
{==> 
When I am in debug, _questionResponseRepository is NOT NUll. I'm trying inject it as a fact but that is not property injection .. I'm just trying one way or the other to get it working


    _nRuleSession.Insert(_questionResponseRepository); 
     _nRuleSession.Fire();
 }

The Rule code

 namespace SagerSystems.AI.WasteManagements.NRules
    {
        [Name("Track Explosive Substances Rule")]
        public class TrackExplosiveSubstancesRule : Rule
        {
            private string[] _listOfExplosiveKeyWords = new string[] { "H3O", "N2" };
            public  IRepository<QuestionResponse, long> _questionResponseRepository { get; set; }
    
            public TrackExplosiveSubstancesRule()
            {
             **This does NOT work** (the value remains null) 
             Dependency()
            .Resolve(() => _questionResponseRepository);
            }
    
            public override void Define()
            {
             *This does work* but only after the rule fires) 
             Dependency()
              .Resolve(() => _questionResponseRepository);
           
             When()
              .Match(() => questionResponseDto, c => CheckKeyWord(c));
              
             Then()
              .Do(ctx => Console.WriteLine(“Test Condition Works”))
            }
        
          private bool CheckKeyWord(QuestionResponseDto questionResponseDto)
            {   
             ==> How do I user ‘questionResponseRepository’
                var isKeyWord= 
                _listOfExplosiveKeyWords.Any(c => questionResponseDto.QuestionText.Contains(c));
               return isKeyWord;
            }
        }
    }

回答1:


There are a few ways to use external information (in this case keywords from the DB) in the rule's match condition in NRules.

  1. Inject the corresponding repository/service into the rule. There are two ways to inject dependencies into rules. During instantiation of rule classes, or at rule's run time via Dependency.Resolve DSL. Since Dependency.Relsove, as you pointed out, can only be used on the right-hand side of the rule (actions), it's not suitable for this use case. But you can still inject the dependency into the rule during the rule's instantiation. What you need to do here is to register the rule type itself with the container, implement an IRuleActivator to resolve rules via that container, and set the RuleRepository.RuleActivator when loading the rules. If both the repository and the rule are registered with the same container, the rule will get injected with the dependency (you can use either property or constructor injection, depending on how you registered the types). Then you can just use the dependency in the expressions. I don't have all your code, so hypothetically, it would look something like below. Here I'm assuming there is a repository of a Keyword entity that can be used to fetch keywords from the DB. I'm also using constructor injection, but the same would work for property injection.
    public class RuleActivator : IRuleActivator
    {
        private readonly IIocResolver _iocResolver;

        public RuleActivator(IIocResolver iocResolver)
        {
            _iocResolver = iocResolver;
        }

        public IEnumerable<Rule> Activate(Type type)
        {
            yield return (Rule)_iocResolver.Resolve(type);
        }
    }

    public class RulesEngineModule : AbpModule
    {
        public override void Initialize()
        {
            //Find rule classes
            var scanner = new RuleTypeScanner();
            scanner.AssemblyOf<TrackExplosiveSubstancesRule>();
            var ruleTypes = scanner.GetRuleTypes();

            //Register rule classes with the container
            foreach (var ruleType in ruleTypes)
            {
                IocManager.Register(ruleType);
            }
            
            //Load rules into the repository; use a rule activator to resolve rules via the container
            var repository = new RuleRepository {Activator = new RuleActivator(IocManager)};
            repository.Load(x => x.From(s => s.Type(ruleTypes)));
            
            //Compile rules into the factory
            var factory = repository.Compile();

            //Register session factory instance
            IocManager.IocContainer.Register(
                Component.For<ISessionFactory>().Instance(factory));
            
            //Register session as a delegate that creates a new instance from a factory
            IocManager.IocContainer.Register(
                Component.For<ISession>().UsingFactoryMethod(k => k.Resolve<ISessionFactory>().CreateSession()).LifestyleTransient());
        }
    }

    [Name("Track Explosive Substances Rule")]
    public class TrackExplosiveSubstancesRule : Rule
    {
        private readonly IRepository<Keyword, long> _keywordRepository;
    
        public TrackExplosiveSubstancesRule(IRepository<Keyword, long> keywordRepository)
        {
            _keywordRepository = keywordRepository;
        }
    
        public override void Define()
        {
            QuestionResponseDto questionResponseDto = default;
            
            When()
                .Match(() => questionResponseDto, c => ContainsKeyword(c));

            Then()
                .Do(ctx => Console.WriteLine("Test Condition Works"));
        }
        
        private bool ContainsKeyword(QuestionResponseDto questionResponseDto)
        {
            var keywords = _keywordRepository.GetAll().ToList();
            var hasKeyWord = keywords.Any(keyword => questionResponseDto.QuestionText.Contains(keyword.Value));
            return hasKeyWord;
        }
    }

Then somewhere in your Trigger method or somewhere in a corresponding controller:

    var dto = new QuestionResponseDto(...);
    _session.Insert(dto);
    _session.Fire();
  1. Retrieve keywords from the repository outside of the rules engine, and insert them into the session as facts. This is actually preferred, because you would have more control over the interactions with the external data. Also, you can put keywords into a data structure with efficient lookup performance (e.g. a trie). In this case you don't need a rule activator or to register the rules with the container, like with the option #1, since all inputs come to the rule as facts, so there are actually no external dependencies. For example:
    public class KeywordSet
    {
        private readonly Keyword[] _keywords;

        public KeywordSet(IEnumerable<Keyword> keywords)
        {
            _keywords = keywords.ToArray();
        }
        
        public bool ContainsAny(string value)
        {
            return _keywords.Any(keyword => value.Contains(keyword.Value));
        }
    }
    
    [Name("Track Explosive Substances Rule")]
    public class TrackExplosiveSubstancesRule : Rule
    {
        public override void Define()
        {
            KeywordSet keywordSet = default;
            QuestionResponseDto questionResponseDto = default;
            
            When()
                .Match(() => keywordSet)
                .Match(() => questionResponseDto, c => keywordSet.ContainsAny(c.QuestionText));

            Then()
                .Do(ctx => Console.WriteLine("Test Condition Works"));
        }
    }

Then somewhere in your Trigger method or somewhere in a corresponding controller:

    var keywords = _keywordRepository.GetAll().ToList();
    var keywordSet = new KeywordSet(keywords);
    _session.Insert(keywordSet);
    
    var dto = new QuestionResponseDto(...);
    _session.Insert(dto);
    _session.Fire();
  1. You can also insert the respository itself as a fact, and then match it in the rule, but I would not recommend doing this, so I would stick to either the option #1 or #2.


来源:https://stackoverflow.com/questions/65469249/property-injection-for-nrules-within-abp

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!