Como Insertar más de 185.000 Registros con Entity Framework en Menos de Dos Milisegundos… Like a Boss!!!

En los últimos meses he tenido la dicha de trabajar con la tecnología Entity Framework del ADO.NET de Microsoft. Debo decir que el paradigma de Code-First es una maravilla… es adictivo y te hace sentir en total control del modo en que se almacenan los datos del sistema que se esté diseñando/implementando.

Sin embargo tiene una limitante importante: cuando hay que sembrar la base de datos (Seed) con información para tablas maestras, y dicha información tiene un volumen importante de registros o datos, entonces tanto el mismísimo Visual Studio como el Entity Framework fallan. En mi caso, fue con intentar sembrar todos los códigos postales de España y Portugal… un poco más de 185.000 registros. El problema es esencialmente uno sólo: el número de registros a insertar es muy alto.

Kudos

Antes de empezar presentando la solución a este problema, me gustaria mencionar que el título de esta publicación es cortesía de mi esposa: Astrid a quien podéis seguir a través de Gikvanna en WordPress, Twitter y Facebook.

El Problema

Por un lado, con Code-First, debemos crear instancias de clases POCO que queremos insertar, pero cuando se tiene demasiados elementos, el Visual Studio se relentiza debido a que tiene que mantener constancia, integridad y estilo de los elementos que escribimos. No importa cuanta memoria o capacidad de procesamiento tenga nuestro equipo de trabajo… el Visual Studio se pondrá denso y pesado.

    /// <summary>
    /// Permite aplicar configuraciones a una base de datos.
    /// </summary>
    internal class DatabaseInitializer : IDatabaseInitializer<DatabaseContext>
    {
        ...
        /// <summary>
        /// Agrega datos al contexto (<see cref="DatabaseContext"/>) para <i>sembrarlo</i>.
        /// </summary>
        /// <param name="context">El contexto a sembrar.</param>
        protected override void Seed(DatabaseContext context)
        {
            ...
            // Códigos postales para el 'seed'.
            List<PostalCode> = new List<PostalCode>
            {
                // Más de 185.000 registros de esta misma forma.
                new PostalCode { Value="pc1", AlternativeId="cp1" },
                new PostalCode { Value="pc2", AlternativeId="cp2" },
                new PostalCode { Value="pc3", AlternativeId="cp3" },
                new PostalCode { Value="pc4", AlternativeId="cp4" },
                new PostalCode { Value="pc5", AlternativeId="cp5" },
                new PostalCode { Value="pc6", AlternativeId="cp6" },
                ...
            }
            ...
        }
        ...
    }

Por otro lado cuando intentemos iniciar la aplicación por primera vez (sin base de datos alguna), el tiempo de creación de ésta junto al sembrado es tan alto y el número de INSERTs tan elevado que se producen muchos time-outs (tanto en la base de datos como en el servidor web en caso de aplicaciones de este tipo); lo cual hace el proceso de desarrollo e implementación muy frustrante.

Una Alternativa de Solución

El primer problema, referente al número elevado de instancias de la clase POCO que debemos crear para el sembrado, se soluciona fácilmente almacenando los datos en archivos de texto o de recursos en un formato parseable, como por ejemplo un listado de elementos separados por coma.

        private static IList<PostalCode> ReadPostalCodes()
        {
            // Leer los códigos postales desde recursos 'embebidos' (empotrados) en el ensamblado (assembly).
            Assembly currentAssembly = Assembly.GetExecutingAssembly();
            IEnumerable<string> postalCodesResourceFileNames = currentAssembly.GetManifestResourceNames().Where(resourceName => resourceName.Contains("PostalCodes_"));

            Stream stream = null;
            string[] itemTokens = null;

            IList<PostalCode> postalCodes = new List<PostalCode>();

            foreach (string postalCodeResourceFileName in postalCodesResourceFileNames)
            {
                try
                {
                    stream = currentAssembly.GetManifestResourceStream(postalCodeResourceFileName);

                    using (StreamReader streamReader = new StreamReader(stream))
                    {
                        // Cada dato está separado por un salto de línea, y a su vez, cada elemento de cada dato está separado por comas.
                        IEnumerable<string> postalCodeItems = streamReader.ReadToEnd().Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);

                        foreach (string postalCodeItem in postalCodeItems)
                        {
                            itemTokens = postalCodeItem.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                            postalCodes.Add(new PostalCode { Country = itemTokens[0], Value = itemTokens[1] });
                        }
                    }
                }
                finally
                {
                    if (stream != null)
                    {
                        stream.Dispose();
                    }
                }
            }

            return postalCodes;
        }

Para resolver el segundo problema, referente a la inserción de un elevado número de registros, nos apoyaremos en una clase del Framework .NET llamada SqlBulkCopy, la cual toma un DataTable o un IDataReader para realizar una inserción masiva de datos en la base de datos.

En los siguientes códigos fuentes, los comentarios estarán en inglés tal y como los tengo en mis proyectos, y que por diversas razones no he tenido tiempo de traducirlos al Castellano. Eventualmente trataré de ir editando esta publicación para traducirlas poco a poco.

La idea es entonces integrar la clase DbContext empleada en Entity Framework para interactuar con el repositorio de datos a través la clase SqlBulkCopy. Esta integración es fácilmente alcanzable con métodos de extensión que permitan emplear el método de inserción con cualquier DbContext; por lo cual el primer paso es crear la clase estática (con los respectivos using que necesitaremos a lo largo de la solución).

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.Metadata.Edm;
using System.Data.Objects;
using System.Data.SqlClient;
using System.Globalization;
using System.Linq;
using System.Reflection;

namespace My.Data.Entity
{
    /// <summary>
    /// Contains extensions method for <see cref="DbContext"/> objects that works specifically with Microsoft SQL Server databases.
    /// </summary>
    public static class DbContextSqlServerExtensions
    {
        ...
    }
}

Dentro de esta clase crearemos el método de extensión BulkInsert para inserción masiva, junto con una sobre carga cuyo objetivo explicaré más adelante. Como podréis ver, el método carece prácticamente de toda lógica más allá de invocar a otro método llamado ExecuteBulkInsert el cual será el verdadero responsable de realizar la lógica de inserción masiva apoyado en la clase SqlBulkCopy.

Sin embargo, igual en la documentación hacemos incapié en una advertencia muy importante que también debemos tomar en cuenta: en esta solución se emplea Reflection para acceder a métodos y propiedades de las clases de Entity Framework que para la versión actual (no beta) disponible a la fecha de esta publicación, correspondiente a la 5.0, son internal y no públicas (public) en el API. Esto significa que esta solución puede dejar de funcionar, o no funcionar como se espera cuando se emplee con nuevas versiones de Entity Framework que puedan haber modificado el comportamiento sistémico de las propiedades y métodos invocados a través de Reflection.


        /// <summary>
        /// <para>
        /// Performs a bulk load of a collection of entities into a database, and regenerates the entity list with updated 
        /// information.
        /// </para>
        /// <para>
        /// Due to some technological limitations, the regeneration of the list is actually a retrieval of fresh data from
        /// storage (database) with all the available entities, in other words, it will get all the entities from storage
        /// and not only those in the original entity list.
        /// </para>
        /// </summary>
        /// <remarks>
        /// <para>
        /// This method leverages the <see cref="SqlBulkCopy "/> class and the <c>BCP</c> protocol to insert a collection
        /// of entities into the storage configured in the <see cref="DbContext"/> parameter.
        /// </para>
        /// <para>
        /// <b><u>WARNING:</u></b> This method relays on <a href="http://msdn.microsoft.com/en-us/library/ms173183(v=vs.100).aspx"><c>Reflection</c></a> 
        /// to retrieve values from properties that are not <c>public</c> (in fact, properties that are  <c>internal</c> or <c>private</c>). 
        /// This means that there is a possible risk that this method will break, stop working or not-working as expected when new versions of 
        /// Entity Framework are released by Microsoft.
        /// </para>
        /// </remarks>
        /// <typeparam name="TEntity">The type of entity to insert.</typeparam>
        /// <param name="context">
        /// The context to work with. This context must have configured a connection to a storage (like a database).
        /// </param>
        /// <param name="list">
        /// A collection of entities to insert.This reference might be reloaded it with updated entities
        /// if the <paramref name="reloadEntities"/> parameter is set to <c>true</c>.
        /// </param>
        /// <param name="reloadEntities">
        /// Determines if the entities list must be reloaded with updated entities from storage. If <c>true</c> the entities are 
        /// going to be reloaded and stored in the list passed as parameter, otherwise <c>false</c> and the entities are not going
        /// to be reloaded.
        /// </param>
        public static void BulkInsert<TEntity>(this DbContext context, IList<TEntity> list, bool reloadEntities) where TEntity : class
        {
            ExecuteBulkInsert<TEntity>(context, ref list, reloadEntities);
        }

Como puede apreciarse, el método BulkInsert posee dos (2) sobre cargas: la primera simplemente considera una colección (en forma de lista) de las entidades (objetos de las clases POCO) a insertar mientras que la segunda toma un valor booleano que corresponde a un indicador de si deseamos recargar las entidades una vez insertadas, lo cual puede resultar muy útil para obtener nuevamente estas entidades pero con las propiedades correspondientes a las claves primaras en el repositorio de datos apropiadamente establecidas (de ahí que el método ExecuteBulkInsert reciba la colección/lista de entidades como un parámetro ref).

        /// <summary>
        /// Actually executes the bulk load of a collection of entities into a database, and regenerates the entity list with updated 
        /// information, if required.
        /// </summary>
        /// <typeparam name="TEntity">The type of entity to insert.</typeparam>
        /// <param name="context">
        /// The context to work with. This context must have configured a connection to a storage (like a database).
        /// </param>
        /// <param name="list">
        /// A collection of entities to insert.This reference might be reloaded it with updated entities if 
        /// the <paramref name="reloadEntities"/> parameter is set to <c>true</c>. It is received as <c>ref</c>
        /// in order to warranty the reload.
        /// </param>
        /// <param name="reloadEntities">
        /// Determines if the entities list must be reloaded with updated entities from storage. If <c>true</c> the entities are 
        /// going to be reloaded and stored in the list passed as parameter, otherwise <c>false</c> and the entities are not going
        /// to be reloaded.
        /// </param>
        private static void ExecuteBulkInsert<TEntity>(DbContext context, ref IList<TEntity> list, bool reloadEntities) where TEntity : class
        {
            Type typeOfEntity = typeof(TEntity);
            ObjectContext objectContext = ((IObjectContextAdapter)context).ObjectContext;

            // Retrieve the Storage Data Space for the entity we are working with. From it, get the members which represents the columns in the database. From 
            // those members, we can get the real name of the columns (since the developer may have changed them with the 'HasColumnName' method) and their types.            
            ReadOnlyMetadataCollection<EdmMember> storageSpaceMembers = objectContext.MetadataWorkspace.GetItem<EntityType>(@"CodeFirstDatabaseSchema." + typeOfEntity.Name, DataSpace.SSpace).Members;

            using (SqlBulkCopy sqlBulkCopy = new SqlBulkCopy(context.Database.Connection.ConnectionString))
            {
                using (DataTable dataTable = new DataTable())
                {
                    dataTable.Locale = CultureInfo.InvariantCulture;

                    // For each column defined in the Storage Data Space...
                    for (int index = 0; index < storageSpaceMembers.Count; index++)
                    {
                        // ...configure the instance of the SqlBulkCopy class with the name of the source and destination column...
                        sqlBulkCopy.ColumnMappings.Add(storageSpaceMembers[index].Name, storageSpaceMembers[index].Name);

                        // ...and also configure a DataTable with the name of the column and its respective CLR type.
                        /**************************************************************************************************************
                         * This is the risky part of the code. From this point on, the properties and methods employed are invoked by *
                         * Reflection and are non-public (i.e. internal) in the Entity Framework assembly.                            *
                         **************************************************************************************************************/
                        /***************************************************************************************************************** 
                         * WARNING: the 'ClrType' property is not public; actually, it is 'internal virtual'. This means that there is a *
                         * possible risk that this  method will break, stop working or not working as expected when new versions of      *
                         * Entity Framework are released by Microsoft.                                                                   *
                         *****************************************************************************************************************/
                        dataTable.Columns.Add(storageSpaceMembers[index].Name, GetPropertyValue<Type>(storageSpaceMembers[index].TypeUsage.EdmType, @"ClrType"));
                    }

                    PopulateDataTable(dataTable, objectContext, list, storageSpaceMembers);

                    sqlBulkCopy.DestinationTableName = GetTableName(objectContext, typeOfEntity);
                    sqlBulkCopy.BatchSize = list.Count;
                    sqlBulkCopy.WriteToServer(dataTable);
                }
            }

            if (reloadEntities)
            {
                list = context.Set<TEntity>().ToList();
            }
        }

El primer paso consiste en obtener el nombre real de las columnas que se corresponden a las propiedades de la entidad que deseamos insertar, el cual en principio no debería diferir mucho de dichas propiedades, pero que ciertamente puede haber cambiado si el programador ha empleado el método HasColumnName al configurar como se almacenaría la entidad en el repositorio de datos.

Para obtener los nombres de las columnas, se realiza una búsqueda en la metadata del contexto obteniendo de ésta todos los objetos que existan en el modelo de almacenamiento cuyo nombre se corresponda al nombre de la entidad a insertar precedido del nombre del esquema por defecto para desarrollos con Code First: CodeFirstDatabaseSchema.

Con los nombres de las columnas se puede crear la instancia de la clase SqlBulkCopy a usar, configurando ésta con los nombres de la columna origen y la columna destino. Así mismo, se crea y configura una instancia de la clase DataTable que se empleará con el SqlBulkCopy para realizar la inserción. Sin embargo, el DataTable requiere conocer el tipo de dato exacto y concreto de la columna, para lo cual es necesario invocar al método internal virtual ClrType de la clase EdmMember obtenida del modelo de almacenamiento de la metadata a través de Reflection.

Una vez configurada la instancia del DataTable es necesario poblarla con los valores apropiados, para lo cual empleamos el método auxiliar PopulateDataTable.

        /// <summary>
        /// Fills a <see cref="DataTable" /> with data from a list of entities.
        /// </summary>
        /// <remarks>
        /// <para>
        /// This method is an auxiliary method for the <see cref="DbContextExtensions.BulkInsert{TEnity}(DbContext, ref IList{TEnity}, bool)"/>  method.
        /// </para>
        /// <para>
        /// The <c>storageSpaceMembers</c> parameter is just a performance consideration. It is previously retrieved in the 
        /// <see cref="DbContextExtensions.BulkInsert{TEnity}(DbContext, ref IList{TEnity}, bool)"/> method, and in order to not calculate it again, it is passed as
        /// parameter to this method. However, this can be changed if improvement of the isolation of this method is required.
        /// </para>
        /// </remarks>
        /// <typeparam name="TEnity">The type of entity used to fill the <see cref="DataTable"/>.</typeparam>
        /// <param name="dataTable">The <see cref="DataTable"/> to fill.</param>
        /// <param name="objectContext">A context to perform operation on the entities. </param>
        /// <param name="list">The list of entities used to fill the <see cref="DataTable"/>.</param>
        /// <param name="storageSpaceMembers">A list of members for the entity type on the Storage Data Space.</param>
        private static void PopulateDataTable<TEnity>(DataTable dataTable, ObjectContext objectContext, IList<TEnity> list, ReadOnlyMetadataCollection<EdmMember> storageSpaceMembers) where TEnity : class
        {
            Type typeOfEntity = typeof(TEnity);

            // Get the Object Space, which contains data and information of the current model (classes and members).
            ReadOnlyMetadataCollection<EdmMember> objectSpaceMembers = objectContext.MetadataWorkspace.GetItem<EntityType>(typeOfEntity.FullName, true, DataSpace.OSpace).Members;
            EntitySetBase entitySetBase = objectContext.MetadataWorkspace.GetEntityContainer(objectContext.DefaultContainerName, DataSpace.CSpace).BaseEntitySets.FirstOrDefault(baseEntitySet => baseEntitySet.ElementType.Name.Equals(typeOfEntity.Name, StringComparison.OrdinalIgnoreCase));
            EntityType entityType = (EntityType)entitySetBase.ElementType;

            foreach (TEnity entity in list)
            {
                DataRow dataRow = dataTable.NewRow();

                // For each property of the entity, retrieve its value and assign it to the appropriate corresponding column of the DataTable.
                foreach (EdmProperty edmProperty in entityType.Properties)
                {
                    // To obtain the appropriate column, We use a combination of the Storage Data Space with the Object Space.
                    // - First: obtain from the Object Space the property that matches the name of the entity property being populated in the DataTable.
                    // - Second: get the index of the property from the Object Space. This index or position in the Object Space is going to be the same 
                    //           in all other spaces, including the Storage Data Space.
                    // - Third: with this index, retrieve the corresponding property in the Storage Data Space.
                    // - Fourth: from the retrieved property in the Storage Data Space, get its name which will correspond to the column name in the DataTable.
                    // - Fifth: by 'Reflection' get the value of the property and assign it to the DataTable.
                    dataRow[storageSpaceMembers[objectSpaceMembers.IndexOf(objectSpaceMembers[edmProperty.Name])].Name] = GetPropertyValue<object>(entity, edmProperty.Name);
                }

                // Get the appropriate value of any referencial constraint (like a foreign key).
                if (entityType.NavigationProperties.Count > 0)
                {
                    ICollection<ReferentialConstraint> referentialConstraints = GetReferentialConstraints(objectContext, typeOfEntity, entitySetBase);

                    foreach (NavigationProperty navigationProperty in entityType.NavigationProperties)
                    {
                        // Filter the referencial contraints to get only those that apply to the entity being manipulated.
                        // Only the "From Role" part of the relationship is required since We are inserting the "To Role" part.
                        IEnumerable<ReferentialConstraint> filteredReferentialConstraints = referentialConstraints.Where(referentialConstraint => referentialConstraint.FromRole.Name.Equals(navigationProperty.Name, StringComparison.OrdinalIgnoreCase));

                        foreach (ReferentialConstraint referentialConstraint in filteredReferentialConstraints)
                        {
                            dataRow[referentialConstraint.ToProperties[0].Name] = GetPropertyValue<object>(GetPropertyValue<object>(entity, referentialConstraint.FromRole.Name), referentialConstraint.FromProperties[0].Name);
                        }
                    }
                }

                dataTable.Rows.Add(dataRow);
            }
        }

Este método se encargará de combinar la información del espacio del modelo de almacenamiento con la información del espacio del modelo de objetos de la metadata del contexto para mapear cada propiedad con la columna correspondiente y asignar el valor respectivo. El truco aquí es darse cuenta que la indexación entre los diferentes espacios de la metadata del contexto es la misma; así, un elemento en una específica posición del espacio de almacenamiento corresponderá al mismo elemento en la misma específica posición en cualquiera de los otros espacios (conceptual, de objectos, etc.).

Un aspecto importante de éste método es la determinación de las referencias (esto es, claves foráneas o secundarias) para obtener los valores que correspondan únicamente a la entidad siendo manipulada y evitar problemas de congruencia en los datos a insertar o excepciones de Entity Framework debido a éstas. Por cada elemento de restricción referencial, se busca la propiedad que le corresponde y por tanto la columna respectiva en el DataTable para asignar su valor.

Estas restricciones referenciales se determinan a través de una invocación al método auxiliar GetReferentialConstraints, el cual toma el contexto de datos siendo empleado, el tipo de la entidad (esto es, el tipo del POCO) y un objeto denominado entity set base que se corresponde a la metadata que relaciona el modelo de almacenamiento con el modelo conceptual (del cual se extraen las referencias).

        /// <summary>
        /// Retrieves a collection of <see cref="ReferentialConstraint"/> elements for a given entity type (and entity set base)
        /// on the current object context.
        /// </summary>
        /// <remarks>
        /// <b><u>WARNING:</u></b> This method relays heavily on <a href="http://msdn.microsoft.com/en-us/library/ms173183(v=vs.100).aspx"><c>Reflection</c></a> 
        /// to retrieve the referential constraints information because it is hidden in the Entity Framework assembly (i.e. <c>internal</c> types and members). 
        /// Starting with the retrieval of the mapping between the conceptual model and the storage model (<see cref="DataSpace.CSSpace"/>) everything in the 
        /// obtained <see cref="ItemCollection"/> is non-public. This means that there is a possible risk that this method will break, stop working or 
        /// not-working as expected when new versions of Entity Framework are released by Microsoft.
        /// </remarks>
        /// <param name="objectContext">Object context to work with.</param>
        /// <param name="entityType">Type of the entity whose referential constraints are going to be retrieved.</param>
        /// <param name="entitySetBase">Entity base with the information of referential constraints to retrieve.</param>
        /// <returns>A collection of referential constraints for the given entity in the current object context.</returns>
        private static ICollection<ReferentialConstraint> GetReferentialConstraints(ObjectContext objectContext, Type entityType, EntitySetBase entitySetBase)
        {
            List<ReferentialConstraint> referentialConstraints = new List<ReferentialConstraint>();

            // Get the data space that contains the mapping between the conceptual model and the storage model.
            GlobalItem conceptToStorageItemCollection = objectContext.MetadataWorkspace.GetItemCollection(DataSpace.CSSpace).First();

            /**************************************************************************************************************
             * This is the risky part of the code. From this point on, the properties and methods employed are invoked by *
             * Reflection and are non-public (i.e. internal) in the Entity Framework assembly.                            *
             **************************************************************************************************************/

            // - First: get the entity set mapping (System.Data.Mapping.StorageSetMapping class) from the data space that maps
            //          the conceptual model and the storage model by invoking the internal 'GetEntitySetMapping' method.
            //
            // - Second: get the collection of type mappings (a System.Collections.ObjectModel.ReadOnlyCollection of System.Data.Mapping.StorageSetMapping objects) 
            //           from the entity set mapping with the 'TypeMappings' property. Since the collection elements is internal it will be 'unknow'
            //           to the compiler during design time, so it's marked as dynamic.
            dynamic dynamicStorageTypeMappings = GetPropertyValue<dynamic>(conceptToStorageItemCollection.GetType().GetMethod("GetEntitySetMapping", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(conceptToStorageItemCollection, new object[] { entitySetBase.Name }), "TypeMappings");

            foreach (object storageTypeMapping in dynamicStorageTypeMappings)
            {
                // - Third: for each type mapping, get the mapping fragments (a System.Collections.ObjectModel.ReadOnlyCollection of System.Data.Mapping.StorageMappingFragment
                //          objects). Each mapping fragment provides information and data for those properties of a type that map to a single table.
                dynamic dynamicStorageMappingFragments = GetPropertyValue<dynamic>(storageTypeMapping, "MappingFragments");

                foreach (object storageMappingFragment in dynamicStorageMappingFragments)
                {
                    // - Fourth: get an entity set that represents a table from which the properties are mapped in this fragment by invoking the 'TableSet' property.
                    EntitySet tableSet = GetPropertyValue<EntitySet>(storageMappingFragment, "TableSet");

                    // Fifth: just verify the table set is not empty and its name corresponds to the entity we are working with.
                    if (tableSet != null && tableSet.Name.Equals(entityType.Name, StringComparison.OrdinalIgnoreCase))
                    {
                        // - Sixth: get a collection of tuples of assosiation sets and referencial contraints from the table set by invoking the internal 
                        //          property 'ForeignKeyDependents'.
                        //
                        // - Seventh: from such collection, get only the referencial contraints items and add them to the collection we are going to 
                        //            return as result of this method.
                        referentialConstraints.AddRange(GetPropertyValue<ICollection<Tuple<AssociationSet, ReferentialConstraint>>>(tableSet, "ForeignKeyDependents").Select(foreignKeyDependent => foreignKeyDependent.Item2));
                    }
                }
            }

            return referentialConstraints;
        }

Este método es el más delicado de todos, ya que es el que más propiedades y métodos private o internal del Entity Framework invoca a través de Reflection. Básicamente, todo lo que se invoca tras obtener la información de la metadata que relaciona el modelo conceptual con el modelo de almacenamiento es o bien internal o bien private. Por otro lado, otro reto es que los tipos del mapeo entre el modelo conceptual y el de almacenamiento no son conocidos, por lo cual deben ser tratados como dynamic. Lo que se hace con estos objetos es buscar aquellos que tienen relación con la entidad que se está insertando para identificarlos como referencias con ésta y conservarlas en una colección que retornaremos al llamador responsable de poblar el DataTable.

Finalmente, nuevamente en el método ExecuteBulkInsert se establece el nombre de la tabla (entidad) a poblar con el SqlBulkCopy, el número de registros y los registros en si.

¡Y ESO SERÍA TODO!

Al ejecutar mi solución sin la base de datos, el proceso de crearla y poblarla (sembrarla) con los códigos postales de España y Portugal (que como hemos mencionado es un número superior a 185.000 registros) tarda algo menos de… ¡¡¡dos milisegundos!!! No hay time/outs ni fallos de ningún tipo.

Más detalles de la implementación en los comentarios de los propios métodos que he dejado como parte del código fuente expuesto en esta publicación. Se que están en inglés, pero los tengo así por temas de mi trabajo del día a día. Sin embargo espero que podáis entenderlos y os aportén más luz sobre esta alternativa de solución.

Métodos Auxiliares

A lo largo de esta publicación, habrán visto referencias e invocaciones a métodos como GetPropertyValue o GetTableName. Dichos métodos no existen en el API del Framework .NET o del Entity Framework. Son métodos auxiliares que he creado para conseguir el objetivo de esta solución. En esta parte os dejo el código fuente de los mismos.

        /// <summary>
        /// Retrieves a the named property (public or not) from an object trough <c>Reflection</c>.
        /// </summary>
        /// <typeparam name="TValue">The type of the property to retrieve.</typeparam>
        /// <param name="element">The object where to reflect the property.</param>
        /// <param name="propertyName">The name of the property to retrieve.</param>
        /// <returns>The value of the named property.</returns>
        private static TValue GetPropertyValue<TValue>(object element, string propertyName) where TValue : class
        {
            return element.GetType().GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).GetValue(element, null) as TValue;
        }

        /// <summary>
        /// Retrieves the name of the dataTable.
        /// </summary>
        /// <remarks>
        /// Developers may change the name of the dataTable from the expected standard to a custom one by using 
        /// the <see cref="System.Data.Entity.ModelConfiguration.EntityTypeConfiguration{TEntityType}.ToTable(string)"/> method. This method will retrieve such configuration.
        /// </remarks>
        /// <param name="objectContext">The <see cref="ObjectContext"/> with mapping and concept model data.</param>
        /// <param name="entityType">Type of the entity type configuration whose dataTable name is going to be retrieved.</param>
        /// <returns>The configured name of the dataTable for the provided entity type configuration.</returns>
        private static string GetTableName(ObjectContext objectContext, Type entityType)
        {
            return objectContext.MetadataWorkspace.GetItems<EntityContainer>(DataSpace.SSpace).FirstOrDefault().BaseEntitySets.FirstOrDefault(entityBaseSet => entityBaseSet.Name.Equals(entityType.Name, StringComparison.OrdinalIgnoreCase)).MetadataProperties[@"Table"].Value.ToString();
        }

Otros Gestores de Bases de Datos

El Entity Framework soporta cualquier gestor de base de datos que implemente un conector para ADO.NET. Para el caso de esta solución y el uso de la clase SqlBulkCopy he asumido que se está usando Entity Framework con una base de datos Microsoft SQL Server. Sin embargo, si se quisiera usar un gestor como ORACLE, en vez de usar la clase SqlBulkCopy se tendría que usar las facilidades propias del ODP.NET como el Bulk Array (también conocido como ODP.NET Array Binding).

Anuncios

Como Validar el DNI o el RUC de Perú en C#

En estos días me ha tocado trabajar en un proyecto que, como tantos otros hoy día, está pensado para un entorno empresarial globalizado.

Parte de las reglas de negocio del proyecto exige, como también es común en tantos otros proyectos, validar el documento de identidad correspondiente al cliente para el país al cual éste pertenece. Para el caso de países como España o Brasil existe amplia documentación y recursos referentes al algoritmo empleado para validar sus respectivos documentos de identidad.

Pero lamentablemente para Perú, uno de los países que tengo que gestionar, la cantidad y la calidad de la información disponible es decepcionante.

DNI Electrónico Peruano

Por otro lado, al hablar de documentos de identidad en Perú, debemos distinguir entre el DNI (Documento Único de Identidad) y el RUC (Registro Único de Contribuyente). El primero aplica a los ciudadanos en general y cuenta con un identificador conocido como CUI (Cédula Única de Identidad), mientras que el segundo aplica a empresas y comercios.

El siguiente algoritmo permite validar documentos de identidad peruanos (tanto DNI como RUC). Para el caso del DNI, es independiente que el CUI tenga como término de verificación un número o una letra.

public static bool ValidateIdentificationDocumentPeru(string identificationDocument)
{
    if (!string.IsNullOrEmpty(identificationDocument))
    {
        int addition = 0;
        int[] hash = { 5, 4, 3, 2, 7, 6, 5, 4, 3, 2 };
        int identificationDocumentLength = identificationDocument.Length;

        string identificationComponent = identificationDocument.Substring(0, identificationDocumentLength - 1);

        int identificationComponentLength = identificationComponent.Length;

        int diff = hash.Length - identificationComponentLength;

        for (int i = identificationComponentLength - 1; i >= 0; i--)
        {
            addition += (identificationComponent[i] - '0') * hash[i + diff];
        }

        addition = 11 - (addition % 11);

        if (addition == 11)
        {
            addition = 0;
        }

        char last = char.ToUpperInvariant(identificationDocument[identificationDocumentLength - 1]);

        if (identificationDocumentLength == 11)
        {
            // The identification document corresponds to a RUC.
            return addition.Equals(last - '0');
        }
        else if (char.IsDigit(last))
        {
            // The identification document corresponds to a DNI with a number as verification digit.
            char[] hashNumbers = { '6', '7', '8', '9', '0', '1', '1', '2', '3', '4', '5' };
            return last.Equals(hashNumbers[addition]);
        }
        else if (char.IsLetter(last))
        {
            // The identification document corresponds to a DNI with a letter as verification digit.
            char[] hashLetters = { 'K', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J' };
            return last.Equals(hashLetters[addition]);
        }
    }

    return false;
}

Finalmente, el gobierno de Perú pone a disposición la siguiente herramienta para verificar documentos de identidad; y esta otra herramienta para validar RUC.

Espero que les sea de utilidad.

Instalando la plataforma .NET 4.0.3

Como seguro saben, hace cosa de cuatro meses salió una revisión de la versión 4 de la plataforma .NET.

Con versión 4.0.3, esta revisión incluye en principio varias correcciones y nuevos miembros y tipos muy solicitados por la comunidad de desarrolladores entre las cuales destacan las siguientes:

Característica / Funcionalidad 4.0.1 4.0.2 4.0.3
Soporte a la ejecución de workflows con confianza parcial.    
Actividades de mensajería de Windows Workflow Foundation (WWF) habilitada para múltiples hospedajes (hosts).    
Nuevo enumerado GCLatencyMode que configuran el comportamiento del garbage collection.    
Soporte para identificadores corelacionados en conexiones de SqlClient.    
Soporte para las nuevas clases y librerías portables (portable classes de la plataforma .NET 4.    
Soporte para AlwaysOn en el SqlClient  
Soporte para la ejecución con el SqlClient para base de datos locales de SQL Server Express.  
Soporte para diseñar y ejecutar StateMachine.
Soporte para SqlWorkflowInstanceStore sobre SQL Azure.
Compensación y balanceo para hosts personalizados.

Para poder disfrutar de esta nueva versión existen tres (3) instalables:

  1. Para desarrollar aplicaciones en el Visual Studio 2010, instalar el paquete de diseño disponible aquí.
  2. Para ejecutar aplicaciones creadas con esta revisión, instalar el paquete de ejecución (run-time) disponible aquí.
  3. Si ya disfrutan y emplean el nuevo Visual Studio 2012, entonces se necesita el paquete multi-target disponible aquí.

Referencias:

Un Framework para Programación Orientada a Aspectos (AOP) en C#    Parte 0: Introducción

Este es el primero de una serie de cuatro publicaciones que estaré realizando sobre programación orientada a aspectos en .NET empleando el lenguaje de programación C#.
Mi intención inicial era que la primera parte incluyese una apropiada introducción teórica a la programación orientada a aspectos, adicional a los detalles propios de diseño e implementación de un framework. Sin embargo, la longitud que estaba adquiriendo esta primera publicación me ha obligado a dividirla en dos partes: una parte cero o introductoria con los conceptos y elementos teóricos, y una futura parte uno con el correspondiente detalle técnico de implementación.

Acorde a la Wikipedia la programación orientada a aspectos, o AOP por sus siglas en inglés, es un paradigma de programación cuya intención es permitir una adecuada modularización de las aplicaciones y posibilitar una mejor separación de incumbencias; encapsulando de forma efectiva los diferentes conceptos que componen una aplicación en entidades bien definidas, eliminando las dependencias entre cada uno de ellas.

El principal objetivo de AOP es la separación de las funcionalidades dentro del sistema:

  • Por un lado funcionalidades comunes utilizadas a lo largo de la aplicación.
  • Por otro lado, las funcionalidades propias de cada módulo.

Cada funcionalidad común se encapsulará en una entidad.

Origen y Razón de Ser

La idea de la programación orientada a aspectos surge directamente del paradigma de la programación orientada a objectos (o OOP por sus sigas en inglés), en la cual muchas veces al estar implementando funcionalidades de un aplicativo de tamaño considerable empiezan a aparecer retos y problemas que no son solventables a través de las formas o técnicas habituales. Algunos de estos problemas son:

  • Scattered Code: o código disperso, no es más que líneas de código que están distribuidas por toda o gran parte del aplicativo, y que incrementan de forma considerable la dificultad de las tareas de mantenimiento y desarrollo.
  • Tangled Code: o código enmarañado, es un problema que puede aparecer cuando una misma pieza de código (como un módulo) implementa múltiples comportamientos o aspectos diferentes del sistema de forma simultánea, empleando cláusulas de tipo switch ó if-then-else anidadas.
  • Loss Uniformity: o pérdida de uniformidad, es más un problema de forma y gobernabilidad del desarrollo, que de diseño o implementación. Se basa en que aún cuando el arquitecto de software o el líder técnico haya sentado de manera formal los estándares y convenios de implementación en temas como nomenclatura o mensajes, estos se pierden a lo largo del ciclo de vida del desarrollo del aplicativo. El caso más común son los mensajes de la aplicación para casos como por ejemplo registro de evetos o excepciones, donde por más que exista un lineamiento formal, cada desarrollador puede acabar escribiendo el mensaje acorde a su mejor sentido común. Este fenómeno se encuentra mucho en equipos de desarrollo de alta rotación, en proyectos de duración muy prolongada (superior a dos años) o que son mantenidos por equipos de desarrollo diferentes.

Al final del día, la razón de ser de una AOP es buscar minimizar (o solventar) el desorden que puede aparecer en la arquitectura de un aplicativo desarrollado mediante OOP. Este desorden aparece cuando algo que el sistema tiene que hacer requiere la participación de muchos y variados objetos diferentes, lo cual suele ocurrir por la abstracción de los requerimientos en objetos que realiza el diseñador, quien a menudo pierde de vista el hecho de que en última instancia, dichos objetos van interactuar unos con otros con el fin de cumplir con tales requerimientos.

Lo anterior ocurre con facilidad y de forma subconscientemente, porque la forma fundamental en que interactúan los requerimientos es muchas veces diferente de la forma en que interactúan los objetos.

Los conceptos descritos en los patrones de diseño son un intento de entender formalmente los temas de la interacción de objetos y para proporcionar al diseñador de algunos medios bien comprendidos para resolver este problema de interdependencia. Más aún, la mayoría de las implementaciones de un AOP se apoyan fuertemente en el patrón de diseño observador (u Observer en inglés).

Así, podemos decir a modo de resumen que AOP surge como un paradigma de programación dentro del mismo paradigma de OOP, que no busca sustituir a éste, sino más bien mitigar algunas de sus limitaciones al modularizar todo aquel código de índole transversal en un sistema de software.

Por supuesto que AOP no es la panacea o un hito utópico en la implantación de arquitecturas y en el desarrollo de aplicativos en OOP. Siempre se necesitará de la participación de equipos ágiles de desarrollo, con credenciales, entrenamiento y formación apropiada, experiencia adecuada y un liderazgo eficaz para llevar con éxito un proyecto de desarrollo de software, con o sin el seguimiento de una AOP.

Terminología

Los siguientes son los conceptos que se suelen manejarse en una AOP (según han sido tomados desde la Wikipedia):

  • Aspect (Aspecto) es una funcionalidad transversal (cross-cutting) que se va a implementar de forma modular y separada del resto del sistema, y que en una OOP tradicional, es el causante habitual de código repetido, disperso o enmarañado. El ejemplo más común y simple de un aspecto es el registro de eventos (event logging) dentro del sistema, ya que necesariamente afecta a todas las partes que generan un suceso.
  • Join point (Punto de Cruce o de Unión) es un punto de ejecución dentro del sistema donde un aspecto puede ser introducido, como la invocación o retorno de un método, la ocurrencia de una excepción o la modificación de un campo. El código del aspecto será inyectado en el flujo de ejecución de la aplicación para añadir su funcionalidad.
  • Advice (Consejo) es la implementación del aspecto, es decir, contiene el código que implementa la nueva funcionalidad. Se insertan en la aplicación en los Puntos de Cruce.
  • Pointcut (Puntos de Corte) define los Consejos que se aplicarán a cada Punto de Cruce. Se especifica mediante Expresiones Regulares o mediante patrones de nombres (de clases, métodos o campos), e incluso dinámicamente en tiempo de ejecución según el valor de ciertos parámetros.
  • Introduction (Introducción) permite añadir métodos o atributos a clases ya existentes. Un ejemplo en el que resultaría útil es la creación de un Consejo de Auditoría que mantenga la fecha de la última modificación de un objeto, mediante una variable y un método apropiado a la tarea, que podrían ser introducidos en todas las clases (o sólo en algunas) para proporcionarles esta nueva funcionalidad. Este concepto también es conocido como inyección de código, o injection por su término en inglés.
  • Target (Destinatario) es la clase aconsejada, la clase que es objeto de un consejo. Sin AOP, esta clase debería contener su lógica, además de la lógica del aspecto.
  • Proxy (Resultante) es el objeto creado después de aplicar el Consejo al Objeto Destinatario. El resto de la aplicación únicamente tendrá que soportar al Objeto Destinatario (pre-AOP) y no al Objeto Resultante (post-AOP).
  • Weaving (Tejido) es el proceso de aplicar Aspectos a los Objetos Destinatarios para crear los nuevos Objetos Resultantes en los especificados Puntos de Cruce. Este proceso puede ocurrir a lo largo del ciclo de vida del Objeto Destinatario y del desarrollo del aplicativo:
    • Aspectos en Tiempo de Compilación, que necesita un compilador especial.
    • Aspectos en Tiempo de Carga, los Aspectos se implementan cuando el Objeto Destinatario es cargado a memoria. Requiere un ClassLoader especial.
    • Aspectos en Tiempo de Ejecución.

Contextos en .NET

Cuando entramos en el mundo de .NET y de AOP es necesario explicar un concepto adicional: el de Contextos. En .NET, un contexto es un conjunto de propiedades o reglas de uso que definen un entorno en el que una colección de objetos reside. Las reglas se aplican cuando los objetos entran o salen de un contexto.

Los contextos se crean durante la activación de un objeto. Un nuevo objeto se coloca en un contexto existente o en un nuevo contexto creado con los atributos incluidos en la metadata del tipo que corresponde al objeto activado.

Desde el punto de vista del CLR (Common Lenguage Runtime), un contexto no es más que una de las muchas subdivisiones que puede llegar a tener un app domain. Más aún, cuando un app domain es creado, tiene un contexto por defecto (que justamente se llama default context).

Un contexto funciona básicamente a través de mensajes (objetos que implementan la interfaz IMessage), que se definen como un conjunto de propiedades tales como el nombre del método que se está invocando, sus parámetros (tanto de entrada como de salida) así como cualquier otra característica propia que defina a la invocación. Estos mensajes son recibidos por sumideros (o sinks en inglés, y que son objetos que implementan la interfaz IMessageSink) y encargadas de procesar los mensajes para ejecutar acciones a través de los datos que estos portan.

La idea de contexto es fundamental para desarrollar una AOP en .NET empleado el lenguage C#, ya que es gracias a las clases que permiten decorar objectos como objetos de contexto (tal como ContextBoundObject) que se podrá intervenir en la ejecución de la lógica de éstos para inyectar la funcionalidad de los aspectos que se implementen a través de los mensajes.

Cuando se ejecuta un sistema desde la óptica de la clase ContextBoundObject ocurre lo siguiente:

  1. La invocación a un método se convierte en una instancia de IMessage.
  2. El mensaje pasa a través de una serie de sumideros (objetos del tipo IMessageSink), que son enlazados para formar una cadena (chain) conceptual.
  3. Cada sumidero es capaz de analizar el mensaje y sus características, ejecutando una lógica específica.
  4. De ser necesario, un sumidero puede modificar el mensaje.
  5. Cuando un sumidero finaliza de procesar el mensaje, lo pasa al siguiente sumidero en la cadena conceptual.
  6. Eventualmente la cadena es totalmente ejecutada y el método invocado se ejecuta.
  7. Si el método retorna algún valor (o genera una excepción) la cadena de sumideros se ejecuta nuevamente en sentido revertido.

Un Framework para .NET

Si realizamos una búsqueda en Internet sobre frameworks en .NET para realizar AOP, encontramos muchos productos que ofrecen toda una gama de posibilidades para inyectar contextos dentro del código durante el tiempo de diseño/codificación del sistema, durante su compilación o después de ésta directamente en el MSIL.

Entonces… ¿por qué crear un framework para AOP habiendo tantos productos disponibles?

Pues porque como Arquitecto de Soluciones de Software encuentro siempre reconfortante el poder crear mis propios frameworks sobre los cuales puedo tener un control más fino y delicado, y en los que puedo poner toda mi confianza en que estoy entregando a clientes y compañeros de trabajo productos de calidad que pueden utilizar y extender fácilmente.

Por otra parte, simplemente ocurre que no me convencen los productos que hay actualmente para hacer AOP; no porque sean malos… sino simplemente porque no siguen una aproximación con la que me sienta cómodo, sobre todo porque estos productos suelen ser muy mastodónticos y complejos. De ahí esta iniciativa de crear un framework lo suficientemente minimalista como para ser confortable de emplear y extender, universal para que cada aspecto pueda implementarse independientemente con el apoyo de la tecnología que el desarrollador encuentre más apropiada a sus gustos y necesidades (que en mi caso suele ser apoyarme en los Microsoft Enterprise Libraries).

En la próxima parte de esta serie de publicaciones mostraré los detalles técnicos de diseño e implementación de este custom framework.

Referencias

Minify JavaScript and CSS files when publishing Web Applications from Visual Studio

One of the best practices to speed up a web site or web application is to minify JavaScript and CSS files; which as a secondary effect, not only reduces the size of those files but also makes them harder to read (something helpful to prevent the stealing of your ideas and efforts).

Intro

You may think that actually this is not very helpful since using external JavaScript and CSS files in the real world generally produces faster pages because they will be cached by the browser. However, if their size is big, you may improve the first request load time by reducing their size.

Minification is the practice of removing unnecessary characters from code to reduce its size thereby improving load times. When code is minified all comments are removed, as well as unneeded white space characters (like newlines or tabs). This improves response time performance because the size of the downloaded file is reduced.

There are many tools to minify JavaScript and CSS files, like for example:

I’m a Microsoft .NET professional, so in this and future post, I will going to focus only on Microsoft’s Ajax Minifier tool.

The proper time to minify

It is not advisable to minify your web application’s JavaScript and CSS files during developing time; it should be done on deployment or publish (for testing or any other purpose) time.

But then, how to minify those files without affecting the original source code in the solution when deploying it?

The answer is quite easy: we must use MSBuild in the web application project file to configure the Microsoft Ajax Minifier to be executed as a task when publishing in ‘Release’ mode.

The steps to minify

The first step is to unload the web application project by right-clicking on it and choose the option ‘Unload Project’. Once unloaded, right-click it again and choose the ‘Edit <web_app_project_name>.csproj’ option, which will open the XML editor of the Visual Studio with the project’s configuration.

What I usually do, is to go to the end of the file and then add the following line just before the closing Project tag:

<UsingTask TaskName="AjaxMin" AssemblyFile="$(SolutionDir)Lib\AjaxMinTask.dll" />
<PropertyGroup>
    <ResGenDependsOn>
        CompressJsAndCss;
        $(ResGenDependsOn)
    </ResGenDependsOn>
</PropertyGroup>

Please note the PropertyGroup tag. It describes the property activity to perform and its dependency with the generation of resources. This is very important to specify.

I prefer the UsingTask tag instead of the Import tag because I work with a lot of people who may have installed the Microsoft Ajax Minifier in different versions or locations that me (or not having it installed at all). So we distribute the solution’s dlls inside a Lib subfolder, that is also under source control. With this mechanism, every body uses the same dlls in the same version and referenced from the same location.

It is very important to take into account that you will need to have the AjaxMinTask.dll and also the AjaxMin.dll in the same location.

The next step is to create a custom target with the following lines:


<Target Name="CompressJsAndCss" AfterTargets="CopyAllFilesToSingleFolderForPackage" Condition="'$(Configuration)'=='Release'">
...
</Target>

The new target is called CompressJsAndCss and will be executed when publishing a web application just after the CopyAllFilesToSingleFolderForPackage target, which is responsible of copying (or materialize in-memory files) the project files into your obj folder (specifically the folder specified by _PackageTempDir) in preparation for a publish.

Next, we need to gather the files suitable to be minified, in this case, JavaScript and CSS files. To do so, just add the following lines to the custom target:


<ItemGroup>
      <JS Include="$(_PackageTempDir)\**\*.js" Exclude="$(_PackageTempDir)\**\*.min.js" />
      <CSS Include="$(_PackageTempDir)\**\*.css" Exclude="$(_PackageTempDir)\**\*.min.css" />
</ItemGroup>

As you can see, we’re creating a set of all available JavaScript and CSS files in the _PackageTempDir folder (where the CopyAllFilesToSingleFolderForPackage put the files for the publication) but excluding those with extension .min.js or .min.css which are already minified (usually jQuery files).

Finally, we must call the Microsoft Ajax Minifier task in order to minify the gathered files with the following lines:


 <AjaxMin JsKnownGlobalNames="jQuery,$" JsSourceFiles="@(JS)" JsSourceExtensionPattern="\.js$" JsTargetExtension=".js"
          CssSourceFiles="@(CSS)" CssSourceExtensionPattern="\.css$" CssTargetExtension=".css"/>

Note that we’re informing the Microsoft Ajax Minifier to respect the ‘jQuery’ and ‘$’ literals on the JavaScript files since they are well know global names that must not be modified in the minify process.

At the end, we should have the following added configuration:


<!-- Minify all JavaScript and CSS files that are present on this web application when publish in release. -->
  <UsingTask TaskName="AjaxMin" AssemblyFile="$(SolutionDir)Lib\AjaxMinTask.dll" />

  <!-- This target will run after the files are copied to PackageTmp folder (usually 'obj\Release\Package\PackageTmp') -->
  <Target Name="CompressJsAndCss" AfterTargets="CopyAllFilesToSingleFolderForPackage" Condition="'$(Configuration)'=='Release'">
    <ItemGroup>
      <JS Include="$(_PackageTempDir)\**\*.js" Exclude="$(_PackageTempDir)\**\*.min.js" />
      <CSS Include="$(_PackageTempDir)\**\*.css" Exclude="$(_PackageTempDir)\**\*.min.css" />
    </ItemGroup>
    <Message Text="Compressing JavaScript and CSS files..." Importance="high" />
    <AjaxMin JsKnownGlobalNames="jQuery,$" JsSourceFiles="@(JS)" JsSourceExtensionPattern="\.js$" JsTargetExtension=".js"
             CssSourceFiles="@(CSS)" CssSourceExtensionPattern="\.css$" CssTargetExtension=".css"/>
  </Target>

So what is left to do is to save the csproj file, close it and right-click on it from the Solution Explorer and choose the ‘Reload Project’ option in order to load it again in the solution.

Now when we go to publish the web application (and in ‘Release’ mode) our custom target will be evaluated just after the CopyAllFilesToSingleFolderForPackage target, and all JavaScript and CSS files will be minified without affecting the original ones.

References