问题
I am using ABP Boilerplate 6.0 and have integrated NRules with our platform.
- 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.
- 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.
- 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();
- 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();
- 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