我构建了一款规则引擎,与您在问题中提出的方法不同,但我认为您会发现它比您当前的方法更加灵活。您当前的方法似乎专注于单个实体“用户”,并且您的持久化规则识别“属性名称”,“运算符”和“值”。相反,我的模式将用于谓词(Func<T, bool>)的C#代码存储在数据库中的“Expression”列中。在当前的设计中,使用代码生成,我正在从数据库查询“规则”,并编译一个带有“Rule”类型的程序集,每个类型都有一个“Test”方法。这是实现每个规则的接口的签名:
public interface IDataRule<TEntity>
{
bool Test(TEntity entity);
int RuleId { get; set; }
string RuleName { get; set; }
string ValidationMessage { get; set; }
bool IsEnabled { get; set; }
int SortOrder { get; set; }
}
"Expression" 在应用程序首次执行时被编译为 "Test" 方法的主体。正如您所看到的,表中的其他列也作为规则的一级属性呈现,以便开发人员可以灵活地创建用户在失败或成功时接收通知的体验。
在应用程序期间生成内存中的程序集是一次性事件,并且通过不必使用反射来评估规则,可以获得性能提升。运行时将检查表达式,因为如果属性名称拼写错误等,则程序集将无法正确生成。
创建内存中程序集的机制如下:
- 从数据库加载规则
- 遍历规则,并使用 StringBuilder 和一些字符串连接来编写表示继承自 IDataRule 类的类的文本
- 使用 CodeDOM 进行编译 -
更多信息
这其实很简单,因为对于大多数人来说,这段代码只是属性实现和构造函数中的值初始化。除此之外,唯一的其他代码就是表达式。
注意:由于CodeDOM的限制,您的表达式必须是.NET 2.0(没有lambda或其他C# 3.0功能)。
这是一些示例代码。
sb.AppendLine(string.Format("\tpublic class {0} : SomeCompany.ComponentModel.IDataRule<{1}>", className, typeName));
sb.AppendLine("\t{");
sb.AppendLine("\t\tprivate int _ruleId = -1;");
sb.AppendLine("\t\tprivate string _ruleName = \"\";");
sb.AppendLine("\t\tprivate string _ruleType = \"\";");
sb.AppendLine("\t\tprivate string _validationMessage = \"\";");
sb.AppendLine("\t\tprivate bool _isenabled= false;");
sb.AppendLine(string.Format("\t\tpublic {0}()", className));
sb.AppendLine("\t\t{");
sb.AppendLine(string.Format("\t\t\tRuleId = {0};", ruleId));
sb.AppendLine(string.Format("\t\t\tRuleName = \"{0}\";", ruleName.TrimEnd()));
sb.AppendLine(string.Format("\t\t\tRuleType = \"{0}\";", ruleType.TrimEnd()));
sb.AppendLine(string.Format("\t\t\tValidationMessage = \"{0}\";", validationMessage.TrimEnd()));
sb.AppendLine(string.Format("\t\t\tSortOrder = {0};", sortOrder));
sb.AppendLine("\t\t}");
sb.AppendLine("\t\tpublic int RuleId { get { return _ruleId; } set { _ruleId = value; } }");
sb.AppendLine("\t\tpublic string RuleName { get { return _ruleName; } set { _ruleName = value; } }");
sb.AppendLine("\t\tpublic string RuleType { get { return _ruleType; } set { _ruleType = value; } }");
sb.AppendLine(string.Format("\t\tpublic bool Test({0} entity) ", typeName));
sb.AppendLine("\t\t{");
sb.AppendLine(string.Format("\t\t\treturn {0};", expressionText.TrimEnd()));
sb.AppendLine("\t\t}");
sb.AppendLine("\t}");
在此之外,我创建了一个名为“DataRuleCollection”的类,实现了ICollection>。这使我能够创建一个“TestAll”功能和一个按名称执行特定规则的索引器。以下是这两种方法的实现。
/// <summary>
/// Indexer which enables accessing rules in the collection by name
/// </summary>
/// <param name="ruleName">a rule name</param>
/// <returns>an instance of a data rule or null if the rule was not found.</returns>
public IDataRule<TEntity, bool> this[string ruleName]
{
get { return Contains(ruleName) ? list[ruleName] : null; }
}
// in this case the implementation of the Rules Collection is:
// DataRulesCollection<IDataRule<User>> and that generic flows through to the rule.
// there are also some supporting concepts here not otherwise outlined, such as a "FailedRules" IList
public bool TestAllRules(User target)
{
rules.FailedRules.Clear();
var result = true;
foreach (var rule in rules.Where(x => x.IsEnabled))
{
result = rule.Test(target);
if (!result)
{
rules.FailedRules.Add(rule);
}
}
return (rules.FailedRules.Count == 0);
}
更多代码:有人要求与代码生成相关的代码。我将功能封装在一个名为“RulesAssemblyGenerator”的类中,如下所示。
namespace Xxx.Services.Utils
{
public static class RulesAssemblyGenerator
{
static List<string> EntityTypesLoaded = new List<string>();
public static void Execute(string typeName, string scriptCode)
{
if (EntityTypesLoaded.Contains(typeName)) { return; }
Compile(new CSharpCodeProvider(), scriptCode);
EntityTypesLoaded.Add(typeName);
}
private static void Compile(CodeDom.CodeDomProvider provider, string source)
{
var param = new CodeDom.CompilerParameters()
{
GenerateExecutable = false,
IncludeDebugInformation = false,
GenerateInMemory = true
};
var path = System.Reflection.Assembly.GetExecutingAssembly().Location;
var root_Dir = System.IO.Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "Bin");
param.ReferencedAssemblies.Add(path);
var dependencies = new string[] { "yyyyyy.dll", "xxxxxx.dll", "NHibernate.dll", "ABC.Helper.Rules.dll" };
foreach (var dependency in dependencies)
{
var assemblypath = System.IO.Path.Combine(root_Dir, dependency);
param.ReferencedAssemblies.Add(assemblypath);
}
param.ReferencedAssemblies.Add(@"C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.dll");
param.ReferencedAssemblies.Add(@"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5\System.Core.dll");
var compileResults = provider.CompileAssemblyFromSource(param, source);
var output = compileResults.Output;
if (compileResults.Errors.Count != 0)
{
CodeDom.CompilerErrorCollection es = compileResults.Errors;
var edList = new List<DataRuleLoadExceptionDetails>();
foreach (CodeDom.CompilerError s in es)
edList.Add(new DataRuleLoadExceptionDetails() { Message = s.ErrorText, LineNumber = s.Line });
var rde = new RuleDefinitionException(source, edList.ToArray());
throw rde;
}
}
}
}
如果有任何其他问题、评论或请求进一步的代码示例,请告诉我。