0

I want a method, annotation or something else that lets me treat a string as C# code.

I read about CodeDom, Reflection and T4 templates but that isn't what I'm looking for.

What I need is more simple, I hope. I don´t want code generated at run-time.

Here's an example to clarify what I want. I'm using VS2010, Entity Framework 5 and the Code First approach.

I have an Insert method for each entity type. The following is the code for the method to insert a Cliente (Costumer). If a Cliente exists in the database then its updated instead of inserted:

    public int InsertarCliente(Cliente cliente)
    {
        int id = cliente.ClienteId;

        try
        {
            if (id != -1)
            {
                var clt = db.Clientes.Find(id);
                clt.Nombre = cliente.Nombre;
                clt.Apellido1 = cliente.Apellido1;
                clt.Apellido2 = cliente.Apellido2;
                // more similar statements
            }
            else
                db.Clientes.Add(cliente);

            db.SaveChanges();
            return cliente.ClienteId;
        }
        catch (DbEntityValidationException exc)
        {
            // code
        }
    }

I was trying to use CodeDom to create a generic method that works for any entity type. The method doesn't work and I know why: CodeDom doesn't compile and run arbitrary code, it requires extra namespaces, using statements, classes, methods, etc. That method doesn't work, here is the code to clarify what I was trying to do:

    public int Insertar<TEntity>(TEntity entidad, string[] atributos)
            where TEntity : class
    {
        string nombreEntidad = entidad.GetType().Name;
        string entidadId = nombreEntidad + "Id";
        string tabla = nombreEntidad + "s";

        int id = Convert.ToInt32(
             entidad.GetType().GetProperty(entidadId).GetValue(entidad, null));

        try
        {
            CodeDomProvider codeProvider = CodeDomProvider.CreateProvider("CSharp");
            CompilerParameters cp = new CompilerParameters();
            cp.GenerateExecutable = false;
            cp.GenerateInMemory = true;
            CompilerResults cr;
            string codigo;

            if (id != -1)
            {
                codigo = "var entidadAlmacenada = db." + tabla + ".Find(id);";
                cr = codeProvider.CompileAssemblyFromSource(cp, codigo);
                CompilerResults cr2;
                string codigoActualizador;

                foreach (string atr in atributos)
                {
                    codigoActualizador =
                        "entidadAlmacenada." + atr + " = entidad." + atr + ";";
                    cr2 = codeProvider.CompileAssemblyFromSource(
                              cp, codigoActualizador);
                }                        
            }
            else
            {
                codigo = "db." + tabla + ".Add(entidad);";
                cr = codeProvider.CompileAssemblyFromSource(cp, codigo);
            }

            db.SaveChanges();
            return Convert.ToInt32(
                entidad.GetType().GetProperty(entidadId).GetValue(entidad, null));
        }
        catch (DbEntityValidationException exc)
        {
            // code
        }
    }

I want a way to convert (inline) a string that represents code to the code that it represents.

Something like:

    string code = "line of code";
    code.toCode(); // or
    toCode(code); // or
    [ToCode]
    code;

Sorry if I'm writing too much, but I want to be clear this time.

What I need is that a string "containing code" to be replaced by the code before compilation time. No run-time compilation or execution.

Is there a way to do something like that?

TIA

EDIT:

The above example was just an example. But I want the "string to code conversion" in any situation.

  • You cant code to be generated at compile-time rather than run-time, right? Then the way to do it is by using code compilation templates. That means, in practice, either T4 or CodeSmith. – Roy Dictus Jan 29 '13 at 09:11
  • 1
    Can you actually achieve that via preprocessor directives `#if`? If you need to treat `string` as code that would always seem to be at runtime, not compile-time. – Alvin Wong Jan 29 '13 at 09:12
  • If I understand you correctly it's almost like you're looking for C style macros in C#? In that case, you can easily use the C preprocessor on C# source files... – MattDavey Jan 29 '13 at 09:17
  • @MattDavey [preprocessor macros are not supported in C#](http://stackoverflow.com/questions/709463/c-sharp-macro-definitions-in-preprocessor). You can use `#if` though. – Alvin Wong Jan 29 '13 at 09:18
  • 2
    If you know what the code will be at compile time, just write the code. – Jodrell Jan 29 '13 at 09:20
  • @AlvinWong there is nothing to stop you using the **C** (not C#) preprocessor on C# source code files. From Wikipedia > *"The C preprocessor is a separate program invoked by the compiler as the first part of translation. The language of preprocessor directives is agnostic to the grammar of C, so the C preprocessor can also be used independently to process other kinds of text files."* – MattDavey Jan 29 '13 at 09:24
  • @all I'm reading about all the suggestion. Let me some minutes to reply. –  Jan 29 '13 at 09:26
  • @MattDavey That is really a great way to make fun on other developers. – Alvin Wong Jan 29 '13 at 09:26
  • @AlvinWong what do you mean? – MattDavey Jan 29 '13 at 09:27
  • @MattDavey isn't it? You write `#define blah(x) Console.WriteLine(x)` and nobody else can compile the code using only C# compiler. – Alvin Wong Jan 29 '13 at 09:29
  • @AlvinWong it's easy to integrate the C preprocessor into the build process. It's just a command line exe, it can be added as a pre-build step. MSBuild makes this pretty seamless. – MattDavey Jan 29 '13 at 09:30
  • @Jodrell *"If you know what the code will be at compile time, just write the code."* - all aboard the truth train! – MattDavey Jan 29 '13 at 09:35
  • @AlvinWong I think that preprocessor directives #if will make the work. :) –  Jan 29 '13 at 09:47
  • Now I will read the answers to vote. Sorry, I read in English slowly. –  Jan 29 '13 at 09:48
  • There's an edit in the question: The above example was just an example. But I want the "string to code conversion" in any situation. –  Jan 29 '13 at 10:02
  • I think that Alvin suggestion is what I need. It isn't exactly the "string to code conversion" but can do the work with few statements. Thanks. –  Jan 29 '13 at 10:05

5 Answers5

1

Take a look at CSScript

CS-Script is a CLR (Common Language Runtime) based scripting system which uses ECMA-compliant C# as a programming language. CS-Script currently targets Microsoft implementation of CLR (.NET 2.0/3.0/3.5/4.0/4.5) with full support on Mono.

PS. Judging from your example you should probably invest your time in writing generic DB repository instead of generating code at runtime.

Jakub Konecki
  • 45,581
  • 7
  • 87
  • 126
  • OP says: `What I need is that a string "containing code" be replaced by that code before compilation time. No run-time compilation or execution.` – Alvin Wong Jan 29 '13 at 09:15
1

I have a feeling you're going up the wrong tree with dynamic code generation.

I did something very similar just this weekend. It transfers tables from an ODBC to EF.

Sorry I don't have time to make this more compact example. It's generic though and I think it does a very similar thing to what you are asking:

using Accounting.Domain.Concrete;
using Accounting.Domain.Entities;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Design.PluralizationServices;
using System.Data.Entity.Migrations;
using System.Data.Odbc;
using System.Globalization;
using System.Linq;

namespace QuickBooks.Services
{
    public class QuickBooksSynchService
    {
        string qodbcConnectionString = @"DSN=QuickBooks Data;SERVER=QODBC;OptimizerDBFolder=%UserProfile%\QODBC Driver for QuickBooks\Optimizer;OptimizerAllowDirtyReads=N;SyncFromOtherTables=Y;IAppReadOnly=Y";
        PluralizationService pluralizationService = PluralizationService.CreateService(CultureInfo.CurrentCulture);
        readonly int companyID;

        public QuickBooksSynchService(string companyName)
        {
            // Make sure the name of QODBC company is same as passed in
            using (var con = new OdbcConnection(qodbcConnectionString))
            using (var cmd = new OdbcCommand("select top 1 CompanyName from Company", con))
            {
                con.Open();
                string currentCompany = (string)cmd.ExecuteScalar();
                if (companyName != currentCompany)
                {
                    throw new Exception("Wrong company - expecting " + companyName + ", got " + currentCompany);
                }
            }

            // Get the company ID using the name passed in (row with matching name must exist)
            using (var repo = new AccountingRepository(new AccountingContext(), true))
            {
                this.companyID = repo.CompanyFileByName(companyName).CompanyId;
            }
        }

        public IEnumerable<T> Extract<T>() where T : new()
        {
            using (var con = new OdbcConnection(qodbcConnectionString))
            using (var cmd = new OdbcCommand("select * from " + typeof(T).Name, con))
            {
                con.Open();
                var reader = cmd.ExecuteReader();
                while (reader.Read())
                {
                    var t = new T();

                    // Set half of the primary key
                    typeof(Customer).GetProperty("CompanyId").SetValue(t, this.companyID, null);

                    // Initialize all DateTime fields
                    foreach (var datePI in from p in typeof(Customer).GetProperties()
                                           where p.PropertyType == typeof(DateTime)
                                           select p)
                    {
                        datePI.SetValue(t, new DateTime(1900, 1, 1), null);
                    }

                    // Auto-map the fields
                    foreach (var colName in from c in reader.GetSchemaTable().AsEnumerable()
                                            select c.Field<string>("ColumnName"))
                    {
                        object colValue = reader[colName];
                        if ((colValue != DBNull.Value) && (colValue != null))
                        {
                            typeof(Customer).GetProperty(colName).SetValue(t, colValue, null);
                        }
                    }

                    yield return t;
                }
            }
        }

        public void Load<T>(IEnumerable<T> ts, bool save) where T : class
        {
            using (var context = new AccountingContext())
            {
                var dbSet = context
                                .GetType()
                                .GetProperty(this.pluralizationService.Pluralize(typeof(T).Name))
                                .GetValue(context, null) as DbSet<T>;

                if (dbSet == null)
                    throw new Exception("could not cast to DbSet<T> for T = " + typeof(T).Name);

                foreach (var t in ts)
                {
                    dbSet.AddOrUpdate(t);
                }

                if (save)
                {
                    context.SaveChanges();
                }
            }
        }
    }
}
Aaron Anodide
  • 16,906
  • 15
  • 62
  • 121
  • +1 for *"I have a feeling you're going up the wrong tree with dynamic code generation."*. Especially for something so trivial as repository. – MattDavey Jan 29 '13 at 09:27
0

Try using the capabilities of the new .net framework which allow you to use Roslyn API to the compiler.

You might get exactly the what code sample you need from this Read-Eval-Print Loop example using Roslyn:

http://gissolved.blogspot.ro/2011/12/c-repl.html http://blogs.msdn.com/b/visualstudio/archive/2011/10/19/introducing-the-microsoft-roslyn-ctp.aspx

dutzu
  • 3,883
  • 13
  • 19
0

personally i would just implement a generic repository pattern (lots of results on google and the asp.net mvc site) which exposes an IQueryable collection so then you could just query the IQueryable collection directly

something like this tutorial http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application

nakchak
  • 498
  • 1
  • 3
  • 13
0

An alternative (and imho preferrable) approach to achieve what you are trying to do is using db.Set<TEntity>().Find(id) etc

Jan Van Herck
  • 2,254
  • 17
  • 15
  • Your answer doesn't talk about the "string to code conversion" but I find it useful. Thanks. –  Jan 29 '13 at 10:11