You are currently browsing the category archive for the ‘.NET’ category.

TCCC14 (off-by-one error?) takes place on April 27th.  Go register!  Even if you just want the free breakfast (donuts, coffee… what more does a geek need?), campus atmosphere and chance to win prizes, do it! Smile  It doesn’t even matter if some of the presentations aren’t in your usual fields; if you can’t decide, go to a random or fun-looking one!

http://www.twincitiescodecamp.com/TCCC/Default.aspx

I’ve been to the last two, and they’re great!

Update: Source code now available on github, and also as an npm package!

What happens when you put node.js and socket.io on a Raspberry Pi, then use them as goop between Box2D (physics engine) demos?

All kinds of awesome Smile – Try it here (not sure how long I’ll keep this running, so get it while it’s hot).

Open it in two browsers, and see how long you can keep them in sync.

image

You can find the source on github, and also as an npm package.

Smile

Following my post last year about modifying LINQ to SQL command text (evil, as it calls private methods through reflection) here’s an equally evil, but faster version that pre-compiles most of its work through an expression tree:

public delegate string ModifyCommandDelegate( string commandText, IDictionary<string, object> parameters );

public class DataContextInterceptor
{
    private DataContext dc;
    private object oldProvider;
    private Type providerType;
    private ModifyCommandDelegate modifyCommand;

    private static Func<object, ModifyCommandDelegate, DataContext, Func<Expression, object>> CompileFactory = CreateCompileFactory().Compile();
    private static Func<object, ModifyCommandDelegate, Func<Expression, IExecuteResult>> ExecuteFactory = CreateExecuteFactory().Compile();

    public static TDataContext Intercept<TDataContext>( TDataContext dc, ModifyCommandDelegate modifyCommand )
        where TDataContext : DataContext
    {
        new DataContextInterceptor( dc, modifyCommand );

        //            typeof( Expression ).GetProperty( "DebugView", BindingFlags.Instance | BindingFlags.NonPublic ).GetValue( executeFactoryExp, null ).Dump();

        return dc;
    }

    public DataContextInterceptor( DataContext dc, ModifyCommandDelegate modifyCommand )
    {
        this.dc = dc;
        this.modifyCommand = modifyCommand;

        FieldInfo providerField = typeof( DataContext ).GetField( "provider", BindingFlags.Instance | BindingFlags.NonPublic );
        var existingProvider = providerField.GetValue( dc );

        if ( existingProvider is IProviderProxy )
        {
            // System.Diagnostics.Trace.WriteLine( string.Format( "DataContext {0} already intercepted", dc.GetHashCode() ) );
        }
        else
        {
            oldProvider = existingProvider;

            var proxy = new ProviderProxy( this, oldProvider ).GetTransparentProxy();

            providerField.SetValue( dc, proxy );

            // System.Diagnostics.Trace.WriteLine( string.Format( "DataContext {0} intercepted", dc.GetHashCode() ) );
        }
    }

    public static MethodCallExpression MakeMethodCall( Type type, string methodName, params Expression[] arguments )
    {
        // ( "Making MethodCallExpression for " + methodName ).Dump();

        var methodInfo = type.GetMethod(
            methodName,
            BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic,
            null,
            arguments.Select( a => a.Type ).ToArray(),
            null );

        if ( methodInfo == null ) throw new ArgumentException( string.Format( "Unable to find method {0}.{1}", type.Name, methodName ), "methodName" );

        return Expression.Call( methodInfo, arguments );
    }

    public static MethodCallExpression MakeMethodCall( Expression instance, string methodName, params Expression[] arguments )
    {
        // ( "Making MethodCallExpression for " + instance.ToString() + "." + methodName ).Dump();

        var methodInfo = instance.Type.GetMethod(
            methodName,
            BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
            null,
            arguments.Select( a => a.Type ).ToArray(),
            null );

        if ( methodInfo == null ) throw new ArgumentException( string.Format( "Unable to find method {0}.{1}", instance.Type.Name, methodName ), "methodName" );

        return Expression.Call( instance, methodInfo, arguments );
    }

    protected internal object Compile( Expression query )
    {
        return DataContextInterceptor.CompileFactory( oldProvider, modifyCommand, dc )( query );
    }

    protected internal virtual IExecuteResult Execute( Expression query )
    {
        return DataContextInterceptor.ExecuteFactory( oldProvider, modifyCommand )( query );
    }

    public static Expression<Func<object, ModifyCommandDelegate, DataContext, Func<Expression, object>>> CreateCompileFactory()
    {
        var assembly = typeof( SqlProvider ).Assembly;

        var providerType = assembly.GetType( "System.Data.Linq.SqlClient.SqlProvider" );
        var providerParam = Expression.Parameter( typeof( object ), "provider" );
        var modifyCommandParam = Expression.Parameter( typeof( ModifyCommandDelegate ), "modifyCommand" );
        var dataContextParam = Expression.Parameter( typeof( DataContext ), "dataContext" );

        return Expression.Lambda<Func<object, ModifyCommandDelegate, DataContext, Func<Expression, object>>>(
            CreateCompileMethod( Expression.Convert( providerParam, providerType ), modifyCommandParam, dataContextParam ),
            providerParam,
            modifyCommandParam,
            dataContextParam );
    }

    public static Expression<Func<Expression, object>> CreateCompileMethod( Expression oldProvider, Expression modifyCommand, Expression dataContextParam )
    {
        var assembly = typeof( SqlProvider ).Assembly;

        var providerType = assembly.GetType( "System.Data.Linq.Provider.IProvider" );
        var funcletizerType = assembly.GetType( "System.Data.Linq.SqlClient.Funcletizer" );
        var annotationsType = assembly.GetType( "System.Data.Linq.SqlClient.SqlNodeAnnotations" );
        var queryInfoType = assembly.GetType( "System.Data.Linq.SqlClient.SqlProvider" ).GetNestedType( "QueryInfo", BindingFlags.NonPublic );
        var readerFactoryType = assembly.GetType( "System.Data.Linq.SqlClient.IObjectReaderFactory" );
        var resultShapeType = assembly.GetType( "System.Data.Linq.SqlClient.SqlProvider" ).GetNestedType( "ResultShape", BindingFlags.NonPublic );
        var compiledSubQueryType = assembly.GetType( "System.Data.Linq.SqlClient.ICompiledSubQuery" );
        var typeSystemType = assembly.GetType( "System.Data.Linq.SqlClient.TypeSystem" );
        var compiledQueryType = assembly.GetType( "System.Data.Linq.SqlClient.SqlProvider+CompiledQuery" );

        var queryParam = Expression.Variable( typeof( Expression ), "query" );

        var providerField = typeof( DataContext ).GetField( "provider", BindingFlags.Instance | BindingFlags.NonPublic );

        var annotationsVar = Expression.Variable( annotationsType, "annotations" );
        var queriesVar = Expression.Variable( queryInfoType.MakeArrayType(), "queries" );
        var infoVar = Expression.Variable( queryInfoType, "info" );
        var lambdaVar = Expression.Variable( typeof( LambdaExpression ), "lambda" );
        var readerFactoryVar = Expression.Variable( readerFactoryType, "readerFactory" );
        var subQueriesVar = Expression.Variable( compiledSubQueryType.MakeArrayType(), "subQueries" );
        var resultShapeVar = Expression.Variable( resultShapeType, "resultShape" );
        var returnTarget = Expression.Label( compiledQueryType );

        var getQuery = MakeMethodCall( infoVar, "get_Query" );
        var getResultType = MakeMethodCall( infoVar, "get_ResultType" );
        var getResultElementType = MakeMethodCall( typeSystemType, "GetElementType", getResultType );
        var modifySubQueries = CreateModifySubQueriesMethod();
        var compiledQueryConstructor = compiledQueryType.GetConstructors( BindingFlags.NonPublic | BindingFlags.Instance ).First();

        return Expression.Lambda<Func<Expression, object>>(
            Expression.Block(
                new[] { annotationsVar, queriesVar, infoVar, lambdaVar, readerFactoryVar, subQueriesVar, resultShapeVar },

                // this.InitializeProviderMode();
                MakeMethodCall( oldProvider, "InitializeProviderMode" ),

                // SqlNodeAnnotations annotations = new SqlNodeAnnotations();
                Expression.Assign( annotationsVar, Expression.New( annotationsType ) ),

                // QueryInfo[] queries = this.BuildQuery( query, annotations );
                Expression.Assign( queriesVar, MakeMethodCall( oldProvider, "BuildQuery", queryParam, annotationsVar ) ),

                // var info = ModifyQueries( (IEnumerable)queries );
                Expression.Assign( infoVar, Expression.Invoke( CreateModifyQueriesMethod(), queriesVar, modifyCommand ) ),

                // this.CheckSqlCompatibility(queries, annotations);
                MakeMethodCall( oldProvider, "CheckSqlCompatibility", queriesVar, annotationsVar ),

                // var lambda = query as LambdaExpression;
                Expression.Assign( lambdaVar, Expression.TypeAs( queryParam, lambdaVar.Type ) ),

                // if ( lambda != null )
                Expression.IfThen(
                    Expression.NotEqual( lambdaVar, Expression.Constant( null ) ),
                    // query = lambda.Body;
                    Expression.Assign( queryParam, Expression.Property( lambdaVar, "Body" ) ) ),

                // IObjectReaderFactory readerFactory = null;
                // ICompiledSubQuery[] subQueries = null;

                // QueryInfo info = queries[queries.Length - 1];
                // info defined above
                // var resultShape = (int)Invoke( info, "get_ResultShape" );
                Expression.Assign( resultShapeVar, MakeMethodCall( infoVar, "get_ResultShape" ) ),

                Expression.Switch(
                    Expression.Convert( resultShapeVar, typeof( int ) ),
                    // if ( resultShape == 1 /* ResultShape.Singleton */ )
                    Expression.SwitchCase(
                        Expression.Block(
                            // subQueries = this.CompileSubQueries( info.Query );
                            Expression.Assign( subQueriesVar, MakeMethodCall( oldProvider, "CompileSubQueries", getQuery ) ),
                            // ModifySubQueries( (IEnumerable)subQueries );
                            Expression.Invoke( modifySubQueries, subQueriesVar, modifyCommand ),
                            // readerFactory = this.GetReaderFactory( info.Query, info.ResultType );
                            Expression.Assign( readerFactoryVar, MakeMethodCall( oldProvider, "GetReaderFactory", getQuery, getResultType ) ),
                            Expression.Empty() ),
                        Expression.Constant( 1 ) ),
                    // else if ( resultShape == 2 /* ResultShape.Sequence */ )
                    Expression.SwitchCase(
                        Expression.Block(
                            // subQueries = this.CompileSubQueries( info.Query );
                            Expression.Assign( subQueriesVar, MakeMethodCall( oldProvider, "CompileSubQueries", getQuery ) ),
                            // ModifySubQueries( (IEnumerable)subQueries );
                            Expression.Invoke( modifySubQueries, subQueriesVar, modifyCommand ),
                            // readerFactory = this.GetReaderFactory( info.Query, TypeSystem.GetElementType( info.ResultType ) );
                            Expression.Assign( readerFactoryVar, MakeMethodCall( oldProvider, "GetReaderFactory", getQuery, getResultElementType ) ),
                            Expression.Empty() ),
                        Expression.Constant( 2 ) ) ),

                // dc.provider = oldProvider;    // (Unfortunately needed to ensure compilation runs)
                Expression.Assign( Expression.MakeMemberAccess( dataContextParam, providerField ), oldProvider ),

                // return new CompiledQuery( oldProvider, query, queries, readerFactory, subQueries );
                Expression.Return( returnTarget, Expression.New( compiledQueryConstructor, oldProvider, queryParam, queriesVar, readerFactoryVar, subQueriesVar ) ),
                // --- Expression.Throw( Expression.New( typeof( Exception ).GetConstructor( new[] { typeof( string ) } ), Expression.Constant( "boo4" ) ) ),
                Expression.Label( returnTarget, Expression.Constant( null, compiledQueryType ) ) ),
            queryParam );
    }

    public static Expression<Func<object, ModifyCommandDelegate, Func<Expression, IExecuteResult>>> CreateExecuteFactory()
    {
        var assembly = typeof( SqlProvider ).Assembly;

        var providerType = assembly.GetType( "System.Data.Linq.SqlClient.SqlProvider" );
        var providerParam = Expression.Parameter( typeof( object ), "provider" );
        var modifyCommandParam = Expression.Parameter( typeof( ModifyCommandDelegate ), "modifyCommand" );

        return Expression.Lambda<Func<object, ModifyCommandDelegate, Func<Expression, IExecuteResult>>>(
            CreateExecuteMethod( Expression.Convert( providerParam, providerType ), modifyCommandParam ),
            providerParam,
            modifyCommandParam );
    }

    public static Expression<Func<Expression, IExecuteResult>> CreateExecuteMethod( Expression oldProvider, Expression modifyCommand )
    {
        var assembly = typeof( SqlProvider ).Assembly;

        var providerType = assembly.GetType( "System.Data.Linq.Provider.IProvider" );
        var funcletizerType = assembly.GetType( "System.Data.Linq.SqlClient.Funcletizer" );
        var annotationsType = assembly.GetType( "System.Data.Linq.SqlClient.SqlNodeAnnotations" );
        var queryInfoType = assembly.GetType( "System.Data.Linq.SqlClient.SqlProvider" ).GetNestedType( "QueryInfo", BindingFlags.NonPublic );
        var readerFactoryType = assembly.GetType( "System.Data.Linq.SqlClient.IObjectReaderFactory" );
        var resultShapeType = assembly.GetType( "System.Data.Linq.SqlClient.SqlProvider" ).GetNestedType( "ResultShape", BindingFlags.NonPublic );
        var compiledSubQueryType = assembly.GetType( "System.Data.Linq.SqlClient.ICompiledSubQuery" );
        var typeSystemType = assembly.GetType( "System.Data.Linq.SqlClient.TypeSystem" );

        var queryParam = Expression.Variable( typeof( Expression ), "query" );

        var cachedResultVar = Expression.Variable( typeof( IExecuteResult ), "cachedResult" );
        var annotationsVar = Expression.Variable( annotationsType, "annotations" );
        var queriesVar = Expression.Variable( queryInfoType.MakeArrayType(), "queries" );
        var infoVar = Expression.Variable( queryInfoType, "info" );
        var lambdaVar = Expression.Variable( typeof( LambdaExpression ), "lambda" );
        var readerFactoryVar = Expression.Variable( readerFactoryType, "readerFactory" );
        var subQueriesVar = Expression.Variable( compiledSubQueryType.MakeArrayType(), "subQueries" );
        var resultShapeVar = Expression.Variable( resultShapeType, "resultShape" );
        var returnTarget = Expression.Label( typeof( IExecuteResult ) );

        var getQuery = MakeMethodCall( infoVar, "get_Query" );
        var getResultType = MakeMethodCall( infoVar, "get_ResultType" );
        var getResultElementType = MakeMethodCall( typeSystemType, "GetElementType", getResultType );
        var modifySubQueriesMethod = CreateModifySubQueriesMethod();

        return Expression.Lambda<Func<Expression, IExecuteResult>>(
            Expression.Block(
                new[] { annotationsVar, queriesVar, infoVar, lambdaVar, readerFactoryVar, subQueriesVar, resultShapeVar },

                // this.InitializeProviderMode();
                MakeMethodCall( oldProvider, "InitializeProviderMode" ),

                // query = Funcletizer.Funcletize(query);
                Expression.Assign( queryParam, MakeMethodCall( funcletizerType, "Funcletize", queryParam ) ),

                // if ( this.EnableCacheLookup )
                Expression.IfThen(
                    MakeMethodCall( oldProvider, "get_EnableCacheLookup" ),
                    Expression.Block(
                        new[] { cachedResultVar },
                        // IExecuteResult cachedResult = this.GetCachedResult(query);
                        Expression.Assign( cachedResultVar, MakeMethodCall( oldProvider, "GetCachedResult", queryParam ) ),
                        // if ( cachedResult != null )
                        Expression.IfThen(
                            Expression.NotEqual( cachedResultVar, Expression.Constant( null ) ),
                            // return cachedResult;
                            Expression.Return( returnTarget, cachedResultVar ) ) ) ),

                // SqlNodeAnnotations annotations = new SqlNodeAnnotations();
                Expression.Assign( annotationsVar, Expression.New( annotationsType ) ),

                // QueryInfo[] queries = this.BuildQuery(query, annotations);
                Expression.Assign( queriesVar, MakeMethodCall( oldProvider, "BuildQuery", queryParam, annotationsVar ) ),

                // var info = ModifyQueries( (IEnumerable)queries );
                Expression.Assign( infoVar, Expression.Invoke( CreateModifyQueriesMethod(), queriesVar, modifyCommand ) ),

                // this.CheckSqlCompatibility(queries, annotations);
                MakeMethodCall( oldProvider, "CheckSqlCompatibility", queriesVar, annotationsVar ),

                // var lambda = query as LambdaExpression;
                Expression.Assign( lambdaVar, Expression.TypeAs( queryParam, lambdaVar.Type ) ),

                // if ( lambda != null )
                Expression.IfThen(
                    Expression.NotEqual( lambdaVar, Expression.Constant( null ) ),
                    // query = lambda.Body;
                    Expression.Assign( queryParam, Expression.Property( lambdaVar, "Body" ) ) ),

                // IObjectReaderFactory readerFactory = null;
                // ICompiledSubQuery[] subQueries = null;

                // QueryInfo info = queries[queries.Length - 1];
                // info defined above
                // var resultShape = (int)Invoke( info, "get_ResultShape" );
                Expression.Assign( resultShapeVar, MakeMethodCall( infoVar, "get_ResultShape" ) ),

                Expression.Switch(
                    Expression.Convert( resultShapeVar, typeof( int ) ),
                    // if ( resultShape == 1 /* ResultShape.Singleton */ )
                    Expression.SwitchCase(
                        Expression.Block(
                            // subQueries = this.CompileSubQueries( info.Query );
                            Expression.Assign( subQueriesVar, MakeMethodCall( oldProvider, "CompileSubQueries", getQuery ) ),
                            // ModifySubQueries( (IEnumerable)subQueries );
                            Expression.Invoke( modifySubQueriesMethod, subQueriesVar, modifyCommand ),
                            // readerFactory = this.GetReaderFactory( info.Query, info.ResultType );
                            Expression.Assign( readerFactoryVar, MakeMethodCall( oldProvider, "GetReaderFactory", getQuery, getResultType ) ),
                            Expression.Empty() ),
                        Expression.Constant( 1 ) ),
                    // else if ( resultShape == 2 /* ResultShape.Sequence */ )
                    Expression.SwitchCase(
                        Expression.Block(
                            // subQueries = this.CompileSubQueries( info.Query );
                            Expression.Assign( subQueriesVar, MakeMethodCall( oldProvider, "CompileSubQueries", getQuery ) ),
                            // ModifySubQueries( (IEnumerable)subQueries );
                            Expression.Invoke( modifySubQueriesMethod, subQueriesVar, modifyCommand ),
                            // readerFactory = this.GetReaderFactory( info.Query, TypeSystem.GetElementType( info.ResultType ) );
                            Expression.Assign( readerFactoryVar, MakeMethodCall( oldProvider, "GetReaderFactory", getQuery, getResultElementType ) ),
                            Expression.Empty() ),
                        Expression.Constant( 2 ) ) ),

                // return this.ExecuteAll(query, queries, readerFactory, null, subQueries);
                Expression.Return( returnTarget, MakeMethodCall( oldProvider, "ExecuteAll", queryParam, queriesVar, readerFactoryVar, Expression.Constant( null, typeof( object[] ) ), subQueriesVar ) ),
                // --- Expression.Throw( Expression.New( typeof( Exception ).GetConstructor( new[] { typeof( string ) } ), Expression.Constant( "boo4" ) ) ),
                Expression.Label( returnTarget, Expression.Constant( null, typeof( IExecuteResult ) ) ) ),
            queryParam );
    }

    private static LambdaExpression CreateModifyQueriesMethod()
    {
        var assembly = typeof( SqlProvider ).Assembly;

        var queryInfoType = assembly.GetType( "System.Data.Linq.SqlClient.SqlProvider" ).GetNestedType( "QueryInfo", BindingFlags.NonPublic );
        var queriesParam = Expression.Parameter( queryInfoType.MakeArrayType(), "queries" );
        var modifyCommandParam = Expression.Parameter( typeof( ModifyCommandDelegate ), "modifyCommand" );

        var returnLabel = Expression.Label();
        var indexVar = Expression.Variable( typeof( int ), "index" );
        var queryVar = Expression.Variable( queryInfoType, "query" );

        return Expression.Lambda(
            Expression.Block(
                new[] { indexVar, queryVar },
                // var index = 0;
                Expression.Assign( indexVar, Expression.Constant( 0 ) ),
                Expression.Loop(
                    Expression.Block(
                        // if ( index >= queries.Length ) break;
                        Expression.IfThen(
                            Expression.GreaterThanOrEqual( indexVar, Expression.ArrayLength( queriesParam ) ),
                            Expression.Break( returnLabel ) ),
                        // query = queries[ index ];
                        Expression.Assign( queryVar, Expression.ArrayIndex( queriesParam, indexVar ) ),
                        // ModifyQuery( query );
                        Expression.Invoke( CreateModifyQueryMethod(), queryVar, modifyCommandParam ),
                        // ++ index;
                        Expression.PreIncrementAssign( indexVar ) ),
                    returnLabel ),
                queryVar ),
            queriesParam,
            modifyCommandParam );
    }

    private static LambdaExpression CreateModifySubQueriesMethod()
    {
        var assembly = typeof( SqlProvider ).Assembly;

        var iCompiledSubQueryType = assembly.GetType( "System.Data.Linq.SqlClient.ICompiledSubQuery" );
        var compiledSubQueryType = assembly.GetType( "System.Data.Linq.SqlClient.SqlProvider" ).GetNestedType( "CompiledSubQuery", BindingFlags.NonPublic );
        var queryInfoType = assembly.GetType( "System.Data.Linq.SqlClient.SqlProvider" ).GetNestedType( "QueryInfo", BindingFlags.NonPublic );
        var subQueriesType = iCompiledSubQueryType.MakeArrayType();

        var queryInfoField = compiledSubQueryType.GetField( "queryInfo", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic );
        var subQueriesField = compiledSubQueryType.GetField( "subQueries", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic );

        var subQueriesParam = Expression.Parameter( subQueriesType, "subQueries" );
        var modifyCommandParam = Expression.Parameter( typeof( ModifyCommandDelegate ), "modifyCommand" );

        var indexVar = Expression.Variable( typeof( int ), "index" );
        var subQueryVar = Expression.Variable( compiledSubQueryType, "subQuery" );
        var queryInfoVar = Expression.Variable( queryInfoType, "queryInfo" );
        var nestedSubQueriesVar = Expression.Variable( iCompiledSubQueryType.MakeArrayType(), "nestedSubQueries" );
        var modifySubQueriesDelegateType = typeof( Action<> ).MakeGenericType( iCompiledSubQueryType.MakeArrayType() );
        var modifySubQueriesDelegateVar = Expression.Variable( modifySubQueriesDelegateType, "modifySubQueries" );
        var loopTarget = Expression.Label();

        var innerLambda = Expression.Lambda(
            modifySubQueriesDelegateType,
            Expression.Block(
                new[] { indexVar },
            // var index = 0;
                Expression.Assign( indexVar, Expression.Constant( 0 ) ),
                Expression.Loop(
                    Expression.Block(
                        new[] { subQueryVar },
                        // if ( index >= subQueries.Length ) break;
                        Expression.IfThen(
                            Expression.GreaterThanOrEqual( indexVar, Expression.ArrayLength( subQueriesParam ) ),
                            Expression.Break( loopTarget ) ),
                        // var subQuery = subQueries[ index ];
                        Expression.Assign( subQueryVar, Expression.TypeAs( Expression.ArrayIndex( subQueriesParam, indexVar ), compiledSubQueryType ) ),
                        // if ( subQuery != null )
                        Expression.IfThen(
                            Expression.NotEqual( subQueryVar, Expression.Constant( null ) ),
                            Expression.Block(
                                new[] { queryInfoVar, nestedSubQueriesVar },
                                // var queryInfo = subQuery.queryInfo;
                                Expression.Assign( queryInfoVar, Expression.MakeMemberAccess( subQueryVar, queryInfoField ) ),
                                // ModifyQuery( queryInfo, modifyCommand );
                                Expression.Invoke( CreateModifyQueryMethod(), queryInfoVar, modifyCommandParam ),
                                // var nestedSubQueries = subQuery.subQueries;
                                Expression.Assign( nestedSubQueriesVar, Expression.MakeMemberAccess( subQueryVar, subQueriesField ) ),
                                // if ( nestedSubQueries != null )
                                Expression.IfThen(
                                    Expression.NotEqual( nestedSubQueriesVar, Expression.Constant( null ) ),
                                    // ModifySubQueries( nestedSubQueries, modifyCommand );
                                    Expression.Invoke( modifySubQueriesDelegateVar, nestedSubQueriesVar ) ) ) ),
                        // ++ index;
                        Expression.PreIncrementAssign( indexVar ) ),
                    loopTarget ) ),
                subQueriesParam );

        return Expression.Lambda(
            Expression.Block(
                new[] { modifySubQueriesDelegateVar },
                Expression.Assign( modifySubQueriesDelegateVar, innerLambda ),
                Expression.Invoke( modifySubQueriesDelegateVar, subQueriesParam ) ),
            subQueriesParam,
            modifyCommandParam );
    }

    private static LambdaExpression CreateModifyQueryMethod()
    {
        var assembly = typeof( SqlProvider ).Assembly;

        var queryInfoType = assembly.GetType( "System.Data.Linq.SqlClient.SqlProvider" ).GetNestedType( "QueryInfo", BindingFlags.NonPublic );
        var sqlParameterInfoType = assembly.GetType( "System.Data.Linq.SqlClient.SqlParameterInfo" );
        var sqlParameterType = assembly.GetType( "System.Data.Linq.SqlClient.SqlParameter" );

        var queryInfoParam = Expression.Parameter( queryInfoType, "query" );
        var modifyCommandParam = Expression.Parameter( typeof( ModifyCommandDelegate ), "modifyCommand" );

        var commandTextField = queryInfoType.GetField( "commandText", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic );
        var parametersField = queryInfoType.GetField( "parameters", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic );

        var commandTextVar = Expression.Variable( typeof( string ), "commandText" );
        var parameterInfosVar = Expression.Variable( typeof( ReadOnlyCollection<> ).MakeGenericType( sqlParameterInfoType ), "parameterInfos" );
        var parametersVar = Expression.Variable( typeof( Dictionary<string, object> ), "parameters" );
        var indexVar = Expression.Variable( typeof( int ), "index" );
        var paramInfoVar = Expression.Variable( sqlParameterInfoType, "paramInfo" );
        var paramVar = Expression.Variable( sqlParameterType, "param" );
        var loopTarget = Expression.Label();

        return Expression.Lambda(
            Expression.IfThen(
                Expression.NotEqual( modifyCommandParam, Expression.Constant( null ) ),
                Expression.Block(
                    new[] { commandTextVar, parameterInfosVar, parametersVar, indexVar },
                    // var commandText = queryInfo.CommandText;
                    Expression.Assign( commandTextVar, Expression.MakeMemberAccess( queryInfoParam, commandTextField ) ),
                    // var parameterInfos = queryInfo.Parameters;
                    Expression.Assign( parameterInfosVar, Expression.MakeMemberAccess( queryInfoParam, parametersField ) ),
                    // var parameters = new Dictionary<string, object>();
                    Expression.Assign( parametersVar, Expression.New( typeof( Dictionary<string, object> ) ) ),
                    // var index = 0;
                    Expression.Assign( indexVar, Expression.Constant( 0 ) ),
                    Expression.Loop(
                        Expression.Block(
                            new[] { paramInfoVar, paramVar },
                            // if ( index >= parameterInfos.Count ) break;
                            Expression.IfThen(
                                Expression.GreaterThanOrEqual( indexVar, Expression.Property( parameterInfosVar, "Count" ) ),
                                Expression.Break( loopTarget ) ),
                            // var paramInfo = parameterInfos[ index ];
                            Expression.Assign( paramInfoVar, MakeMethodCall( parameterInfosVar, "get_Item", indexVar ) ),
                            // var param = paramInfo.Parameter;
                            Expression.Assign( paramVar, MakeMethodCall( paramInfoVar, "get_Parameter" ) ),
                            // parameters[ param.Name ] = paramInfo.Value;
                            MakeMethodCall( parametersVar, "set_Item", MakeMethodCall( paramVar, "get_Name" ), MakeMethodCall( paramInfoVar, "get_Value" ) ),
                            // ++ index;
                            Expression.PreIncrementAssign( indexVar ) ),
                        loopTarget ),
                    // queryInfo.CommandText = modifyCommand( commandText, parameters );
                    Expression.Assign( Expression.MakeMemberAccess( queryInfoParam, commandTextField ), Expression.Invoke( modifyCommandParam, commandTextVar, parametersVar ) ) ) ),
            queryInfoParam,
            modifyCommandParam );
    }

    internal interface IProviderProxy
    {
        DataContextInterceptor Interceptor { get; }
        object OldProvider { get; }
    }

    public class ProviderProxy : RealProxy, IRemotingTypeInfo, IProviderProxy
    {
        public DataContextInterceptor Interceptor { get; private set; }
        public object OldProvider { get; private set; }

        internal ProviderProxy( DataContextInterceptor extender, object oldProvider )
            : base( typeof( ContextBoundObject ) )
        {
            this.Interceptor = extender;
            this.OldProvider = oldProvider;
        }

        public override IMessage Invoke( IMessage msg )
        {
            var call = msg as IMethodCallMessage;

            if ( call != null && OldProvider != null )
            {
                try
                {
                    if ( call.MethodBase.DeclaringType.Name == "IProvider" && call.MethodBase.DeclaringType.IsInterface )
                    {
                        Interceptor.providerType = call.MethodBase.DeclaringType;

                        switch ( call.MethodName )
                        {
                            case "Compile": return new ReturnMessage( Interceptor.Compile( call.Args.Cast<Expression>().First() ), null, 0, null, call );
                            case "Execute": return new ReturnMessage( Interceptor.Execute( call.Args.Cast<Expression>().First() ), null, 0, null, call );
                        }
                    }

                    return new ReturnMessage( call.MethodBase.Invoke( OldProvider, call.Args ), null, 0, null, call );
                }
                catch ( TargetInvocationException e )
                {
                    return new ReturnMessage( e.InnerException, call );
                }
            }

            throw new NotImplementedException();
        }

        public bool CanCastTo( Type fromType, object o )
        {
            return true;
        }

        public string TypeName
        {
            get { return this.GetType().Name; }
            set { }
        }
    }
}

To use it, simply call DataContextInterceptor.Intercept on your DataContext before use (passing in a delegate to modify command text as needed):

DataContextInterceptor.Intercept( this, ( c, p ) => c.Replace( "[t0].[cName]", "NULL" ).Dump( "test") );

 

As it’s still calling private methods, you’ll need to be running in a full-trust environment.

Last year I posted this article about modifying LINQ to SQL (L2S) command text.  It was slightly evil in that it called private methods inside L2S, and it did it through reflection.

I have an alternative version that does the same thing through a pre-compiled expression tree, which I’ll post soon.  Until then, here’s an update to the original reflection-based interceptor that properly handles sub-queries:

    public delegate string ModifyCommandDelegate( string commandText, IDictionary<string, object> parameters );

    public class ReflectionDataContextInterceptor
    {
        private DataContext dc;
        private object oldProvider;
        private Type providerType;
        private ModifyCommandDelegate modifyCommand;

        public static TDataContext Intercept<TDataContext>( TDataContext dc, ModifyCommandDelegate modifyCommand )
            where TDataContext : DataContext
        {
            new ReflectionDataContextInterceptor( dc, modifyCommand );

            return dc;
        }

        public ReflectionDataContextInterceptor( DataContext dc, ModifyCommandDelegate modifyCommand )
        {
            this.dc = dc;
            this.modifyCommand = modifyCommand;

            FieldInfo providerField = typeof( DataContext ).GetField( "provider", BindingFlags.Instance | BindingFlags.NonPublic );
            var existingProvider = providerField.GetValue( dc );

            if ( existingProvider is IProviderProxy )
            {
//                System.Diagnostics.Trace.WriteLine( string.Format( "DataContext {0} already intercepted", dc.GetHashCode() ) );
            }
            else
            {
                oldProvider = existingProvider;

                var proxy = new ProviderProxy( this, oldProvider ).GetTransparentProxy();

                providerField.SetValue( dc, proxy );

//                System.Diagnostics.Trace.WriteLine( string.Format( "DataContext {0} intercepted", dc.GetHashCode() ) );
            }
        }

        public static MethodInfo GetMethod( Type type, string methodName, params object[] args )
        {
            var hasNullArgs = args.Any( a => a == null );

            var method = hasNullArgs
                ? type.GetMethods( BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic )
                    .FirstOrDefault( m => m.Name == methodName )
                : null;

            if ( method == null && !hasNullArgs )
            {
                method = type.GetMethod(
                    methodName,
                    BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
                    null,
                    args.Select( a => a.GetType() ).ToArray(),
                    null );
            }

            return method;
        }

        private object Invoke( object instance, string methodName, params object[] args )
        {
            var type = instance.GetType();
            var method = GetMethod( type, methodName, args );

            return ( method != null )
                ? method.Invoke( instance, args )
                : null;
        }

        private object Invoke( Type type, string methodName, params object[] args )
        {
            var method = type.GetMethod( methodName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic );

            return method.Invoke( null, args );
        }

        protected object CompileImpl( Expression query )
        {
            try
            {
                var assembly = typeof( SqlProvider ).Assembly;

                /*
                this.CheckDispose();
                this.CheckInitialized();
                if ( query == null )
                {
                    throw Error.ArgumentNull( "query" );
                }
                */

                // this.InitializeProviderMode();
                Invoke( oldProvider, "InitializeProviderMode" );

                // SqlNodeAnnotations annotations = new SqlNodeAnnotations();
                var annotations = Activator.CreateInstance( assembly.GetType( "System.Data.Linq.SqlClient.SqlNodeAnnotations" ) );

                // QueryInfo[] queries = this.BuildQuery( query, annotations );
                var queries = Invoke( oldProvider, "BuildQuery", query, annotations );

                var info = ModifyQueries( (IEnumerable)queries );

                // this.CheckSqlCompatibility( queries, annotations );
                Invoke( oldProvider, "CheckSqlCompatibility", queries, annotations );

                LambdaExpression expression = query as LambdaExpression;

                if ( expression != null )
                {
                    query = expression.Body;
                }

                // IObjectReaderFactory readerFactory = null;
                object readerFactory = null;

                // ICompiledSubQuery[] subQueries = null;
                object subQueries = null;

                // QueryInfo info = queries[ queries.Length - 1 ];
                // info defined above

                var resultShape = (int)Invoke( info, "get_ResultShape" );

                // if ( info.ResultShape == ResultShape.Singleton )
                if ( resultShape == 1 /* Singleton */ )
                {
                    // subQueries = this.CompileSubQueries( info.Query );
                    subQueries = Invoke( oldProvider, "CompileSubQueries", Invoke( info, "get_Query" ) );

                    ModifySubQueries( (IEnumerable)subQueries );

                    // readerFactory = this.GetReaderFactory( info.Query, info.ResultType );
                    readerFactory = Invoke( oldProvider, "GetReaderFactory", Invoke( info, "get_Query" ), Invoke( info, "get_ResultType" ) );
                }
                // else if ( info.ResultShape == ResultShape.Sequence )
                else if ( resultShape == 2 /* Sequence */ )
                {
                    // subQueries = this.CompileSubQueries( info.Query );
                    subQueries = Invoke( oldProvider, "CompileSubQueries", Invoke( info, "get_Query" ) );

                    ModifySubQueries( (IEnumerable)subQueries );

                    // readerFactory = this.GetReaderFactory( info.Query, TypeSystem.GetElementType( info.ResultType ) );
                    var resultType = Invoke( info, "get_ResultType" );
                    var typeSystemType = assembly.GetType( "System.Data.Linq.SqlClient.TypeSystem" );
                    var elementType = Invoke( typeSystemType, "GetElementType", resultType );

                    readerFactory = Invoke( oldProvider, "GetReaderFactory", Invoke( info, "get_Query" ), elementType );
                }

                FieldInfo providerField = typeof( DataContext ).GetField( "provider", BindingFlags.Instance | BindingFlags.NonPublic );
                providerField.SetValue( dc, oldProvider );

                //            System.Diagnostics.Trace.WriteLine( string.Format( "DataContext {0} interceptor released (compiled query)", dc.GetHashCode() ) );

                // return new CompiledQuery( this, query, queries, readerFactory, subQueries );
                var compiledQueryType = assembly.GetType( "System.Data.Linq.SqlClient.SqlProvider+CompiledQuery" );

                return Activator.CreateInstance(
                    compiledQueryType,
                    BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
                    null,
                    new object[] { oldProvider, query, queries, readerFactory, subQueries },
                    null );
            }
            catch ( TargetInvocationException ex )
            {
                throw ex.InnerException;
            }
        }

        protected internal virtual IExecuteResult ExecuteImpl( Expression query )
        {
            try
            {
                var assembly = typeof( SqlProvider ).Assembly;

                /*
                this.CheckDispose();
                this.CheckInitialized();
                this.CheckNotDeleted();
                if (query == null)
                {
                    throw Error.ArgumentNull("query");
                }
                */

                // this.InitializeProviderMode();
                Invoke( oldProvider, "InitializeProviderMode" );

                // query = Funcletizer.Funcletize(query);
                var funcletizerType = assembly.GetType( "System.Data.Linq.SqlClient.Funcletizer" );
                query = (Expression)Invoke( funcletizerType, "Funcletize", query );

                // if ( this.EnableCacheLookup )
                if ( (bool)Invoke( oldProvider, "get_EnableCacheLookup" ) )
                {
                    // IExecuteResult cachedResult = this.GetCachedResult(query);
                    object cachedResult = Invoke( oldProvider, "GetCachedResult", query );

                    if ( cachedResult != null )
                    {
                        // return cachedResult;
                        return (IExecuteResult)cachedResult;
                    }
                }

                // SqlNodeAnnotations annotations = new SqlNodeAnnotations();
                var annotations = Activator.CreateInstance( assembly.GetType( "System.Data.Linq.SqlClient.SqlNodeAnnotations" ) );

                // QueryInfo[] queries = this.BuildQuery(query, annotations);
                var queries = Invoke( oldProvider, "BuildQuery", query, annotations );

                var info = ModifyQueries( (IEnumerable)queries );

                // this.CheckSqlCompatibility(queries, annotations);
                Invoke( oldProvider, "CheckSqlCompatibility", queries, annotations );

                LambdaExpression expression = query as LambdaExpression;

                if ( expression != null )
                {
                    query = expression.Body;
                }

                // IObjectReaderFactory readerFactory = null;
                object readerFactory = null;

                // ICompiledSubQuery[] subQueries = null;
                object subQueries = null;

                // QueryInfo info = queries[queries.Length - 1];
                // info defined above

                var resultShape = (int)Invoke( info, "get_ResultShape" );

                // if (info.ResultShape == ResultShape.Singleton)
                if ( resultShape == 1 /* Singleton */ )
                {
                    // subQueries = this.CompileSubQueries(info.Query);
                    subQueries = Invoke( oldProvider, "CompileSubQueries", Invoke( info, "get_Query" ) );

                    ModifySubQueries( (IEnumerable)subQueries );

                    // readerFactory = this.GetReaderFactory(info.Query, info.ResultType);
                    readerFactory = Invoke( oldProvider, "GetReaderFactory", Invoke( info, "get_Query" ), Invoke( info, "get_ResultType" ) );
                }
                // else if (info.ResultShape == ResultShape.Sequence)
                else if ( resultShape == 2 /* Sequence */ )
                {
                    // subQueries = this.CompileSubQueries(info.Query);
                    subQueries = Invoke( oldProvider, "CompileSubQueries", Invoke( info, "get_Query" ) );

                    ModifySubQueries( (IEnumerable)subQueries );

                    // readerFactory = this.GetReaderFactory(info.Query, TypeSystem.GetElementType(info.ResultType));
                    var resultType = Invoke( info, "get_ResultType" );
                    var typeSystemType = assembly.GetType( "System.Data.Linq.SqlClient.TypeSystem" );
                    var elementType = Invoke( typeSystemType, "GetElementType", resultType );

                    readerFactory = Invoke( oldProvider, "GetReaderFactory", Invoke( info, "get_Query" ), elementType );
                }

                // return this.ExecuteAll(query, queries, readerFactory, null, subQueries);
                return (IExecuteResult)Invoke( oldProvider, "ExecuteAll", query, queries, readerFactory, null, subQueries );
            }
            catch ( TargetInvocationException ex )
            {
                throw ex.InnerException;
            }
        }

        private object ModifyQueries( IEnumerable queries )
        {
            object lastQuery = null;

            foreach ( var q in queries )
            {
                lastQuery = q;

                ModifyQuery( q );
            }

            return lastQuery;
        }

        private void ModifySubQueries( IEnumerable subQueries )
        {
            if ( subQueries == null ) return;

            foreach ( var sq in subQueries )
            {
                var queryInfoField = sq.GetType().GetField( "queryInfo", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic );
                var queryInfo = queryInfoField.GetValue( sq );
                ModifyQuery( queryInfo );

                var subQueriesField = sq.GetType().GetField( "subQueries", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic );
                var nestedSubQueries = subQueriesField.GetValue( sq );

                if ( nestedSubQueries != null ) ModifySubQueries( (IEnumerable)nestedSubQueries );
            }
        }

        private void ModifyQuery( object q /* QueryInfo */ )
        {
            var commandTextField = q.GetType().GetField( "commandText", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic );
            var parametersField = q.GetType().GetField( "parameters", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic );

            var commandText = (string)commandTextField.GetValue( q );
            var parameterInfos = parametersField.GetValue( q );
            var parameters = new Dictionary<string, object>();

            foreach ( var p in (IEnumerable)parameterInfos )
            {
                var param = Invoke( p, "get_Parameter" );
                var name = (string)Invoke( param, "get_Name" );

                parameters[ name ] = Invoke( p, "get_Value" );
            }

            var modifiedCommandText = modifyCommand( commandText, parameters );

//            System.Diagnostics.Trace.WriteLine( modifiedCommandText + "\n---" );

            commandTextField.SetValue( q, modifiedCommandText );
        }
/*
        protected internal DbCommand GetCommandImpl( Expression query )
        {
            return (DbCommand)Invoke( oldProvider, "GetCommand", query );
        }

        protected internal string GetQueryTextImpl( Expression query )
        {
            return (string)Invoke( oldProvider, "GetQueryText", query );
        }
*/
        internal interface IProviderProxy
        {
            ReflectionDataContextInterceptor Interceptor { get; }
            object OldProvider { get; }
        }

        public class ProviderProxy : RealProxy, IRemotingTypeInfo, IProviderProxy
        {
            public ReflectionDataContextInterceptor Interceptor { get; private set; }
            public object OldProvider { get; private set; }

            internal ProviderProxy( ReflectionDataContextInterceptor extender, object oldProvider )
                : base( typeof( ContextBoundObject ) )
            {
                this.Interceptor = extender;
                this.OldProvider = oldProvider;
            }

            public override IMessage Invoke( IMessage msg )
            {
                if ( msg is IMethodCallMessage )
                {
                    IMethodCallMessage call = (IMethodCallMessage)msg;
                    MethodInfo mi = null;

                    if ( call.MethodBase.DeclaringType.Name == "IProvider" && call.MethodBase.DeclaringType.IsInterface )
                    {
                        Interceptor.providerType = call.MethodBase.DeclaringType;

                        mi = ReflectionDataContextInterceptor.GetMethod( typeof( ReflectionDataContextInterceptor ), call.MethodBase.Name + "Impl", call.Args );

                        if ( mi == null && OldProvider != null )
                        {
                            mi = ReflectionDataContextInterceptor.GetMethod( call.MethodBase.DeclaringType, call.MethodBase.Name, call.Args );

                            try
                            {
                                return new ReturnMessage( mi.Invoke( OldProvider, call.Args ), null, 0, null, call );
                            }
                            catch ( TargetInvocationException e )
                            {
                                return new ReturnMessage( e.InnerException, call );
                            }
                        }

                        if ( mi != null )
                        {
                            try
                            {
                                return new ReturnMessage( mi.Invoke( this.Interceptor, call.Args ), null, 0, null, call );
                            }
                            catch ( TargetInvocationException e )
                            {
                                return new ReturnMessage( e.InnerException, call );
                            }
                        }
                    }
//                    else mi = typeof( ReflectionDataContextInterceptor ).GetMethod( call.MethodBase.Name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic );
                    else
                    {
                        mi = ReflectionDataContextInterceptor.GetMethod( OldProvider.GetType(), call.MethodBase.Name, call.Args );

                        if ( mi != null )
                        {
                            try
                            {
                                return new ReturnMessage( mi.Invoke( OldProvider, call.Args ), null, 0, null, call );
                            }
                            catch ( TargetInvocationException e )
                            {
                                return new ReturnMessage( e.InnerException, call );
                            }
                        }
                    }

                    throw new NotImplementedException(
                        string.Format( "Method not found: {0}( {1} )",
                            call.MethodBase.Name,
                            string.Join( ", ", call.Args.Select( a => Convert.ToString( a ) ) ) ) );
                }

                throw new NotImplementedException();
            }

            public bool CanCastTo( Type fromType, object o )
            {
                return true;
            }

            public string TypeName
            {
                get { return this.GetType().Name; }
                set { }
            }
        }
    }

It’s still evil, but works great Smile

Writing a recursive method in C# is easy, but it’s not immediately obvious how to do the same in a LINQ expression tree.

Here’s the usual factorial example:

public uint Factorial( uint n )
{
    return ( n <= 1 ) ? 1 : n * Factorial( n - 1 );
}

To represent this in an expression tree, just assign the method to a local variable and call it.  Here’s the equivalent C#:

Func<uint, uint> factorial;
factorial = n => ( n <= 1 ) ? 1 : n * factorial( n - 1 );

Here’s how you might build it:

public Expression<Func<T, T>> MakeFactorialExpression<T>()
{
    var nParam = Expression.Parameter( typeof( T ), "n" );
    var methodVar = Expression.Variable( typeof( Func<T, T> ), "factorial" );
    var one = Expression.Convert( Expression.Constant( 1 ), typeof( T ) );

    return Expression.Lambda<Func<T, T>>(
        Expression.Block(
            // Func<uint, uint> method;
            new[] { methodVar },
            // method = n => ( n <= 1 ) ? 1 : n * method( n - 1 );
            Expression.Assign(
                methodVar,
                Expression.Lambda<Func<T, T>>(
                    Expression.Condition(
                        // ( n <= 1 )
                        Expression.LessThanOrEqual( nParam, one ),
                        // 1
                        one,
                        // n * method( n - 1 )
                        Expression.Multiply(
                            // n
                            nParam,
                            // method( n - 1 )
                            Expression.Invoke(
                                methodVar,
                                Expression.Subtract( nParam, one ) ) ) ),
                    nParam ) ),
            // return method( n );
            Expression.Invoke( methodVar, nParam ) ),
        nParam );            
}

This just returns an Expression<Func<T, T>>, which you could compile and call like this:

var factorial = MakeFactorialExpression<uint>().Compile();
var result = factorial( 5 );

Since posting the WPF 4 version of my Awesomium (Chromium-based) browser wrapper, Awesomium introduced its own wrapper.  I assumed everyone would just use the new wrapper and everything would be fine.

Turns out it wasn’t quite that simple, so I’ve updated my WPF 4.0 Chromium browser demo to use the new Awesomium wrapper.  You can get the project here (requires VS2010).

It’s setup assuming you’ve installed the Awesomium SDK to the default location.

Way back in 2009, the MVC Futures assembly introduced “strongly-typed action helpers”.  They allowed you to render links using lambda expressions pointing directly to the actions on your controller, rather than using strings:

<%= Html.ActionLink<HomeController>( c => c.Index() ) %>

Other benefits included:

  • Intellisense and refactoring in Visual Studio
  • Compile-time validation
  • Check if user is authorized for an action (automatically hide or disable links)

Unfortunately there was a performance overhead associated with all this (http://haacked.com/archive/2009/06/02/alternative-to-expressions.aspx) that led to some interesting alternatives, including the T4MVC project.

However, there’s still hope for strongly-typed helpers.  You can find an alternative implementation here that aims to resolve most of the performance concerns.

The main classes of interest are:

  • ActionTemplate – Captures controller and action name from action expression.  Defers getting “baked in” parameter values until (and if) needed.
  • ActionBuilder – Created from ActionTemplate, captures optional parameter values.  Renders links and forms (calls HtmlHelper.ActionLink or BeginForm).

Previously, most of the performance hit was from compiling the argument expressions. This version tries a few alternative approaches first, like extracting the argument value directly if it’s a ConstantExpression, and allowing named parameters in the ActionBuilder instance (if it finds a value in the parameters collection, it doesn’t bother looking at the argument part of the action expression).

Some of the features demonstrated (in HomeController.cs) include:

  • Using ActionBuilders as properties of the view model.  The controller defines the target actions, and the view just renders them as links.
  • Using a static ActionTemplate instance to minimize the work needed to generate a list of parameterized ActionBuilders.  Note the template expression simply passes ‘null’ as the argument; in this case it’ll never get used as the ActionBuilders are given their own parameter values.
  • A dummy authentication mechanism to demonstrate automatically disabled links (log in with password “password” to enable the actions marked with the Authorize attribute).

You can get all the source code here Smile

A great thing about .NET Expression Trees is you can compile them at runtime, and they’ll execute as fast as any .NET code.  However, while building and compiling a tree is pretty quick, it’s best if you only need to do it once.

The way you’d usually do that is cache the compiled delegate somewhere.  If you just want to create it and use it repeatedly in the same code block, just hold the delegate in a local variable:

void Main()
{
    var dump = MakeDumpDelegate<DateTime>();

    for ( var yearOffset = 0; yearOffset < 5; ++ yearOffset )
    {
        Console.WriteLine( string.Join( " | ", dump( DateTime.Now.AddYears( yearOffset ) ) ) );
    }
}

Func<TModel, object[]> MakeDumpDelegate<TModel>()
{
    var modelParam = Expression.Parameter( typeof( TModel ), "model" );
    var properties = typeof( TModel ).GetProperties( BindingFlags.Public | BindingFlags.Instance ).OrderBy( p => p.Name );

    var lambda = Expression.Lambda<Func<TModel, object[]>>(
        Expression.NewArrayInit(
            typeof( object ),
            from p in properties
            select Expression.Convert( Expression.MakeMemberAccess( modelParam, p ), typeof( object ) ) ),
        modelParam );

    return lambda.Compile();
}

Here MakeDumpDelegate creates a delegate that takes an object of type TModel, and returns an object array of its alphabetically sorted property values.  In the Main method we’re creating a delegate based on the DateTime type, running it 5 times and spitting out the values.

What if there are a bunch of classes we want to run delegates for?  We could move the code out of Main and call it wherever we need it:

void Main()
{
    var dates = Enumerable.Range( 0, 5 ).Select( yo => DateTime.Now.AddYears( yo ) );

    foreach ( var d in DumpProperties( dates ) )
    {
        Console.WriteLine( d );
    }
}

IEnumerable<string> DumpProperties<TModel>( IEnumerable<TModel> models )
{
    var dump = MakeDumpDelegate<TModel>();

    return from m in models select string.Join( " | ", dump( m ) );
}

This works great, and we can call DumpProperties with any collection we like.  The problem is, we’re building and compiling the expression tree every time we use it.  This is pretty inefficient, so let’s add a cache of delegates for each type.

But wait…  Let’s say we want to hit these things from multiple threads.  We’re probably going to need a static Dictionary<Type, delegate> to hold onto them, and we’ll need to provide some locking to stop the threads messing us up.  This is too complicated to be good.

Instead, we’ll use a generic class and let .NET help us out.  When you declare a generic class, each specialization of that class gets its own copy of its static members:

class MyClass<T>
{
    public static T MyStaticValue { get; set; }
}

So if you access MyClass<int>.MyStaticValue or MyClass<double>.MyStaticValue, they’re obviously different instances (one is int and the other double).  We can use this to hold onto our delegate:

void Main()
{
    var dates = Enumerable.Range( 0, 5 ).Select( yo => DateTime.Now.AddYears( yo ) );

    foreach ( var d in DumpProperties( dates ) )
    {
        Console.WriteLine( d );
    }
}

IEnumerable<string> DumpProperties<TModel>( IEnumerable<TModel> models )
{
    var dump = DumpDelegate<TModel>.Dump;

    return from m in models select string.Join( " | ", dump( m ) );
}

class DumpDelegate<TModel>
{
    public static Func<TModel, object[]> Dump { get; private set; }

    static DumpDelegate()
    {
        var modelParam = Expression.Parameter( typeof( TModel ), "model" );
        var properties = typeof( TModel ).GetProperties( BindingFlags.Public | BindingFlags.Instance ).OrderBy( p => p.Name );
    
        var lambda = Expression.Lambda<Func<TModel, object[]>>(
            Expression.NewArrayInit(
                typeof( object ),
                from p in properties
                select Expression.Convert( Expression.MakeMemberAccess( modelParam, p ), typeof( object ) ) ),
            modelParam );

        Dump = lambda.Compile();
    }
}

What’s great about this is .NET guarantees the static constructor will only be called once, and in a completely thread-safe manner.  Since DumpDelegate is a generic class based on TModel, each specialization will only be initialized once – and only on first use.  Our threading headache has just disappeared, and we’re building and compiling the tree just once for each model type Smile

kick it on DotNetKicks.com

Live in Minnesota?  http://tccc12.eventbrite.com Smile

On the second Thursday of the month at 5pm, the Rochester, MN .NET User Group meets at Mann Hall.  You’re very welcome to join us if you’re in the area.

We often have someone interesting presenting something exciting, but occasionally that doesn’t work out and I’ll present something instead Winking smile  This is where the LOLCode Parser was born…

First you should try it out here.  It’ll ask you to write some LOLCode and optionally give it some inputs to pass in.  Let’s start with “Hai World”…

HAI
CAN HAS STDIO?
VISIBLE "HAI WORLD!" N " I SLEEPIN"
KTHXBYE

Paste that and click the “PLZ RUN THE CODE 4 ME” button.  This demonstrates the VISIBLE statement and the N concatenation operator.

Let’s step it up a bit and add some conditions (to see this work its magic, you’ll need an input value in the second textbox):

HAI
CAN HAS STDIO?
I HAS A VAR
VISIBLE "GIMMEH INPUTZ!..."
GIMMEH VAR
VISIBLE "U SEZ " N VAR
IZ VAR BIGR THAN 10?
    YARLY
        BTW this is true
        VISIBLE VAR N " IZ BIG NUMBER!"
    NOWAI
        BTW this is false
        VISIBLE VAR N " IZ LITTLE NUMBER!"
KTHX
VISIBLE "I IZ DONE"
KTHXBYE

Here we’ve added some user input (GIMMEH VAR will read a line from the input source to the VAR variable). The IZ..YARLY..NOWAI..KTHX construct lets us specify a condition based on its value. The BTW statement begins a comment.

Cats won’t usually chase their tails like dogs will, but they use loops to remind their owners it’s feeding time. This one just wants to count to 10 between meows:

HAI
CAN HAS STDIO?
VISIBLE "HAI WORLD!"
I HAS A VAR
IM IN YR KITCHN
    UPZ VAR!!1
    VISIBLE VAR
    IZ VAR BIGR THAN 9? GTFO. KTHX
KTHX
VISIBLE "FEED MEOW!"
KTHXBYE

If you’re thinking this isn’t very impressive, you’re right.  Still, it does have some cool points:

  • It’s using a parser generated by TinyPG (awesome tool on CodeProject by Herre Kuijpers).
  • The LOLCode is being parsed into a LINQ expression tree (similar to this old post, but no DLR and much simpler).
  • Because it’s an expression tree, it can be compiled and cached on the server.  This should run as fast as C#, so your cat will be happy.  For this demo, if you don’t change the code between requests it’ll just run the cached, pre-compiled code.  Anything in the Inputs box is passed as a parameter.
  • This isn’t just an expression returning a value; it’s building a full LOLCode program (with conditions, loops etc).
  • It allows your program to run for up to two seconds; if it overstays its welcome, it will be brutally terminated (putting your cat in an infinite loop is animal cruelty).

You’re unlikely to want LOLCode in your own apps, but some of the ideas could be useful.  For example, you could use it to provide a custom scripting language for clients to automate aspects of your apps.

You can find the source code here.  It’s pretty easy to use TinyPG to define your own parser (see the Cjc.LOLCode.tpg file for sample syntax), then extend the ParseTreeVisitor / Visitor classes to build an expression tree.

Finally, here’s some bonus LOLCode:

HAI
CAN HAS STDIO?
VISIBLE "HAI WORLD!"
I HAS A VAR
I HAS A INPUTZ
IM IN YR KITCHN
    UPZ VAR!!1
    GIMMEH INPUTZ
    VISIBLE VAR N ", " N INPUTZ
    IZ VAR BIGR THAN 9? GTFO. KTHX
KTHX
VISIBLE "FEED MEOW!"
KTHXBYE

And some inputs to go with it:

10
9
8
7
6
5
4
3
2
1

You might also want to take a look at a LOLQL parser (source here); it allows your cat to query a database through Entity Framework!

DotNetKicks Image

Apps

My Cashflow - The easy way to forecast your bank balances.

My Cashflow - for iPhone, iPod Touch and iPad