// constructor that receives EquationDefinition, VariableDefinition and DLL file name public Evaluator( EquationDefinition[] edEquations, VariableDefinition[] vdVariables, string strDLLFileNameParam ) { ConstructEvaluator( edEquations, vdVariables, strDLLFileNameParam ); }
// method that dynamically compiles and instantiates the _Evaluator class public void ConstructEvaluator( EquationDefinition[] edEquations, VariableDefinition[] vdVariables, string strDLLFileNameParam ) { // instantiate a C# Code Compiler and a Compiler Parameter to use with it //ICodeCompiler CSharpCodeCompiler = (new CSharpCodeProvider().CreateCompiler()); CodeDomProvider CSharpCodeCompiler = CodeDomProvider.CreateProvider("CSharp"); CompilerParameters cpCompilerParameters = new CompilerParameters(); // the Parameter object will tell the compiler what DLLs to use cpCompilerParameters.ReferencedAssemblies.Add( "system.dll" ); if( bDebug ) AddTracingAssemblies( cpCompilerParameters ); // add Metric Manager reference String strPath = System.Configuration.ConfigurationManager.AppSettings[ "AppPath" ].ToString(); cpCompilerParameters.ReferencedAssemblies.Add( strPath + strMetricManagerPath ); if( strDLLFileNameParam.Equals( "" ) ) // this tells to compile the code (in memory only) and to not generate .EXE cpCompilerParameters.GenerateExecutable = false; else { // unload the app domain with the DLL FreeDLL(); // release the NEW DLL if it is loaded (who knows? it could be) // because we're going to overwrite it FreeLibrary( GetModuleHandle( strDLLFileNameParam ) ); // deletes the file because we're going to create // a new one with this name File.Delete( strDLLFileNameParam ); // this tells to generate executable, that is, a DLL cpCompilerParameters.GenerateExecutable = true; cpCompilerParameters.OutputAssembly = strDLLFileNameParam; strLoadedDLLName = strDLLFileNameParam; } cpCompilerParameters.GenerateInMemory = true; // create a string builder that will // hold the source code in C# StringBuilder strbCSharpCode = new StringBuilder(); if( bDebug ) { // these are needed to save Xml from inside the equation strbCSharpCode.Append( @" using System.Data; using System.Data.SqlClient; using System.Data.OleDb; using System.Xml; using System.IO; " ); } // write the C# header in the string builder. // inside of the C# code there will be a class _Evaluator // the _Evaluator class will have functions that will return the result // of the expressions // the use of adEvaluatorDomain requires compiled assembly to be serializable // add attributes to hold MetricManager instance and some parameters // and add methods GreaterOf and LesserOf strbCSharpCode.Append( @" using System; using MetricManagerClasses; namespace ExpressionEvaluator { [Serializable] public class _Evaluator { public MetricManager mmMetricManager; public DateTime dtBegin; public DateTime dtEnd; public Decimal decLocationId; public Decimal decProvLocId; public Decimal decProcessId; public Decimal decPayHeaderId; public String strScheduleType; " ); // add methods for > and < if( CheckExpression( edEquations, "GreaterOf(" ) ) strbCSharpCode.Append( @" public Decimal GreaterOf( Decimal decValue1, Decimal decValue2 ) { return decValue1 > decValue2 ? decValue1 : decValue2; } " ); if( CheckExpression( edEquations, "LesserOf(" ) ) strbCSharpCode.Append( @" public Decimal LesserOf( Decimal decValue1, Decimal decValue2 ) { return decValue1 < decValue2 ? decValue1 : decValue2; } " ); // append constant definitions that will store the vdVariable values foreach( VariableDefinition vdVariable in vdVariables ) { if( Convert.IsDBNull( vdVariable ) || vdVariable == null ) continue; if( !vdVariable.Cacheable ) { // create public vdVariable in the format: public Decimal NAME = VALUE; strbCSharpCode.AppendFormat( " public Decimal {0} = {1};\r\n", vdVariable.Name, vdVariable.Value ); } else { // create a getter alias for every variable in order to call the // MetricManager // special trick "\"" below is to insert double quotes // inside the StringBuilder strbCSharpCode.AppendFormat( "\r\n public Decimal {0}\r\n", vdVariable.Name ); strbCSharpCode.Append( " { get { return mmMetricManager.GetMetricValue( \"" ); strbCSharpCode.Append( vdVariable.Name ); strbCSharpCode.Append("\", \r\n" ); strbCSharpCode.Append( @" dtBegin, dtEnd, decProvLocId, decLocationId, decProcessId, decPayHeaderId, strScheduleType ); } } " ); } } // skip a line and add main method to satisfy the compiler // in case of DLL generation required strbCSharpCode.Append( "\r\n" ); if( !strDLLFileNameParam.Equals( "" ) ) { strbCSharpCode.Append( @" public static void Main() {} " ); } // append C# function definitions that will return the result of the expressions foreach( EquationDefinition edEquation in edEquations ) { String strEquationName = ConvertEquationName( edEquation.Name ); // create public function in the format: public TYPE getNAME() strbCSharpCode.AppendFormat( "\r\n public {0} get{1}()\r\n ", edEquation.ReturnType.Name, strEquationName ); strbCSharpCode.Append( "{ \r\n" ); // define the function in the format: { return ( EXPRESSION ); } // or { return ( CONDITION ? EXPRESSION : 0 ); } if there is a condition if( edEquation.ConditionExpression.Equals( "" ) ) { if( bDebug ) AddTracingCode( strbCSharpCode ); // if there is division, enclose expression in a try-catch if( edEquation.Expression.IndexOf( "/" ) > 0 ) { strbCSharpCode.Append( " try { \r\n return ( " ); strbCSharpCode.Append( edEquation.Expression ); strbCSharpCode.Append( " ); \r\n } \r\n catch( Exception excpt ) {\r\n" ); strbCSharpCode.Append( " if( excpt.Message.IndexOf( \"Attempted to divide by zero\" ) >= 0 ) return 0; \r\n" ); strbCSharpCode.Append( " throw( excpt ); \r\n" ); strbCSharpCode.Append( " } \r\n" ); } else strbCSharpCode.AppendFormat( " return ( {0} ); \r\n", edEquation.Expression ); } else { // if there is division, enclose expression in a try-catch if( edEquation.Expression.IndexOf( "/" ) > 0 ) { strbCSharpCode.Append( " try { \r\n " ); strbCSharpCode.AppendFormat( "return ( {0} ? {1} : 0m ); \r\n", edEquation.ConditionExpression, edEquation.Expression ); strbCSharpCode.Append( " } \r\n catch( Exception excpt ) {\r\n" ); strbCSharpCode.Append( " if( excpt.Message.IndexOf( \"Attempted to divide by zero\" ) >= 0 ) return 0; \r\n" ); strbCSharpCode.Append( " throw( excpt ); \r\n" ); strbCSharpCode.Append( " } \r\n" ); } else strbCSharpCode.AppendFormat( " return ( {0} ? {1} : 0m ); \r\n", edEquation.ConditionExpression, edEquation.Expression ); } strbCSharpCode.Append( "}\r\n\r\n" ); // create a getter alias for every equation name (in order to not use // parenthesis when referencing the equation function) strbCSharpCode.AppendFormat( " public {0} {1} \r\n", edEquation.ReturnType.Name, strEquationName ); strbCSharpCode.Append( " { get { return " ); strbCSharpCode.AppendFormat( "get{0}", strEquationName ); strbCSharpCode.Append( "(); } }\r\n\r\n" ); // if there is a Qty Source specified, create a // public function in the format: public TYPE QuantityOfNAME() if( ! edEquation.QuantitySource.Equals( "" ) ) { strbCSharpCode.AppendFormat( "\r\n public {0} QuantityOf{1}", edEquation.ReturnType.Name, strEquationName ); strbCSharpCode.Append( "()\r\n { \r\n" ); // if there is a condition, arrange it so it only calculates quantity if condition is evaluated as true if( edEquation.ConditionExpression.Equals( "" ) ) // define the function in the format: { return ( QTY EXPRESSION ); } strbCSharpCode.AppendFormat( " return ( {0} ); \r\n", edEquation.QuantitySource ); else // define the function in the format: { return ( condition ? QTY EXPRESSION : 0m ); } strbCSharpCode.AppendFormat( "return ( {0} ? {1} : 0m ); \r\n", edEquation.ConditionExpression, edEquation.QuantitySource ); strbCSharpCode.Append( " }\r\n\r\n" ); } } // close the C# code strbCSharpCode.Append( "} }" ); // save source code if debugging (uncomment code in the method below) if( bDebug ) SaveSourceCode( strLoadedDLLName, strbCSharpCode ); // send the C# code in the string builder to the compiler and get the results CompilerResults crCompilerResults = CSharpCodeCompiler.CompileAssemblyFromSource( cpCompilerParameters, strbCSharpCode.ToString() ); if( crCompilerResults.Errors.HasErrors ) { if( strLoadedDLLName.Equals( "" ) ) { String strEQUATION_DLL_PREFIX = System.Configuration.ConfigurationManager.AppSettings[ "DLLPathAndName" ]; strLoadedDLLName = strEQUATION_DLL_PREFIX + ConvertEquationName( edEquations[ 0 ].Name ); } // save source code for debugging purposes StreamWriter swSourceFile = new StreamWriter( File.OpenWrite( strLoadedDLLName + "_with_error.cs" ) ); swSourceFile.Write( strbCSharpCode.ToString() ); swSourceFile.Close(); // create a string builder listing all errors and throw an exception StringBuilder strbErrors = new StringBuilder(); strbErrors.Append( "Error Compiling Expression: " ); foreach( CompilerError err in crCompilerResults.Errors ) { strbErrors.AppendFormat( "{0}\r\n(Error {1}, Line {2}, Column {3})", err.ErrorText, err.ErrorNumber, err.Line.ToString(), err.Column.ToString() ); } throw new Exception( strbErrors.ToString() ); } // reinstantiate app domain and load the new DLL adEvaluatorDomain = AppDomain.CreateDomain( "EvaluatorDomain" ); try { // get the dynamically compiled assembly and create an instance // of the _Evaluator class Assembly assemblyEvaluatorClass = crCompilerResults.CompiledAssembly; _objInstantiatedEvaluator = assemblyEvaluatorClass.CreateInstance( "ExpressionEvaluator._Evaluator" ); // // create instance of evaluator (from the DLL) within the application domain // _objInstantiatedEvaluator = adEvaluatorDomain.CreateInstanceFromAndUnwrap( // strDLLFileNameParam, "ExpressionEvaluator._Evaluator" ); } catch( BadImageFormatException eBadFormat ) { throw eBadFormat; } }
// constructor that receives EquationDefinition and VariableDefinition public Evaluator( EquationDefinition[] edEquations, VariableDefinition[] vdVariables ) { ConstructEvaluator( edEquations, vdVariables, "" ); }