Compile C# Roslyn

657 views Asked by At

I'm using Roslyn to try and compile and run code at runtime. I've ysed some code I found online and have it somewhat working.

public Type EvalTableScript(string Script, CRMMobileFramework.EnbuUtils EnbuUtils, CRMMobileFramework.Includes.DBAdapter dbConn)
{
    var syntaxTree = SyntaxTree.ParseText(Script);
    var compilation = Compilation.Create("EnbuScript.dll",
        options: new CompilationOptions(outputKind: OutputKind.DynamicallyLinkedLibrary),
        references: new[]
        {
            new MetadataFileReference(typeof(object).Assembly.Location),
            new MetadataFileReference(typeof(EnbuUtils).Assembly.Location),
            new MetadataFileReference(typeof(DBAdapter).Assembly.Location),
            MetadataFileReference.CreateAssemblyReference("System.Data"),
            MetadataFileReference.CreateAssemblyReference("System.Linq"),
            MetadataFileReference.CreateAssemblyReference("System"),
            MetadataFileReference.CreateAssemblyReference("System.XML")


        },
        syntaxTrees: new[] { syntaxTree });
    var diagnostics = compilation.GetDiagnostics();


    foreach (var diagnostic in diagnostics)
    {
        Console.WriteLine("Error: {0}", diagnostic.Info.GetMessage());
    }

    Assembly assembly;
    using (var stream = new MemoryStream())
    {

        EmitResult emitResult = compilation.Emit(stream);
        assembly = Assembly.Load(stream.GetBuffer());
    }


    Type ScriptClass = assembly.GetType("EnbuScript");
    // Pass back the entire class so we can call it at the appropriate time.
    return ScriptClass;
}

Then I'm trying to call this:

string Script = @"
using System;
using System.Data;
using System.IO;
using System.Linq;

public class EnbuScript 
{


    public string PostInsertRecord(CRMMobileFramework.EnbuUtils EnbuUtils,CRMMobileFramework.Includes.DBAdapter dbConn) 
    {
        string ScriptTable = ""QuoteItems""; 
        DataSet EntityRecord = dbConn.FindRecord(""*"", ScriptTable, ""QuIt_LineItemID='"" + EnbuUtils.GetContextInfo(ScriptTable) + ""'"", """", 1, 1, false); 
        string OrderId = EntityRecord.Tables[""item""].Rows[0][""QuIt_orderquoteid""].ToString(); 

        string UpdateOrderTotalCommand = ""UPDATE Quotes SET Quot_nettamt = (select SUM(QuIt_listprice * quit_quantity) from QuoteItems where quit_orderquoteid = "" + OrderId + "" ) where Quot_OrderQuoteID = "" + OrderId; 
        dbConn.ExecSql(UpdateOrderTotalCommand);  
        return ""Complete"";
    }

}";


Type EnbuScript = EnbuUtils.EvalTableScript(Script, EnbuUtils, dbConn);
MethodInfo methodInfo = EnbuScript.GetMethod("InsertRecord");
object[] parameters = { EnbuUtils, dbConn };
string InsertRecordResult = methodInfo.Invoke(null, parameters).ToString();

As you can see I've been messing around with trying to pass parameters to the compilation. Basically I've got 4 functions I need to support, that will come in as a string. What I'm trying to do is create a class for these 4 functions and compile and run them. This part works. What I now need to be able to do is pass class instances to this. In the code you'll see a dbConn which is basically my database connection. I need to pass the instance of this to the method I'm calling at runtime so it has it's correct context.

I have another implementation of this where I'm using the Roslyn session. I originally tried to use this and override my function at runtime but that didn't work either. See below what I tried:

    public static void EvalTableScript(ref EnbuUtils EnbuUtils, DBAdapter dbConn, string EvaluateString)
{
    ScriptEngine roslynEngine = new ScriptEngine();
    Roslyn.Scripting.Session Session = roslynEngine.CreateSession(EnbuUtils);

    Session.AddReference(EnbuUtils.GetType().Assembly);
    Session.AddReference(dbConn.GetType().Assembly);
    Session.AddReference("System.Web");
    Session.AddReference("System.Data");
    Session.AddReference("System");
    Session.AddReference("System.XML");
    Session.ImportNamespace("System");
    Session.ImportNamespace("System.Web");
    Session.ImportNamespace("System.Data");
    Session.ImportNamespace("CRMMobileFramework");
    Session.ImportNamespace("CRMMobileFramework.Includes");

    try
    {
        var result = (string)Session.Execute(EvaluateString);
    }
    catch (Exception ex)
    {

    }
}

I tried to call this using:

                    string PostInsertRecord = "" +
                " public override void PostInsertRecord() " +
                "{ " +
                    " string ScriptTable = \"QuoteItems\"; " +
                    "DataSet EntityRecord = dbConn.FindRecord(\"*\", ScriptTable, \"QuIt_LineItemID='\" + EnbuUtils.GetContextInfo(ScriptTable) + \"'\", \"\", 1, 1, false); " +
                    "string OrderId = EntityRecord.Tables[\"item\"].Rows[0][\"QuIt_orderquoteid\"].ToString(); " +

                    "string UpdateOrderTotalCommand = \"UPDATE Quotes SET Quot_nettamt = (select SUM(QuIt_listprice * quit_quantity) from QuoteItems where quit_orderquoteid = \" + OrderId + \" ) where Quot_OrderQuoteID = \" + OrderId; " +
                    "dbConn.ExecSql(UpdateOrderTotalCommand); " +
                "} ";

The function is declared as a public virtual void in the EnbuUtils class but it says it doesn't have a suitable method to override.

Safe to say, I'm stumped! Any help appreciated! Thanks

1

There are 1 answers

0
David On

I got this in the end - this first method was very close to what I actually needed. Changed the method to static and had to add a few references including the full namespace.