Click here to Skip to main content
15,886,258 members
Articles / Programming Languages / SQL

Projecting on Linq to SQL, From Different Layers

Rate me:
Please Sign up or sign in to vote.
4.90/5 (6 votes)
20 Oct 2015CPOL4 min read 7.5K   6  
Projecting on Linq to SQL, from different layers

Compiled Code

The Need

I needed to add the possibility for upper layers like the Model/Service to make projection on Linq to SQL statements, but without leaking IQueryable.

As the normal Linq to SQL projection is:

C#
var query = from c in Cars

                    Select c.carNo, c.eName

can only be done in the layers that have the "Cars" as IQueryable, and once you return it as an IList or IEnumerable, then any projection on it will be Linq to Entities, so no projection is going to be sent to the database.

Download code from here:

After de-compressing, attach the supplied database to your SQL server instance, and you might need to change the connection string “name="PIModelConnection"” in the zClient\App.config file.

Benefits

The main purpose is:

  • To speed up the results coming from SQL server.
  • Lessen the size of the data if they are going to be passed through wire.
  • Lessening/Or removing the need for making several DTOs for each Entity, aka table, in the domain, by sending the request for only the required fields, and returning the full domain entities to the model, with only those fields are filled.
  • One procedure for each functionality that returns Entities, but with different projections, instead of multiple procedures for each projection.

but because anonymous classes can only exist in the function/class they are created, it poses some difficulties, because the layer that wants to decide the number of fields is different than the layer that has the DAL IQueryable EF entities. so I needed a way to send the required fields from the upper layers to the DAL IQueryable EF layers.

So after a lot of trial and error, I moved to DLR compilation, by sending the required fields list to code snipt string, that will get compiled, then call the compiled function to project on any entity.

Trials But No Success

I tried to make expressions, and lambdas, to pass the projection list to the Linq select statement, but I couldn't succeed, e.g.:

C#
public Expression<Func<TEntity, IQueryable>> ProjectionExpression

public IUQueryConstraints Projection
	(Expression<Func<TEntity, IQueryable>> projection_)
public Expression<Func> ProjectionExpression2
 
public IEnumerable Select
(IQueryable source, Expression<Func<TEntity, TResult>> selector = null)
{
   return source.Select(selector);
}
public void Select2( Expression<Func<TEntity, TResult>> selector = null)
{
   //
}

If you know how to pass lamdas or expressions from another layer to the DAL layer and have the Linq to SQL work with it, then please send me your solution. :)

Compiled Code Speed

In the code, there is some caching for the compiled methods, so they can be reused again if the same projection and the same Entity is passed, but even without it, it's pretty fast.

You can see the speed comparison between direct call of a function vs runtime compilation at the below link:

and better results at the link below:

but maybe caching the call could have speeded up the compilation even more.

Solution

The sample solution has several projects:

Solution

With this Data movement:

Solution Full Picture

Compiling and starting it will run and show one window:

Car Projection

with three buttons:

  • List Without Projection: List most of the POCO Entity fields, so the SQL statement sent to the DB, has all fields.
  • List With Projection in Compile Time: Projects in compile time, on some fields, so the SQL statement sent to the DB, has only required fields, but the problem is that you must define a function for each projection, and only in the layers that have reference to the EntityFramework like the Repository.
  • List With Projection in Runtime: Projects in compile time, on some fields, so the SQL statement sent to the DB, has only required fields, and with only one function.

The main functionality is in the "LinqProjectionC.LinqProjection" class:

Which has a predefined code string, that will  have its Linq select columns change depending on the passed fields, it will compile the code, run it, and return a List of the same passed type.

C#
public class LinqProjection
{
    static string codeIEnumerable = @"
    using System;
    using System.Collections.Generic;
    using System.Linq;

    namespace UserClasses
    {
        public class RuntTimeClass
        {
            public static IEnumerable<%TypeName%> anyFunction(IQueryable<%TypeName%> query)
            {
                var enumerable = query.Select(c => new { %fieldsList% }).ToList();

                var queryEntities = from c in enumerable
                                        select new %TypeName%()
                                        {
                                            %fieldsList%
                                        };

                return queryEntities.ToList();
            }
        }
    }";

    public static IEnumerable projectOn<T, TCaller>(IQueryable query, String fieldsList_)
        where T : class
        where TCaller : class
    {

        var typeClass = typeof(T);
        var typeName = typeClass.FullName;
        Assembly assembly = typeClass.Assembly;
        Assembly callerAssembly = typeof(TCaller).Assembly;

        MethodInfo function = CreateFunction(fieldsList_, assembly, callerAssembly, typeName);
        var delegateFunction = (Func<IQueryable, IEnumerable>)
        Delegate.CreateDelegate(typeof(Func<IQueryable, IEnumerable>), function);

        IEnumerable result;
        result = delegateFunction(query);
        return result;
    }

    public static MethodInfo CreateFunction
    (string fieldsList_, Assembly assembly_, Assembly callerAssembly_, string typeName_)
    {
        string typeWithFields = string.Format("{0}:{1}", typeName_, fieldsList_);

        MethodInfo method = FunctionList.Singleton.getFunction(typeWithFields);

        if (method != null) return method;


        var fieldsQuery = from field in fieldsList_.Split(",".ToCharArray())
                            select ", " + field.Trim() + " = c." + field.Trim();

        StringBuilder sbFieldsList = new StringBuilder();
        foreach (string  field in fieldsQuery)
        {
            sbFieldsList.Append(field);
        }

        string finalCode = null;
        finalCode = codeIEnumerable;

        finalCode = finalCode.Replace("%TypeName%", typeName_);
        finalCode = finalCode.Replace
            ("%fieldsList%", sbFieldsList.ToString().Substring(1));

        CSharpCodeProvider provider = new CSharpCodeProvider();
        CompilerParameters compilerParams = new CompilerParameters
        {
            GenerateInMemory = true,
            GenerateExecutable = false
        };

        SortedSet refsList = new SortedSet();

        //This will traverse the passed type assembly and
        //add its reference assemblies to the "compilerParams"
        AssemblyReferences.Singleton.addToList(assembly_);
        AssemblyReferences.Singleton.addToList(callerAssembly_);
        AssemblyReferences.Singleton.addReferencesToParams(compilerParams);

        CompilerResults results =
            provider.CompileAssemblyFromSource(compilerParams, finalCode);

        if (results.Errors.HasErrors)
        {
            StringBuilder sb = new StringBuilder();

            foreach (CompilerError error in results.Errors)
            {
                sb.AppendLine(String.Format
                ("Error ({0}): {1}", error.ErrorNumber, error.ErrorText));
            }

            throw new InvalidOperationException(sb.ToString());
        }

        Type binaryFunction =
            results.CompiledAssembly.GetType("UserClasses.RuntTimeClass");

        var newMethod = binaryFunction.GetMethod("anyFunction");

        FunctionList.Singleton.addFunction(typeWithFields, newMethod);

        return newMethod;
    }
}

The following function will call the creation of the compiled code, and then saves the returned MethodInfo in a cache, so if it gets called again with the same fields and type, it will use the cached MethodInfo.

C#
public static IEnumerable projectOn<T, TCaller>(IQueryable query, String fieldsList_)
    where T : class
    where TCaller : class
{
    var typeClass = typeof(T);
    var typeName = typeClass.FullName;
    Assembly assembly = typeClass.Assembly;
    Assembly callerAssembly = typeof(TCaller).Assembly;

    MethodInfo function = CreateFunction(fieldsList_, assembly, callerAssembly, typeName);
    var delegateFunction = (Func<IQueryable,
    IEnumerable>)Delegate.CreateDelegate(typeof(Func<IQueryable, IEnumerable>), function);

    IEnumerable result;
    result = delegateFunction(query);
    return result;
}

Concerns

If anyone has a better, simpler, faster idea, I will appreciate it, if he can write it in the comments. :)

History

  • v1.3 Optimized the references cache, added more searching for references, better graphics
  • v1.2 Added section "Trials but no success" + "Concerns"
  • V1.1 Some minor styling and headings for more clarity
  • V1.0 Creation of the article

Filed under: Development, Technical Tagged: Linq Projection, Linq to SQL, Runtime compilation Image 5 Image 6

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Dev
United Arab Emirates United Arab Emirates
Hussain Naji Al-Safafeer

Work: Software Developer (Assistant manager)
Like: Programming, reading.
Technical site: https://readerman1.wordpress.com

Comments and Discussions

 
-- There are no messages in this forum --