Sorunuzda belirttiğinizden farklı bir yaklaşım gerektiren bir kural motoru oluşturdum, ancak mevcut yaklaşımınızdan çok daha esnek olduğunu düşünüyorum.
Şu anki yaklaşımınız tek bir varlığa odaklanmış gibi görünüyor, "Kullanıcı" ve kalıcı kurallarınız "özeladı", "operatörü" ve "değeri" tanımlıyor. Benim desen yerine bir yüklem için C # kodunu (Func <T, bool>) veritabanımdaki "İfade" sütununda saklar. Geçerli tasarımda, kod oluşturma kullanarak veritabanımdan "kurallar" sorgu ve her biri "Test" yöntemi ile "Kural" türleri ile bir derleme derliyorum. Her kuralı uygulayan arabirimin imzası:
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; }
}
"İfade", uygulama ilk çalıştırıldığında "Test" yönteminin gövdesi olarak derlenir. Gördüğünüz gibi, tablodaki diğer sütunlar da kural üzerinde birinci sınıf özellikler olarak ortaya çıkar, böylece bir geliştirici kullanıcının başarısızlık veya başarı hakkında nasıl bilgilendirileceği konusunda bir deneyim oluşturma esnekliğine sahiptir.
Bir bellek içi derleme oluşturmak, uygulamanız sırasında 1 kez gerçekleşir ve kurallarınızı değerlendirirken yansıma kullanmak zorunda kalmadan performans kazanırsınız. Özellik adı yanlış yazılmışsa derleme doğru oluşturulmayacağından, ifadeleriniz çalışma zamanında kontrol edilir.
Bir bellek içi montaj oluşturma mekanizması aşağıdaki gibidir:
- Kurallarınızı DB'den yükleyin
- bir StringBuilder ve bazı dize birleştirme kullanarak kurallar ve her biri için yineleme, IDataRule'den devralan bir sınıfı temsil eden Metni yazın
- CodeDOM kullanarak derleme - daha fazla bilgi
Bu aslında oldukça basittir, çünkü çoğunluk için bu kod, yapıcıdaki özellik uygulamaları ve değer başlatmadır. Bunun yanı sıra, diğer tek kod İfadedir.
Not: CodeDOM bir sınırlama nedeniyle ifadenizin .NET 2.0 (lambdas veya diğer C # 3.0 özellikleri) olması gereken bir sınırlama vardır.
İşte bunun için bazı örnek kod.
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
Bunun ötesinde ICollection> uygulayan "DataRuleCollection" adlı bir sınıf yaptım. Bu, "TestAll" yeteneği ve belirli bir kuralı ada göre yürütmek için bir dizin oluşturucu oluşturmamı sağladı. İşte bu iki yöntemin uygulamaları.
/// <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);
}
DAHA FAZLA KOD: Kod Üretimi ile ilgili kod için bir istek vardı. Ben aşağıda dahil ettim 'RulesAssemblyGenerator' adlı bir sınıftaki işlevselliği kapsüllü.
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;
}
}
}
}
Herhangi varsa diğer ileri kod örnekleri için sorular veya yorumlar veya istekler bana bildirin.