Click here to Skip to main content
15,896,278 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.5K   1K   22  
In this part, we will focus on writing business logic, LINQ queries and explain inheritance
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Data;
using Signum.Entities;
using Signum.Utilities;
using Signum.Utilities.Reflection;
using Signum.Utilities.ExpressionTrees;
using System.Diagnostics;
using Signum.Entities.Reflection;
using Signum.Engine.Properties;
using System.Linq.Expressions;
using System.Runtime.Remoting.Contexts;

namespace Signum.Engine.Maps
{
    public class SchemaBuilder
    {
        Schema schema;
        public SchemaSettings Settings
        {
            get { return schema.Settings; }
        }

        public SchemaBuilder()
        {
            schema = new Schema(new SchemaSettings());
            Include<TypeDN>();
        }

        protected SchemaBuilder(Schema schema)
        {
            this.schema = schema; 
        }

        public SchemaBuilder(SchemaSettings settings)
        {
            schema = new Schema(settings);
        }
       
        public Schema Schema
        {
            get { return schema; }
        }

    
        public void AddUniqueIndex<T>(Expression<Func<T, object>> fields) where T : IdentifiableEntity
        {
            AddUniqueIndex<T>(fields, null); 
        }

        public void AddUniqueIndex<T>(Expression<Func<T, object>> fields, Expression<Func<T, object>> fieldsNotNull) where T : IdentifiableEntity
        {
            Schema schema = Schema.Current;

            Expression<Func<T, object>>[] fieldLambdas = Split(fields);
            Expression<Func<T, object>>[] fieldsNotNullLambdas = Split(fieldsNotNull);

            Field[] colFields = fieldLambdas.Select(fun => schema.Field<T>(fun)).ToArray();
            Field[] colFieldsNotNull = fieldsNotNullLambdas.Select(fun => schema.Field<T>(fun)).ToArray();

            AddUniqueIndex(new UniqueIndex(schema.Table<T>(), colFields).WhereNotNull(colFieldsNotNull));
        }
        public void AddUniqueIndexMList<T, V>(Expression<Func<T, MList<V>>> toMList, Expression<Func<MListElement<T, V>, object>> fields)
           where T : IdentifiableEntity
        {
            AddUniqueIndexMList(toMList, fields, null); 
        }

        public void AddUniqueIndexMList<T, V>(Expression<Func<T, MList<V>>> toMList, Expression<Func<MListElement<T, V>, object>> fields, Expression<Func<MListElement<T, V>, object>> fieldsNotNull)
            where T : IdentifiableEntity
        {
            Schema schema = Schema.Current;

            Expression<Func<MListElement<T, V>, object>>[] fieldLambdas = Split(fields);
            Expression<Func<MListElement<T, V>, object>>[] fieldsNotNullLambdas = Split(fieldsNotNull);

            RelationalTable table = ((FieldMList)Schema.FindField(schema.Table(typeof(T)), Reflector.GetMemberList(toMList), true)).RelationalTable;

            Field[] colFields = fieldLambdas.Select(fun => Schema.FindField(table,  Reflector.GetMemberList(fun), true)).ToArray();
            Field[] colFieldsNotNull = fieldsNotNullLambdas.Select(fun => Schema.FindField(table,  Reflector.GetMemberList(fun), true)).ToArray();

            AddUniqueIndex(table, colFields, colFieldsNotNull);
        }

        Expression<Func<T, object>>[] Split<T>(Expression<Func<T, object>> columns)
        {
            if (columns == null)
                return new Expression<Func<T, object>>[0];

            if (columns.Body.NodeType == ExpressionType.New)
            {
                return ((NewExpression)columns.Body).Arguments
                    .Select(a => Expression.Lambda<Func<T, object>>(Expression.Convert(a, typeof(object)), columns.Parameters))
                    .ToArray();
            }

            return new[] { columns };
        }

        public void AddUniqueIndex(ITable table, Field[] fields, Field[] notNullFields)
        {
            AddUniqueIndex(new UniqueIndex(table, fields).WhereNotNull(notNullFields));
        }

        public void AddUniqueIndex(ITable table, IColumn[] columns, IColumn[] notNullColumns)
        {
            AddUniqueIndex(new UniqueIndex(table, columns).WhereNotNull(notNullColumns));
        }

        private void AddUniqueIndex(UniqueIndex uniqueIndex)
        {
            ITable table = uniqueIndex.Table;

            if (table.MultiIndexes == null)
                table.MultiIndexes = new List<UniqueIndex>();

            table.MultiIndexes.Add(uniqueIndex);
        }

        public Table Include<T>() where T : IdentifiableEntity
        {
            return Include(typeof(T));
        }

        public virtual Table Include(Type type)
        {
            Table result;
            if (!schema.Tables.TryGetValue(type, out result))
            {
                if (type.IsAbstract)
                    throw new InvalidOperationException("Impossible to include in the Schema the type {0} because is abstract".Formato(type));

                if (!Reflector.IsIdentifiableEntity(type))
                    throw new InvalidOperationException("Impossible to include in the Schema the type {0} because is not and IdentifiableEntity".Formato(type));

                result = new Table(type);

                schema.Tables.Add(type, result);

                string name = schema.Settings.desambiguatedNames.TryGetC(type) ?? Reflector.CleanTypeName(Reflector.ExtractEnumProxy(type) ?? type);

                if (schema.NameToType.ContainsKey(name))
                    throw new InvalidOperationException("Two types have the same cleanName, desambiguate using Schema.Current.Settings.Desambiguate method: \r\n {0}\r\n {1}".Formato(schema.NameToType[name].FullName, type.FullName)); 

                schema.NameToType[name] = type;
                schema.TypeToName[type] = name;

                Complete(result);
            }
            return result;
        }

        void Complete(Table table)
        {
            Type type = table.Type;
            table.Identity = Reflector.ExtractEnumProxy(type) == null;
            table.Name = GenerateTableName(type);
            table.CleanTypeName = GenerateCleanTypeName(type);
            table.Fields = GenerateFields(type, Contexts.Normal, table, NameSequence.Void, false);
            table.GenerateColumns();
        }

        HashSet<string> loadedModules = new HashSet<string>();
        public bool NotDefined(MethodBase methodBase)
        {
            return loadedModules.Add(methodBase.DeclaringType.TypeName() + "." + methodBase.Name); 
        }

        public void AssertDefined(MethodBase methodBase)
        {
            string name = methodBase.DeclaringType.TypeName() + "." + methodBase.Name;

            if (!loadedModules.Contains(name))
                throw new ApplicationException("Call {0} first".Formato(name)); 
        }

        #region Field Generator
        protected Dictionary<string, EntityField> GenerateFields(Type type, Contexts contexto, Table table, NameSequence preName, bool forceNull)
        {
            Dictionary<string, EntityField> result = new Dictionary<string, EntityField>();
            foreach (FieldInfo fi in Reflector.InstanceFieldsInOrder(type))
            {
                if (!Settings.FieldInfoAttributes(type, fi).Any(a=>a is IgnoreAttribute))
                {
                    if (!SilentMode() && Reflector.FindPropertyInfo(fi) == null)
                        Debug.WriteLine("Field {0} of type {1} has no property".Formato(fi.Name, type.Name));

                    Field campo = GenerateField(type, fi, fi.FieldType, contexto, table, preName, forceNull);

                    if (result.ContainsKey(fi.Name))
                        throw new InvalidOperationException("Duplicated field with name {0} on {1}, shadowing not supported".Formato(fi.Name, type.TypeName())); 

                    result.Add(fi.Name, new EntityField(type, fi) { Field = campo });
                }
            }
            return result;
        }

        protected virtual bool SilentMode()
        {
            return schema.SilentMode; 
        }

        protected virtual Field GenerateField(Type type, FieldInfo fi, Type fieldType, Contexts contexto, Table table, NameSequence preName, bool forceNull)
        {
            //fieldType: Va variando segun se entra en colecciones o contenidos
            //fi.Type: el tipo del campo asociado

            KindOfField kof = GetKindOfField(type, fi, fieldType).ThrowIfNullS("Field {0} of type {1} has no database representation".Formato(fi.Name, type.Name));

            if ((allowedContexts[kof] & contexto) != contexto)
                throw new InvalidOperationException("Field {0} of Type {1} should be mapped as {2} but is incompatible with context {3}".Formato(fi.Name, type.Name, fieldType, contexto));

            //generacion del nombre del campo
            NameSequence name = preName;
            if (contexto == Contexts.Normal || contexto == Contexts.Embedded || contexto == Contexts.View)
                name = name.Add(GenerateFieldName(type, fi, kof));
            else if (contexto == Contexts.MList && (kof == KindOfField.Enum || kof == KindOfField.Reference))
                name = name.Add(GenerateFieldName(Reflector.ExtractLite(fieldType) ?? fieldType, kof));

            switch (kof)
            {
                case KindOfField.PrimaryKey:
                    return GenerateFieldPrimaryKey(type, fi, table, name);
                case KindOfField.Value:
                    return GenerateFieldValue(type, fi, fieldType, name, forceNull);
                case KindOfField.Reference:
                    {
                        Implementations at = Settings.GetImplementations(type, fi);
                        if (at == null)
                            return GenerateFieldReference(type, fi, fieldType, name, forceNull);
                        else if (at is ImplementedByAttribute)
                            return GenerateFieldImplmentedBy(type, fi, fieldType, name, forceNull, (ImplementedByAttribute)at);
                        else
                            return GenerateFieldImplmentedByAll(type, fi, fieldType, name, forceNull, (ImplementedByAllAttribute)at);
                    }
                case KindOfField.Enum:
                    return GenerateFieldEnum(type, fi, fieldType, name, forceNull);
                case KindOfField.Embedded:
                    return GenerateFieldEmbedded(type, fi, fieldType, name, forceNull);
                case KindOfField.MList:
                    return GenerateFieldMList(type, fi, table, name);
                default:
                    throw new NotSupportedException(Resources.NoWayOfMappingType0Found.Formato(fieldType));
            }
        }

        static Dictionary<KindOfField, Contexts> allowedContexts = new Dictionary<KindOfField, Contexts>()
        {
            {KindOfField.PrimaryKey,    Contexts.Normal },
            {KindOfField.Value,         Contexts.Normal | Contexts.MList | Contexts.Embedded | Contexts.View },
            {KindOfField.Reference,     Contexts.Normal | Contexts.MList | Contexts.Embedded | Contexts.View },
            {KindOfField.Enum,          Contexts.Normal | Contexts.MList | Contexts.Embedded | Contexts.View },
            {KindOfField.Embedded,      Contexts.Normal | Contexts.MList | Contexts.Embedded | Contexts.View },
            {KindOfField.MList,         Contexts.Normal },
        };

        private KindOfField? GetKindOfField(Type type, FieldInfo fi, Type fieldType)
        {
            if (fi.FieldEquals((IdentifiableEntity ie) => ie.id))
                return KindOfField.PrimaryKey;

            if (Settings.GetSqlDbType(type, fi, fieldType.UnNullify()) != null)
                return KindOfField.Value;

            if (fieldType.UnNullify().IsEnum)
                return KindOfField.Enum;

            if (Reflector.IsIIdentifiable(Reflector.ExtractLite(fieldType) ?? fieldType))
                return KindOfField.Reference;

            if (Reflector.IsEmbeddedEntity(fieldType))
                return KindOfField.Embedded;

            if (Reflector.IsMList(fieldType))
                return KindOfField.MList;

            return null;
        }

        private static Field GenerateFieldPrimaryKey(Type type, FieldInfo fi, Table table, NameSequence name)
        {
            return new FieldPrimaryKey(fi.FieldType, table);
        }

        protected virtual Field GenerateFieldValue(Type type, FieldInfo fi, Type fieldType, NameSequence name, bool forceNull)
        {
            SqlDbType sqlDbType = Settings.GetSqlDbType(type, fi, fieldType.UnNullify()).Value;

            return new FieldValue(fieldType)
            {
                Name = name.ToString(),
                SqlDbType = sqlDbType,
                Nullable = Settings.IsNullable(type, fi, fieldType, forceNull),
                Size = Settings.GetSqlSize(type, fi, sqlDbType),
                Scale = Settings.GetSqlScale(type, fi, sqlDbType),
                IndexType = Settings.GetIndexType(type, fi)
            };
        }

        protected virtual Field GenerateFieldEnum(Type type, FieldInfo fi, Type fieldType, NameSequence name, bool forceNull)
        {
            Type cleanEnum = fieldType.UnNullify();

            var table = Include(Reflector.GenerateEnumProxy(cleanEnum));

            return new FieldEnum(fieldType)
            {
                Name = name.ToString(),
                Nullable = Settings.IsNullable(type, fi, fieldType, forceNull),
                IsLite = false,
                IndexType = Settings.GetIndexType(type, fi),
                ReferenceTable = cleanEnum.HasAttribute<FlagsAttribute>() && !fi.HasAttribute<ForceForeignKey>() ? null : table,
            };
        }

        protected virtual Field GenerateFieldReference(Type type, FieldInfo fi, Type fieldType, NameSequence name, bool forceNull)
        {
            return new FieldReference(fieldType)
            {
                Name = name.ToString(),
                IndexType = Settings.GetIndexType(type, fi),
                Nullable = Settings.IsNullable(type, fi, fieldType, forceNull),
                IsLite  = Reflector.ExtractLite(fieldType) != null,
                ReferenceTable = Include(Reflector.ExtractLite(fieldType) ?? fieldType),
            };
        }

        protected virtual Field GenerateFieldImplmentedBy(Type type, FieldInfo fi, Type fieldType, NameSequence name, bool forceNull, ImplementedByAttribute ib)
        {
            Type cleanType = Reflector.ExtractLite(fieldType) ?? fieldType;
            string erroneos = ib.ImplementedTypes.Where(t => !cleanType.IsAssignableFrom(t)).ToString(t => t.TypeName(), ", ");
            if (erroneos.Length != 0)
                throw new InvalidOperationException("Type {0} do not implement {1}".Formato(erroneos, cleanType));

            bool nullable = Settings.IsNullable(type, fi, fieldType, forceNull) || ib.ImplementedTypes.Length > 1;

            return new FieldImplementedBy(fieldType)
            {
                IndexType = Settings.GetIndexType(type, fi),
                ImplementationColumns = ib.ImplementedTypes.ToDictionary(t => t, t => new ImplementationColumn
                {
                    ReferenceTable = Include(t),
                    Name = name.Add(TypeLogic.GetCleanName(t)).ToString(),
                    Nullable = nullable,
                }),
                IsLite  = Reflector.ExtractLite(fieldType) != null
            };
        }

        protected virtual Field GenerateFieldImplmentedByAll(Type type, FieldInfo fi, Type fieldType, NameSequence preName, bool forceNull, ImplementedByAllAttribute iba)
        {
            bool nullable = Settings.IsNullable(type, fi, fieldType, forceNull);

            return new FieldImplementedByAll(fieldType)
            {
                IndexType = Settings.GetIndexType(type, fi),
                Column = new ImplementationColumn
                {
                    Name = preName.ToString(),
                    Nullable = nullable,
                    ReferenceTable = null,
                },
                ColumnTypes = new ImplementationColumn
                {
                    Name = preName.Add("Type").ToString(),
                    Nullable = nullable,
                    ReferenceTable = Include(typeof(TypeDN))
                },
                IsLite = Reflector.ExtractLite(fieldType) != null
            };
        }

        protected virtual Field GenerateFieldMList(Type type, FieldInfo fi, Table table, NameSequence name)
        {
            Type elementType = fi.FieldType.ElementType();

            RelationalTable relationalTable = new RelationalTable(fi.FieldType)
            {
                Name = GenerateTableNameCollection(type, name),
                BackReference = new FieldReference(table.Type)
                {
                    Name = GenerateBackReferenceName(type),
                    ReferenceTable = table
                },
                PrimaryKey = new RelationalTable.PrimaryKeyColumn(),
                Field = GenerateField(type, fi, elementType, Contexts.MList, null, NameSequence.Void, false) 
            };

            relationalTable.GenerateColumns(); 

            return new FieldMList(fi.FieldType)
            {
                RelationalTable = relationalTable,
            };
        }

        protected virtual Field GenerateFieldEmbedded(Type type, FieldInfo fi, Type fieldType, NameSequence name, bool forceNull)
        {
            bool nullable = Settings.IsNullable(type, fi, fieldType, false);

            return new FieldEmbedded(fieldType)
            {
                HasValue = nullable ? new FieldEmbedded.EmbeddedHasValueColumn() { Name = name.Add("HasValue").ToString() } : null,
                EmbeddedFields = GenerateFields(fieldType, Contexts.Embedded, null, name, nullable || forceNull)
            };
        }
        #endregion

        #region Names

        public virtual string GenerateTableName(Type type)
        {
            return CleanType(type).Name;
        }

        public virtual string GenerateCleanTypeName(Type type)
        {
            type = CleanType(type);

            CleanTypeNameAttribute ctn = Settings.TypeAttributes(type).OfType<CleanTypeNameAttribute>().SingleOrDefault();
            if (ctn != null)
                return ctn.Name;

            return Reflector.CleanTypeName(type);
        }

        protected static Type CleanType(Type type)
        {
            type = Reflector.ExtractLite(type) ?? type;
            type = Reflector.ExtractEnumProxy(type) ?? type;
            return type;
        }

        public virtual string GenerateTableNameCollection(Type type, NameSequence name)
        {
            return CleanType(type).Name + name.ToString();
        }

        public virtual string GenerateFieldName(Type type, KindOfField tipoCampo)
        {
            switch (tipoCampo)
            {
                case KindOfField.Value:
                case KindOfField.Embedded:
                    return type.Name.FirstUpper();
                case KindOfField.Enum:
                case KindOfField.Reference:
                    return "id" + CleanType(type).Name;
                default:
                    throw new NotImplementedException("No field name for type {0} defined".Formato(type));
            }
        }

        public virtual string GenerateFieldName(Type type, FieldInfo fi, KindOfField tipoCampo)
        {
            string name = Reflector.CleanFieldName(fi.Name);

            switch (tipoCampo)
            {
                case KindOfField.PrimaryKey:
                case KindOfField.Value:
                case KindOfField.Embedded:
                case KindOfField.MList:  //se usa solo para el nombre de la tabla 
                    return name;
                case KindOfField.Reference:
                case KindOfField.Enum:
                    return "id" + name;
                default:
                    throw new NotImplementedException("No name for {0} defined".Formato(fi.Name));
            }
        }

        public virtual string GenerateBackReferenceName(Type type)
        {
            return "idParent";
        }
        #endregion
    }

    internal class ViewBuilder : SchemaBuilder
    {
        public ViewBuilder(Schema schema) : base(schema)
        {
        }

        public override Table Include(Type type)
        {
            return Schema.Table(type);
        }

        protected override bool SilentMode()
        {
            return true;
        }

        public Table NewView(Type type)
        {
            Table table = new Table(type)
            {
                Name = GenerateTableName(type),
                IsView = true
            };

            table.Fields = GenerateFields(type, Contexts.View, table, NameSequence.Void, false);

            return table;
        }

        public override string GenerateTableName(Type type)
        {
            SqlViewNameAttribute vn = type.SingleAttribute<SqlViewNameAttribute>();
            if (vn != null)
                return vn.Name;

            return CleanType(type).Name;
        }

        public override string GenerateFieldName(Type type, FieldInfo fi, KindOfField tipoCampo)
        {
            SqlViewColumnAttribute vc = fi.SingleAttribute<SqlViewColumnAttribute>();
            if (vc != null)
                return vc.Name;

            return base.GenerateFieldName(type, fi, tipoCampo);
        }

        public override string GenerateFieldName(Type type, KindOfField tipoCampo)
        {
            return base.GenerateFieldName(type, tipoCampo);
        }
    }

}

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