Click here to Skip to main content
15,885,365 members
Articles / Web Development / HTML

Signum Framework Tutorials Part 2 – Southwind Logic

Rate me:
Please Sign up or sign in to vote.
4.45/5 (6 votes)
15 Nov 2012LGPL325 min read 31.3K   1K   22  
In this part, we will focus on writing business logic, LINQ queries and explain inheritance
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Diagnostics;
using Signum.Utilities;
using Signum.Utilities.ExpressionTrees;
using Signum.Entities;
using Signum.Utilities.DataStructures;
using Signum.Utilities.Reflection;
using Signum.Engine.Maps;
using Signum.Entities.Reflection;
using Signum.Engine.Properties;
using System.Collections.ObjectModel;
using Signum.Engine.DynamicQuery;
using Signum.Entities.DynamicQuery;

namespace Signum.Engine.Linq
{
    /// <summary>
    /// QueryBinder is a visitor that converts method calls to LINQ operations into 
    /// custom DbExpression nodes and references to class members into references to columns
    /// </summary>
    internal class MetadataVisitor : SimpleExpressionVisitor
    {
        Dictionary<ParameterExpression, Expression> map = new Dictionary<ParameterExpression, Expression>();

        private MetadataVisitor() { }

        static internal Dictionary<string, Meta> GatherMetadata(Expression expression)
        {
            if (expression == null)
                throw new ArgumentException("expression");

            if (!typeof(IQueryable).IsAssignableFrom(expression.Type))
                throw new InvalidOperationException("Expression type is not IQueryable");

            Expression simplified = MetaEvaluator.Clean(expression);

            MetaProjectorExpression meta = new MetadataVisitor().Visit(simplified) as MetaProjectorExpression;

            if (meta == null)
                return null;

            var proj = meta.Projector;

            if (proj.NodeType != ExpressionType.New &&  //anonymous types
                proj.NodeType != ExpressionType.MemberInit && // not-anonymous type
                !(proj.NodeType == (ExpressionType)MetaExpressionType.MetaExpression && ((MetaExpression)proj).IsEntity)) // raw-entity!
                return null;

            PropertyInfo[] props = proj.Type.GetProperties(BindingFlags.Public | BindingFlags.Instance);

            return props.ToDictionary(pi => pi.Name, pi =>
            {
                Expression ex = BindMember(proj, pi, pi.PropertyType);
                return (ex as MetaExpression).TryCC(c => c.Meta);
            });
        }

        internal static Expression JustVisit(LambdaExpression expression, Type type)
        {
            var cleaned = MetaEvaluator.Clean(expression);

            var replaced = ExpressionReplacer.Replace(Expression.Invoke(cleaned, new MetaExpression(type, null)));

            return new MetadataVisitor().Visit(replaced);
        }

        static MetaExpression MakeCleanMeta(Type type, Expression expression)
        {
            MetaExpression meta = expression as MetaExpression;

            return new MetaExpression(type, meta.Meta);
        }

        static MetaExpression MakeDirtyMeta(Type type, params Expression[] expression)
        {
            var metas = expression.OfType<MetaExpression>().Select(a => a.Meta).NotNull().ToArray();

            return new MetaExpression(type, new DirtyMeta(metas));
        }

        static internal Expression MakeVoidMeta(Type type)
        {
            return new MetaExpression(type, new DirtyMeta(new Meta[0]));
        }

        protected override Expression VisitMethodCall(MethodCallExpression m)
        {
            if (m.Method.DeclaringType == typeof(Queryable) ||
                m.Method.DeclaringType == typeof(Enumerable))
            {
                switch (m.Method.Name)
                {
                    case "Where":
                        return this.BindWhere(m.Type, m.GetArgument("source"), m.GetArgument("predicate").StripQuotes());
                    case "Select":
                        return this.BindSelect(m.Type, m.GetArgument("source"), m.GetArgument("selector").StripQuotes());
                    case "SelectMany":
                        if (m.Arguments.Count == 2)
                            return this.BindSelectMany(m.Type, m.GetArgument("source"), m.GetArgument("selector").StripQuotes(), null);
                        else
                            return this.BindSelectMany(m.Type, m.GetArgument("source"), m.GetArgument("collectionSelector").StripQuotes(), m.TryGetArgument("resultSelector").StripQuotes());
                    case "Join":
                        return this.BindJoin(
                            m.Type, m.GetArgument("outer"), m.GetArgument("inner"),
                            m.GetArgument("outerKeySelector").StripQuotes(),
                            m.GetArgument("innerKeySelector").StripQuotes(),
                            m.GetArgument("resultSelector").StripQuotes());
                    case "OrderBy":
                        return this.BindOrderBy(m.Type, m.GetArgument("source"), m.GetArgument("keySelector").StripQuotes(), OrderType.Ascending);
                    case "OrderByDescending":
                        return this.BindOrderBy(m.Type, m.GetArgument("source"), m.GetArgument("keySelector").StripQuotes(), OrderType.Descending);
                    case "ThenBy":
                        return this.BindThenBy(m.GetArgument("source"), m.GetArgument("keySelector").StripQuotes(), OrderType.Ascending);
                    case "ThenByDescending":
                        return this.BindThenBy(m.GetArgument("source"), m.GetArgument("keySelector").StripQuotes(), OrderType.Descending);
                    case "GroupBy":
                        return this.BindGroupBy(m.Type, m.GetArgument("source"),
                            m.GetArgument("keySelector").StripQuotes(),
                            m.GetArgument("elementSelector").StripQuotes());
                    case "Count":
                        return this.BindCount(m.Type, m.GetArgument("source"));
                    case "DefaultIfEmpty":
                       return Visit(m.GetArgument("source"));
                    case "Any":
                        return this.BindAny(m.Type, m.GetArgument("source"));
                    case "All":
                        return this.BindAll(m.Type, m.GetArgument("source"), m.GetArgument("predicate").StripQuotes());
                    case "Contains":
                        return this.BindContains(m.Type, m.GetArgument("source"), m.TryGetArgument("item") ?? m.GetArgument("value"));
                    case "Sum":
                    case "Min":
                    case "Max":
                    case "Average":
                        return this.BindAggregate(m.Type, m.Method.Name.ToEnum<AggregateFunction>(),
                            m.GetArgument("source"), m.TryGetArgument("selector").StripQuotes());
                    case "First":
                    case "FirstOrDefault":
                    case "Single":
                    case "SingleOrDefault":
                        return BindUniqueRow(m.Type, m.Method.Name.ToEnum<UniqueFunction>(),
                            m.GetArgument("source"), m.TryGetArgument("predicate").StripQuotes());
                    case "Distinct":
                        return BindDistinct(m.Type, m.GetArgument("source"));
                    case "Take":
                        return BindTake(m.Type, m.GetArgument("source"), m.GetArgument("count"));
                    case "Skip":
                        return BindSkip(m.Type, m.GetArgument("source"), m.GetArgument("count"));
                }
            }


            if (m.Method.DeclaringType == typeof(LiteUtils) && m.Method.Name == "ToLite")
                return MakeCleanMeta(m.Type, Visit(m.Arguments[0]));

            if (m.Method.DeclaringType == typeof(Math) &&
               (m.Method.Name == "Abs" ||
                m.Method.Name == "Ceiling" ||
                m.Method.Name == "Floor" ||
                m.Method.Name == "Round" ||
                m.Method.Name == "Truncate"))
                return MakeCleanMeta(m.Type, Visit(m.Arguments[0]));

            if (m.Object != null)
            {
                var a = this.Visit(m.Object);
                var list = this.VisitExpressionList(m.Arguments);
                return MakeDirtyMeta(m.Type, list.PreAnd(a).ToArray());
            }
            else
            {
                var list = this.VisitExpressionList(m.Arguments);
                return MakeDirtyMeta(m.Type, list.ToArray());
            }
        }

        private Expression MapAndVisit(LambdaExpression lambda, params MetaProjectorExpression[] projs)
        {
            map.SetRange(lambda.Parameters, projs.Select(a => a.Projector));
            var result =  Visit(lambda.Body);
            map.RemoveRange(lambda.Parameters);
            return result;
        }

        private MetaProjectorExpression AsProjection(Expression expression)
        {
            MetaProjectorExpression mpe = expression as MetaProjectorExpression;
            if (mpe != null)
                return (MetaProjectorExpression)mpe;

            if (expression.NodeType == ExpressionType.New)
            {
                NewExpression nex = (NewExpression)expression;
                if (nex.Type.IsInstantiationOf(typeof(Grouping<,>)))
                    return (MetaProjectorExpression)nex.Arguments[1]; 
            }

            Type elementType = expression.Type.ElementType();
            if (elementType != null)
            {
                MetaExpression meta = expression as MetaExpression;
                if (meta != null && meta.Meta is CleanMeta)
                {
                    PropertyRoute route = ((CleanMeta)meta.Meta).PropertyRoutes.Single("Metas doesn't work over polymorphic MLists").Add("Item");

                    return new MetaProjectorExpression(expression.Type,
                        new MetaExpression(elementType,
                            new CleanMeta(new[] { route })));
                }

                return new MetaProjectorExpression(expression.Type,
                     MakeVoidMeta(elementType)); 
            }

            throw new InvalidOperationException(); 
        }

        private Expression BindTake(Type resultType, Expression source, Expression count)
        {
            return AsProjection(Visit(source));
        }

        private Expression BindSkip(Type resultType, Expression source, Expression count)
        {
            return AsProjection(Visit(source));
        }

        private Expression BindUniqueRow(Type resultType, UniqueFunction function, Expression source, LambdaExpression predicate)
        {
            return AsProjection(Visit(source)).Projector;
        }

        private Expression BindDistinct(Type resultType, Expression source)
        {
            return AsProjection(Visit(source));
        }

        private Expression BindCount(Type resultType, Expression source)
        {
            return MakeVoidMeta(resultType);
        }

        private Expression BindAll(Type resultType, Expression source, LambdaExpression predicate)
        {
            return MakeVoidMeta(resultType);
        }

        private Expression BindAny(Type resultType, Expression source)
        {
            return MakeVoidMeta(resultType);
        }

        private Expression BindContains(Type resultType, Expression source, Expression item)
        {
            return MakeVoidMeta(resultType);
        }

        private Expression BindAggregate(Type resultType, AggregateFunction aggregateFunction, Expression source, LambdaExpression selector)
        {
            MetaProjectorExpression mp = AsProjection(Visit(source));
            if (selector == null)
                return mp.Projector;

            Expression projector = MapAndVisit(selector, mp);
            return projector; 
        }

        private Expression BindWhere(Type resultType, Expression source, LambdaExpression predicate)
        {
            return AsProjection(Visit(source)); 
        }

        private Expression BindSelect(Type resultType, Expression source, LambdaExpression selector)
        {
            MetaProjectorExpression mp = AsProjection(Visit(source));
            Expression projector = MapAndVisit(selector, mp);
            return new MetaProjectorExpression(resultType, projector); 
        }

        protected virtual Expression BindSelectMany(Type resultType, Expression source, LambdaExpression collectionSelector, LambdaExpression resultSelector)
        {
            MetaProjectorExpression mp = AsProjection(Visit(source));
            MetaProjectorExpression collectionProjector = AsProjection(MapAndVisit(collectionSelector, mp));

            if (resultSelector == null)
                return collectionProjector; 

            Expression resultProjection = MapAndVisit(resultSelector, mp, collectionProjector);
            return new MetaProjectorExpression(resultType, resultProjection); 
        }

        protected virtual Expression BindJoin(Type resultType, Expression outerSource, Expression innerSource, LambdaExpression outerKey, LambdaExpression innerKey, LambdaExpression resultSelector)
        {
            MetaProjectorExpression mpOuter = AsProjection(Visit(outerSource));
            MetaProjectorExpression mpInner = AsProjection(Visit(innerSource));
            Expression projector = MapAndVisit(resultSelector, mpOuter, mpInner);
            return new MetaProjectorExpression(resultType, projector); 
        }

        private Expression BindGroupBy(Type resultType, Expression source, LambdaExpression keySelector, LambdaExpression elementSelector)
        {
            MetaProjectorExpression mp = AsProjection(Visit(source));
            Expression key = MapAndVisit(keySelector, mp);
            Expression element = MapAndVisit(elementSelector, mp);

            Type colType = typeof(IEnumerable<>).MakeGenericType(element.Type);
            Type groupType = typeof(Grouping<,>).MakeGenericType(key.Type, element.Type);

            return new MetaProjectorExpression(resultType,
                Expression.New(groupType.GetConstructor(new Type[] { key.Type, colType }),
                key, new MetaProjectorExpression(colType, element)));
        }
   
        protected virtual Expression BindOrderBy(Type resultType, Expression source, LambdaExpression orderSelector, OrderType orderType)
        {
            return AsProjection(Visit(source)); 
        }

        protected virtual Expression BindThenBy(Expression source, LambdaExpression orderSelector, OrderType orderType)
        {
            return AsProjection(Visit(source)); 
        }

        public Type TableType(object value)
        {
            if (value == null)
                return null;

            Type t = value.GetType();
            return t.IsInstantiationOf(typeof(Query<>)) ?
                t.GetGenericArguments()[0] :
                null;
        }

        protected override Expression VisitConstant(ConstantExpression c)
        {
            Type type = TableType(c.Value);
            if (type != null)
                return new MetaProjectorExpression(c.Type, new MetaExpression(type, null));

            return MakeVoidMeta(c.Type);
        }

        protected override Expression VisitParameter(ParameterExpression p)
        {
            return map.TryGetC(p) ?? p;
        }

        protected override Expression VisitMemberAccess(MemberExpression m)
        {
            Expression source = Visit(m.Expression);

            return BindMember(source, m.Member, m.Type);
        }

        static Expression BindMember(Expression source, MemberInfo member, Type memberType)
        {
            switch (source.NodeType)
            {
                case ExpressionType.MemberInit:
                    return ((MemberInitExpression)source).Bindings
                        .OfType<MemberAssignment>()
                        .Single(a => ReflectionTools.MemeberEquals(a.Member, member)).Expression;
                case ExpressionType.New:
                    NewExpression nex = (NewExpression)source;
                    if (nex.Type.IsInstantiationOf(typeof(Grouping<,>)) && member.Name == "Key")
                    {
                        return nex.Arguments[0];
                    }

                    if (nex.Members != null)
                    {
                        PropertyInfo pi = (PropertyInfo)member;
                        return nex.Members.Zip(nex.Arguments).Single(p => ReflectionTools.PropertyEquals((PropertyInfo)p.Item1, pi)).Item2;
                    }

                    break;
            }

            if (typeof(ModifiableEntity).IsAssignableFrom(source.Type) || typeof(IIdentifiable).IsAssignableFrom(source.Type))
            {
                var pi = Reflector.FindPropertyInfo(member); 

                MetaExpression meta = (MetaExpression)source;

                if (meta.Meta is CleanMeta)
                {
                    PropertyRoute[] routes = ((CleanMeta)meta.Meta).PropertyRoutes.SelectMany(r=>GetRoutes(r, source.Type, pi.Name)).ToArray();

                    return new MetaExpression(memberType, new CleanMeta(routes));
                }
                            
                if (typeof(IRootEntity).IsAssignableFrom(source.Type)) //Works for simple entities and also for interface casting
                    return new MetaExpression(memberType, new CleanMeta(new[]{ PropertyRoute.Root(source.Type).Add(pi)}));
            }

            return MakeDirtyMeta(memberType, source);
        }

        private static PropertyRoute[] GetRoutes(PropertyRoute route, Type type, string piName)
        {
            Implementations imp = route.GetImplementations();

            if (imp == null)
                return new[] { route.Add(piName) };
            else if (imp.IsByAll)
                throw new InvalidOperationException("Metas doesn't work on ImplementedByAll");
            else
                return ((ImplementedByAttribute)imp).ImplementedTypes.Where(t=>type.IsAssignableFrom(t)).Select(t => PropertyRoute.Root(t).Add(piName)).ToArray();
        }

        protected override Expression VisitTypeIs(TypeBinaryExpression b)
        {
            return MakeDirtyMeta(b.Type, Visit(b.Expression)); 
        }

        protected override Expression VisitUnary(UnaryExpression u)
        {
            return MakeCleanMeta(u.Type, Visit(u.Operand));
        }

        protected override Expression VisitBinary(BinaryExpression b)
        {
            return MakeDirtyMeta(b.Type, Visit(b.Right), Visit(b.Left)); 
        }

        protected override Expression VisitConditional(ConditionalExpression c)
        {
            return MakeDirtyMeta(c.Type, Visit(c.Test), Visit(c.IfTrue), Visit(c.IfFalse));
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)


Written By
Software Developer (Senior) Signum Software
Spain Spain
I'm Computer Scientist, one of the founders of Signum Software, and the lead developer behind Signum Framework.

www.signumframework.com

I love programming in C#, Linq, Compilers, Algorithms, Functional Programming, Computer Graphics, Maths...

Comments and Discussions