5

I'm using the Mono.CSharp library to emit code. Following another question on SO (http://stackoverflow.com/questions/3407318/mono-compiler-as-a-service-mcs) I managed to get Mono.CSharp evaluating correctly on the Microsoft CLR.

To add flexibility in my app I'd like to be able to customize a query at runtime - by allowing the user to provide a LINQ query as a string that gets parsed and hits the database when executed.

Given this basic snippet of code:

IQueryable<Contact> contacts = GetContacts();
string query = "from contact in contacts
                where contact.Name == \"name\"
                select contact";
var queryableResult = Mono.CSharp.Evaluator.Evaluate(query);

How can I 'inject' the contacts variable into the Mono.CSharp.Evaluator to be evaluated as part of the query? Am I going about this the right way? In the end I either need the resulting Expression or the IQueryable from the 'query' string.

Kevin McKelvin
  • 3,467
  • 1
  • 27
  • 27
  • Check out my answer here: https://stackoverflow.com/questions/3788096/injecting-a-variable-into-the-mono-csharp-evaluator-runtime-compiling-a-linq-qu – henon Jun 03 '21 at 10:06

2 Answers2

5

I think you have a few options:

  1. Use static or ThreadStatic variables to exchange data between the caller and you string based code:

    namespace MyNs
    {
      public class MyClass
      {
     [ThreadStatic] // thread static so the data is specific to the calling thread
     public static string MyEnumerableVariable;
    
    
     public void DoSomething() 
     {
          Evaluator.ReferenceAssembly(Assembly.GetExecutingAssembly());
          Evaluator.Run("using MyNs;")
          // run the dynamic code
          var s = @"return (from contact in MyNs.MyClass.MyEnumerableVariable where contact.Name == ""John"" select contact).ToList();";
          Evaluator.Evaluate(s);
     }
    

    } }

  2. Return a delegate from your string code:

    
     public void DoSomething() 
     {
    
    

    // run the dynamic code var s = @"return new Func<string, IQueryable<MyNs.Model.Contact>, IList>((s,q) => (from contact in q where contact.Name == s select contact).ToList());"; var func = (Func<string, IQueryable<MyNs.Model.Contact>, IList>)Evaluator.Evaluate(s); var result = func("John", myQueryableOfContactsFromNHibernate);

    }

  3. Go the full blown route.

string query = string.Format(
@"using (var dc = new DataContext()) 
{
  return (from contact in dc.Contacts where contact.Name == ""{0}"" select contact).ToList();
}", "John");

var result = Mono.CSharp.Evaluator.Evaluate(query);

Jeff
  • 35,755
  • 15
  • 108
  • 220
  • From a design point that's tightly coupling the code to the DataContext. I'm using NHibernate 3 with Ninject to get a new, clean ISession injected into my MVC controller's constructor for every request. If that's the only route then I can work around it with some static calls, just isn't ideal. – Kevin McKelvin Sep 24 '10 at 15:04
  • Let me think some more, and I'm sure there are better ways, but my first thought is to make a ThreadStatic variable in your calling code and reference that from your string evaluated code. – Jeff Sep 24 '10 at 15:11
  • Attempting #2, <% var obj = Evaluator.Evaluate("return new Func () => 3;"); %>. I'm receiving [this error](http://msdn.microsoft.com/en-us/library/cz0x62kh%28v=VS.90%29.aspx). If I remove the new, I get "Unexpected symbol '=>'". How do I return a Func from Evaluate into a var? – DougJones Nov 12 '10 at 14:13
  • 1
    The solution to my problem turned out to be to drop the return keyword and include the actual function as the parameter to the constructor, so... <% var obj = Evaluator.Evaluate("new Func(() => 3);"); %>. Hopefully that helps someone else. – DougJones Nov 12 '10 at 14:33
  • I found a pretty good way to inject local variables and posted it under this question: https://stackoverflow.com/questions/11569177/mono-csharp-how-do-i-inject-a-value-entity-into-a-script/67819345#67819345 – henon Jun 03 '21 at 10:01
2

I didn't try this, but I guess you could use Mono compiler to create a delegate vthat takes IQueryable<Contract> as argument and returns the filtered query. Something like:

IQueryable<Contact> contacts = GetContacts(); 
string query = "new Func<IQueryable<Contact>, IQueryable<Contact>>(contracts =>
                  from contact in contacts 
                  where contact.Name == \"name\" 
                  select contact)"; 
var res = Mono.CSharp.Evaluator.Evaluate(query); 

Then you just need to cast res to an appropriate Func<,> type and invoke it to get the result.

Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553