C # Dynamically apply evaluation rule on collection

I have a fixed length data file that I need to store in a database. I am using an XML file to define the lengths for the fields and use a list of FieldItem classes to store the data.

   class FieldItem
   {
        public string ObjectName {get; set;}
        public string ObjectProperty {get; set;}
        public string ObjectValue {get; set;}

        public FieldItem()
       {
       }
   }

      

So the FieldItem will look like

var fieldItem = new FieldItem
                {
                   ObjectName = "Company",
                   ObjectProperty = "Name",
                   ObjectValue = "ABC Corp."
                }

      

After getting the list of FieldItems, I'll make a fix to create the company and other domain objects to store in the database.

But before they are saved to the database, I have some business rules that need to be applied to validate a row of data. My data line will look like this:

var fieldItemList = new List<FieldItem>(){
                new FieldItem {
                    ObjectName="Company",
                    ObjectProperty = "Name",
                    ObjectValue = "ABC"
                }

                new FieldItem{
                    ObjectName = "Product",
                    ObjectProperty = "ProductCode",
                    ObjectValue ="XYZ0123"

                    new FieldItem{
                    ObjectName = "Product",
                    ObjectProperty = "ProductName",
                    ObjectValue ="Christmas Tree"       
                }

                    // other FieldItem objects...

            }

      

For example, my rule is to check if company == "ABC" and ProductCode == "XYZ0123". These rules are created by users and saved as a string in the database. I am currently using Microsoft System.Linq.Dynamic to evaluate a rule like

string validationRule = " (ObjectName == \"Company\" And ObjectProperty=\"CompanyName\" And ObjectValue = \"ABC Corp.\") And (ObjectName == \"Product\" And ObjectProperty=\"ProductCode\" And ObjectValue = \"XYZ0123\") ";

  var query = fieldItemList.AsQuerable().Where(validationRule);

      

then check if it has one line to tell if that data line passed the rule or not. Obviously, this is too wordy. Do you have a better deal? What if I only like the rule expression: "Company.CompanyName =" ABC Corp. "and Product.ProductCode = 'XYZ001'"?

+1


source to share


5 answers


RE: "What if I only like the rule expression:" Company.CompanyName = "ABC Corp" and Product.ProductCode = 'XYZ001' "?


Compose this handy query: "Company.CompanyName =" ABC Corp " AND Product.ProductCode = 'XYZ001'" to something that is friendly to your data structure (FieldItem):


"ObjectName =" Company "AND ObjectProperty =" CompanyName "AND ObjectValue =" ABC Corp " OR ObjectName = 'Product' AND ObjectProperty = 'ProductCode' AND ObjectValue = 'XYZ001'"

How do you know if a user's rules have passed the rules or not? Count the number of conditions if it matches the result count of fieldItemList.AsQueryable (). Where (itemFieldFriendlyQuery) then the data string is valid according to the user's rules.




some rudimentary cartographer (use a regex or roll your own parser to actually make the following code):

public Form3()
{
    InitializeComponent();

    string userFriendlyQuery = "Company.CompanyName = 'ABC Corp' AND Product.ProductCode = 'XYZ001'";

    string[] queryConditions = userFriendlyQuery.Split(new string[]{" AND "},StringSplitOptions.None);
    int conditionsCount = queryConditions.Length;

    string itemFieldFriendlyQuery = string.Join(" OR ", 
        queryConditions.Select(condition =>
            {
                var conditionA = condition.Split(new string[] { " = " }, StringSplitOptions.None);

                var left = conditionA[0];
                var leftA = left.Split('.');

                string objectName = leftA[0];
                string objectProperty = leftA[1];

                var right = conditionA[1];

                return string.Format("ObjectName = '{0}' AND ObjectProperty = '{1}' AND ObjectValue = {2}",
                    objectName, objectProperty, right);
            }
        ).ToArray());



    MessageBox.Show(itemFieldFriendlyQuery);

    // outputs: "ObjectName = 'Company' AND ObjectProperty = 'CompanyName' AND ObjectValue = 'ABC Corp' OR ObjectName = 'Product' AND ObjectProperty = 'ProductCode' AND ObjectValue = 'XYZ001'"


    bool isValid = fieldItemList.AsQueryable().Where(itemFieldFriendlyQuery).Count() == conditionsCount;


    MessageBox.Show(isValid.ToString());
}   

      

+1


source


Consider using the FileHelpers library to parse your file directly into regular .NET objects. I believe this, combined with your Dynamic Linq approach, will give you the syntax you are looking for.



+1


source


You could store them in a database as a C # code expression and then use the CodeDom classes to parse and compile to a type that provides a method that will do the comparison.

This will create a lot of temporary assemblies that PITA will manage, IMO.

Rather, I am still using C # notation for expression syntax, but instead of compiling it into code, dynamically create a lambda expression (via the Expression class) and then compile into a delegate that takes all parameters (you will need a display engine here) and then just call it using the types you created.

0


source


FieldItem

Failed to create a property for your class where you can inject the Predicate delegate method, for example:

class FieldItem
{
    public string ObjectName {get; set;}
    public string ObjectProperty {get; set;}
    public string ObjectValue {get; set;}
    public Predicate<FieldItem> EvalMethod { private get; set; }

    public FieldItem()
    {
    }

    public bool Evaluate()
    {
        return EvalMethod(this);
    }
}

      

FieldItem

You can use the following to create your object :

new FieldItem { ObjectName = "SomeObject",
                ObjectProperty = "SomeProperty",
                ObjectValue = "SomeValue",
                EvalMethod = GetEvalMethodFor("SomeObject")
               }

      

Of course you will have to have some kind of parser ( GetEvalMethodFor

method above) to translate the validationRule into a boolean evaluation method.

On your collection, you can call a loop Evaluate()

inside, say foreach

.

UPDATE

If you need to evaluate each item in the list, you can do this:

bool areAllItemsValid = true;

foreach (var f in fieldItemList)
{
    areAllItemsValid = areAllItemsValid && f.Evaluate();

    if (areAllItemsValid)
    {
        continue;
    }
    else
    {
        return false;
    }
}

return true;

      

You can choose to inherit List<FieldItem>

and make the method EvaluateAll()

contain the above, or declare it as a method in a separate object altogether.

0


source


As an aside: why class instead of struct?

Value types for data, reference types for behavior.

0


source







All Articles