[解決済み】ルールエンジンの実装方法は?
質問
私は以下を格納するdbテーブルを持っています。
RuleID objectProperty ComparisonOperator TargetValue
1 age 'greater_than' 15
2 username 'equal' 'some_name'
3 tags 'hasAtLeastOne' 'some_tag some_tag2'
さて、これらのルールのコレクションがあるとします。
List<Rule> rules = db.GetRules();
今度は、ユーザーのインスタンスも持っています。
User user = db.GetUser(....);
これらのルールをどのようにループさせ、ロジックを適用し、比較などを実行するのでしょうか?
if(user.age > 15)
if(user.username == "some_name")
age' や 'user_name' といったオブジェクトのプロパティは、比較演算子 'great_than' や 'equal' と共にテーブルに格納されているので、どうすればこれを実行できるのでしょうか?
C#は静的型付け言語なので、どのように進めればよいかわからない。
どうすればいい?
このスニペット は、ルールを高速な実行可能コードにコンパイルします。 (を使用)。 表現ツリー ) であり、複雑なswitch文は必要ありません。
(編集: ジェネリックメソッドによる完全な動作例 )
public Func<User, bool> CompileRule(Rule r)
{
var paramUser = Expression.Parameter(typeof(User));
Expression expr = BuildExpr(r, paramUser);
// build a lambda function User->bool and compile it
return Expression.Lambda<Func<User, bool>>(expr, paramUser).Compile();
}
すると、書くことができます。
List<Rule> rules = new List<Rule> {
new Rule ("Age", "GreaterThan", "21"),
new Rule ( "Name", "Equal", "John"),
new Rule ( "Tags", "Contains", "C#" )
};
// compile the rules once
var compiledRules = rules.Select(r => CompileRule(r)).ToList();
public bool MatchesAllRules(User user)
{
return compiledRules.All(rule => rule(user));
}
以下はBuildExprの実装です。
Expression BuildExpr(Rule r, ParameterExpression param)
{
var left = MemberExpression.Property(param, r.MemberName);
var tProp = typeof(User).GetProperty(r.MemberName).PropertyType;
ExpressionType tBinary;
// is the operator a known .NET operator?
if (ExpressionType.TryParse(r.Operator, out tBinary)) {
var right = Expression.Constant(Convert.ChangeType(r.TargetValue, tProp));
// use a binary operation, e.g. 'Equal' -> 'u.Age == 21'
return Expression.MakeBinary(tBinary, left, right);
} else {
var method = tProp.GetMethod(r.Operator);
var tParam = method.GetParameters()[0].ParameterType;
var right = Expression.Constant(Convert.ChangeType(r.TargetValue, tParam));
// use a method call, e.g. 'Contains' -> 'u.Tags.Contains(some_tag)'
return Expression.Call(left, method, right);
}
}
greater_than」等の代わりに「GreaterThan」を使用したことに注意してください。- これは、'GreaterThan'が演算子の.NET名であり、余分なマッピングが必要ないためです。
カスタム名が必要な場合は、非常にシンプルな辞書を作成して、ルールをコンパイルする前にすべての演算子を翻訳するだけでよいのです。
var nameMap = new Dictionary<string, string> {
{ "greater_than", "GreaterThan" },
{ "hasAtLeastOne", "Contains" }
};
このコードでは、簡単のためにUser型を使用しています。User を一般的な型 T に置き換えて 汎用ルールコンパイラ は、どのような種類のオブジェクトにも対応します。また、不明な演算子名などのエラーも処理する必要があります。
なお、オンザフライでのコード生成は、Expression trees APIが導入される以前からReflection.Emitを使用して可能でした。LambdaExpression.Compile()というメソッドは、Reflection.Emitを隠れて使っています(これを見るには ILSpy ).
関連
-
[解決済み】「The breakpoint will not currently be hit」を改善するには?このドキュメントにはシンボルが読み込まれていません。" という警告はどうすれば改善されますか?
-
[解決済み】指定されたキャストが有効でない?
-
[解決済み】C#におけるtypedefの等価性
-
[解決済み】データが存在しないのに読み込もうとする試みが無効である
-
[解決済み] enumを列挙するには
-
[解決済み] intをenumにキャストするにはどうすればよいですか?
-
[解決済み] C#で文字列のエンコーディングを手動で指定せずに、一貫性のあるバイト表現を得るには?
-
[解決済み] 乱数(int)を生成する方法を教えてください。
-
[解決済み] DateTime型の誕生日から年齢を計算するにはどうしたらいいですか?
-
[解決済み] Microsoft Officeをインストールせずに、C#でExcel(.XLSおよび.XLSX)ファイルを作成するにはどうすればよいですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] エンティティタイプ ApplicationUser は、現在のコンテキストのモデルの一部ではありません。
-
[解決済み】GDI+、JPEG画像をMemoryStreamに変換する際にジェネリックエラーが発生しました。
-
[解決済み】Ajax処理で「無効なJSONプリミティブ」と表示される件
-
[解決済み】文字列が有効な DateTime " format dd/MM/yyyy " として認識されなかった。
-
[解決済み】5.7.57 SMTP - MAIL FROMエラー時に匿名メールを送信するためにクライアントが認証されない
-
[解決済み】2年前のMSDateを把握する【クローズド
-
[解決済み】aspNetCore 2.2.0 - AspNetCoreModuleV2 エラー
-
[解決済み】インデックスが範囲外でした。コレクションパラメータname:indexのサイズより小さく、非負でなければなりません。
-
[解決済み】WebResource.axdとは何ですか?
-
[解決済み】スレッド終了またはアプリケーションの要求により、I/O操作が中断されました。