Friday, April 11, 2014

Speeding up inserts using the Entity Framework - Part 4 (EF 6.1)

This is yet another post about speeding up inserts using the entity framework. Previously I have written about this in the following posts.
  1. Speeding up inserts using Linq-To-Entities - Part 3 (EF 6.0)
  2. Speeding up inserts using Linq-To-Entities - Part 2 (EF 5)
  3. Speeding up inserts using Linq-To-Entities - Part 1
  4. http://blog.tanneryd.com/2011/11/speeding-up-inserts-using-linq-to-sql.html
  5. http://blog.tanneryd.com/2011/12/speeding-up-inserts-using-linq-to-sql.html
This time the code is adapted for EF 6.1. It's pretty much the same as for EF 6.0 but there are some subtle differences.

    public partial class MyEntities
    {
        private Dictionary<string, object> _cache;
        public Dictionary<string, object> Cache
        {
            get { return _cache ?? (_cache = new Dictionary<string, object>()); }
        }

        public MyEntities(string nameOrConnectionString)
            : base(string.Format("name={0}", nameOrConnectionString))
        {            
        }

        public void BulkInsertAll<T>(T[] entities) where T : class
        {
            var conn = (SqlConnection)Database.Connection;

            conn.Open();
            
            Type t = typeof(T);
            Set(t).ToString();
            var objectContext = ((IObjectContextAdapter)this).ObjectContext;
            var workspace = objectContext.MetadataWorkspace;
            var mappings = GetMappings(workspace, objectContext.DefaultContainerName, typeof(T).Name);

            var tableName = GetTableName<T>();
            var bulkCopy = new SqlBulkCopy(conn) { DestinationTableName = tableName };

            // Foreign key relations show up as virtual declared 
            // properties and we want to ignore these.
            var properties = t.GetProperties().Where(p => !p.GetGetMethod().IsVirtual).ToArray();
            var table = new DataTable();
            foreach (var property in properties)
            {
                Type propertyType = property.PropertyType;

                // Nullable properties need special treatment.
                if (propertyType.IsGenericType &&
                    propertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
                {
                    propertyType = Nullable.GetUnderlyingType(propertyType);
                }

                // Since we cannot trust the CLR type properties to be in the same order as
                // the table columns we use the SqlBulkCopy column mappings.
                table.Columns.Add(new DataColumn(property.Name, propertyType));
                var clrPropertyName = property.Name;
                var tableColumnName = mappings[property.Name];
                bulkCopy.ColumnMappings.Add(new SqlBulkCopyColumnMapping(clrPropertyName, tableColumnName));
            }

            // Add all our entities to our data table
            foreach (var entity in entities)
            {
                var e = entity;
                table.Rows.Add(properties.Select(property => GetPropertyValue(property.GetValue(e, null))).ToArray());
            }

            // send it to the server for bulk execution
            bulkCopy.BulkCopyTimeout = 5*60;
            bulkCopy.WriteToServer(table);

            conn.Close();
        }

        private string GetTableName<T>() where T : class
        {
            var dbSet = Set<T>();
            var sql = dbSet.ToString();
            var regex = new Regex(@"FROM (?<table>.*) AS");
            var match = regex.Match(sql);
            return match.Groups["table"].Value;
        }

        private object GetPropertyValue(object o)
        {
            if (o == null)
                return DBNull.Value;
            return o;
        }

        private Dictionary<string, string> GetMappings(MetadataWorkspace workspace, string containerName, string entityName)
        {
            var mappings = new Dictionary<string, string>();
            var storageMapping = workspace.GetItem<GlobalItem>(containerName, DataSpace.CSSpace);
            dynamic entitySetMaps = storageMapping.GetType().InvokeMember(
                "EntitySetMappings",
                BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance,
                null, storageMapping, null);

            foreach (var entitySetMap in entitySetMaps)
            {
                var typeMappings = GetArrayList("EntityTypeMappings", entitySetMap);
                dynamic typeMapping = typeMappings[0];
                dynamic types = GetArrayList("EntityTypes", typeMapping);

                if (types[0].Name == entityName)
                {
                    var fragments = GetArrayList("Fragments", typeMapping);
                    var fragment = fragments[0];
                    var properties = GetArrayList("PropertyMappings", fragment);
                    foreach (var property in properties)
                    {
                        var edmProperty = GetProperty("Property", property);
                        var columnProperty = GetProperty("Column", property);
                        mappings.Add(edmProperty.Name, columnProperty.Name);
                    }
                }
            }

            return mappings;
        }

        private ArrayList GetArrayList(string property, object instance)
        {
            var type = instance.GetType();
            var objects = (IEnumerable)type.InvokeMember(
                property, 
                BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance, null, instance, null);
            var list = new ArrayList();
            foreach (var o in objects)
            {
                list.Add(o);
            }
            return list;
        }

        private dynamic GetProperty(string property, object instance)
        {
            var type = instance.GetType();
            return type.InvokeMember(property, BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance, null, instance, null);
        }


        private bool disposed = false;
        protected override void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (disposing)
                {
                    Cache.Clear();
                }
                disposed = true;
            }
            base.Dispose(disposing);
        }
    }