ฉันสร้างเครื่องมือสร้างกฎที่ใช้แนวทางที่แตกต่างจากที่คุณระบุไว้ในคำถามของคุณ แต่ฉันคิดว่าคุณจะพบว่ามันมีความยืดหยุ่นมากกว่าแนวทางปัจจุบันของคุณ
แนวทางปัจจุบันของคุณดูเหมือนจะมุ่งเน้นไปที่เอนทิตีเดียว "ผู้ใช้" และกฎถาวรของคุณระบุ "คุณสมบัติชื่อ", "ผู้ประกอบการ" และ "ค่า" รูปแบบของฉันแทนเก็บรหัส C # สำหรับเพรดิเคต (Func <T, bool>) ในคอลัมน์ "นิพจน์" ในฐานข้อมูลของฉัน ในการออกแบบปัจจุบันโดยใช้การสร้างรหัสฉันกำลังค้นหา "กฎ" จากฐานข้อมูลของฉันและรวบรวมแอสเซมบลีด้วยประเภท "กฎ" แต่ละชนิดมีวิธี "ทดสอบ" นี่คือลายเซ็นสำหรับอินเตอร์เฟสที่ใช้แต่ละกฎ:
public interface IDataRule<TEntity>
{
/// <summary>
/// Evaluates the validity of a rule given an instance of an entity
/// </summary>
/// <param name="entity">Entity to evaluate</param>
/// <returns>result of the evaluation</returns>
bool Test(TEntity entity);
/// <summary>
/// The unique indentifier for a rule.
/// </summary>
int RuleId { get; set; }
/// <summary>
/// Common name of the rule, not unique
/// </summary>
string RuleName { get; set; }
/// <summary>
/// Indicates the message used to notify the user if the rule fails
/// </summary>
string ValidationMessage { get; set; }
/// <summary>
/// indicator of whether the rule is enabled or not
/// </summary>
bool IsEnabled { get; set; }
/// <summary>
/// Represents the order in which a rule should be executed relative to other rules
/// </summary>
int SortOrder { get; set; }
}
"Expression" ถูกคอมไพล์เป็นเนื้อความของวิธี "Test" เมื่อแอปพลิเคชันดำเนินการครั้งแรก ในขณะที่คุณสามารถดูคอลัมน์อื่น ๆ ในตารางก็ปรากฏขึ้นเป็นคุณสมบัติชั้นหนึ่งในกฎเพื่อให้นักพัฒนามีความยืดหยุ่นในการสร้างประสบการณ์สำหรับวิธีที่ผู้ใช้ได้รับแจ้งจากความล้มเหลวหรือความสำเร็จ
การสร้างแอสเซมบลีในหน่วยความจำเป็นสิ่งที่เกิดขึ้น 1 ครั้งในระหว่างแอปพลิเคชันของคุณและคุณจะได้รับประสิทธิภาพที่เพิ่มขึ้นโดยไม่ต้องใช้การสะท้อนเมื่อประเมินกฎของคุณ นิพจน์ของคุณถูกตรวจสอบที่รันไทม์เนื่องจากแอสเซมบลีจะไม่สร้างอย่างถูกต้องหากสะกดชื่อคุณสมบัติ ฯลฯ
กลไกการสร้างแอสเซมบลีในหน่วยความจำมีดังนี้:
- โหลดกฎของคุณจากฐานข้อมูล
- วนซ้ำกฎและสำหรับแต่ละตัวโดยใช้ StringBuilder และการต่อสายอักขระบางตัวเขียนข้อความที่แสดงคลาสที่สืบทอดมาจาก IDataRule
- รวบรวมโดยใช้ CodeDOM - ข้อมูลเพิ่มเติม
นี่เป็นจริงค่อนข้างง่ายเพราะส่วนใหญ่รหัสนี้เป็นการใช้งานทรัพย์สินและการเริ่มต้นค่าในตัวสร้าง นอกจากนั้นโค้ดอื่น ๆ เท่านั้นคือ Expression
หมายเหตุ: มีข้อ จำกัด ว่านิพจน์ของคุณต้องเป็น. NET 2.0 (ไม่มี lambdas หรือคุณสมบัติ C # 3.0 อื่น ๆ ) เนื่องจากข้อ จำกัด ใน CodeDOM
นี่คือตัวอย่างรหัสสำหรับสิ่งนั้น
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;");
// constructor
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}");
// properties
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; } }");
/// ... more properties -- omitted
sb.AppendLine(string.Format("\t\tpublic bool Test({0} entity) ", typeName));
sb.AppendLine("\t\t{");
// #############################################################
// NOTE: This is where the expression from the DB Column becomes
// the body of the Test Method, such as: return "entity.Prop1 < 5"
// #############################################################
sb.AppendLine(string.Format("\t\t\treturn {0};", expressionText.TrimEnd()));
sb.AppendLine("\t\t}"); // close method
sb.AppendLine("\t}"); // close Class
นอกเหนือจากนี้ฉันได้สร้างคลาสฉันเรียกว่า "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; }
// only allow the assembly to load once per entityType per execution session
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);
// Note: This dependencies list are included as assembly reference and they should list out all dependencies
// That you may reference in your Rules or that your entity depends on.
// some assembly names were changed... clearly.
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);
}
// reference .NET basics for C# 2.0 and C#3.0
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;
}
}
}
}
หากมีคำถามหรือความคิดเห็นหรือคำขออื่น ๆสำหรับตัวอย่างโค้ดเพิ่มเติมแจ้งให้เราทราบ