Улучшение производительности объемной вставки в инфраструктуре Entity

Я хочу вставить 20000 записей в таблицу по инфраструктуре сущности и занимает около 2 минут. Есть ли способ, кроме использования SP, улучшить его производительность. Это мой код:

 foreach (Employees item in sequence)
 {
   t = new Employees ();
   t.Text = item.Text;
   dataContext.Employees.AddObject(t);                  
 }
 dataContext.SaveChanges();

Ответ 1

Есть возможность для нескольких улучшений (если вы используете DbContext):

Set:

yourContext.Configuration.AutoDetectChangesEnabled = false;
yourContext.Configuration.ValidateOnSaveEnabled = false;

Сделайте SaveChanges() в пакетах из 100 вставок... или вы можете попробовать с пакетами из 1000 элементов и посмотреть изменения в производительности.

Поскольку во всех этих вставках контекст один и тот же, и он становится все больше, вы можете перестроить свой контекстный объект каждые 1000 вставок. var yourContext = new YourContext(); Я думаю, что это большой выигрыш.

Выполняя эти улучшения в процессе импорта данных, я взял его от 7 минут до 6 секунд.

Фактические цифры... не могут быть 100 или 1000 в вашем случае... попробуйте и настройте его.

Ответ 2

Невозможно заставить EF повысить производительность при выполнении этого способа. Проблема в том, что EF выполняет каждую вставку в отдельном обратном направлении в базу данных. Удивительно, не так ли? Даже DataSets поддерживает пакетную обработку. Проверьте эту статью для некоторого обходного пути. Другим обходным решением может быть использование пользовательской хранимой процедуры, принимающей параметр таблицы, но для этого вам нужен необработанный ADO.NET.

Ответ 3

Вы можете использовать расширение объемной вставки

Вот небольшая таблица сравнения

EntityFramework.BulkInsert vs EF AddRange

и использование чрезвычайно просто

context.BulkInsert(hugeAmountOfEntities);

надеюсь, что это поможет

Ответ 4

Используя приведенный ниже код, вы можете расширить класс частичного контекста с помощью метода, который будет принимать коллекцию объектов сущности и массовое копирование их в базу данных. Просто замените имя класса из MyEntities на то, что названо вашим классом сущности, и добавьте его в свой проект в правильном пространстве имен. После этого все, что вам нужно сделать, это вызвать метод BulkInsertAll, передавая объекты сущности, которые вы хотите вставить. Не используйте повторно класс контекста, а создавайте новый экземпляр каждый раз, когда используете его. Это требуется, по крайней мере, в некоторых версиях EF, поскольку данные аутентификации, связанные с используемым здесь SQLConnection, теряются после использования класса один раз. Я не знаю, почему.

Эта версия предназначена для EF 5

public partial class MyEntities
{
    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(
            "EntitySetMaps",
            BindingFlags.GetProperty | BindingFlags.NonPublic | BindingFlags.Instance,
            null, storageMapping, null);

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

            if (types[0].Name == entityName)
            {
                var fragments = GetArrayList("MappingFragments", typeMapping);
                var fragment = fragments[0];
                var properties = GetArrayList("AllProperties", fragment);
                foreach (var property in properties)
                {
                    var edmProperty = GetProperty("EdmProperty", property);
                    var columnProperty = GetProperty("ColumnProperty", 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.NonPublic | 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.NonPublic | BindingFlags.Instance, null, instance, null);
    }
}

Эта версия предназначена для EF 6

public partial class CMLocalEntities
{
    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(
            "EntitySetMaps",
            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("Types", typeMapping);

            if (types[0].Name == entityName)
            {
                var fragments = GetArrayList("MappingFragments", typeMapping);
                var fragment = fragments[0];
                var properties = GetArrayList("AllProperties", fragment);
                foreach (var property in properties)
                {
                    var edmProperty = GetProperty("EdmProperty", property);
                    var columnProperty = GetProperty("ColumnProperty", 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);
    }

}

И, наконец, немного для вас, любителей Linq-To-Sql.

partial class MyDataContext
{
    partial void OnCreated()
    {
        CommandTimeout = 5 * 60;
    }

    public void BulkInsertAll<T>(IEnumerable<T> entities)
    {
        entities = entities.ToArray();

        string cs = Connection.ConnectionString;
        var conn = new SqlConnection(cs);
        conn.Open();

        Type t = typeof(T);

        var tableAttribute = (TableAttribute)t.GetCustomAttributes(
            typeof(TableAttribute), false).Single();
        var bulkCopy = new SqlBulkCopy(conn) { 
            DestinationTableName = tableAttribute.Name };

        var properties = t.GetProperties().Where(EventTypeFilter).ToArray();
        var table = new DataTable();

        foreach (var property in properties)
        {
            Type propertyType = property.PropertyType;
            if (propertyType.IsGenericType &&
                propertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
            {
                propertyType = Nullable.GetUnderlyingType(propertyType);
            }

            table.Columns.Add(new DataColumn(property.Name, propertyType));
        }

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

        bulkCopy.WriteToServer(table);
        conn.Close();
    }

    private bool EventTypeFilter(System.Reflection.PropertyInfo p)
    {
        var attribute = Attribute.GetCustomAttribute(p, 
            typeof (AssociationAttribute)) as AssociationAttribute;

        if (attribute == null) return true;
        if (attribute.IsForeignKey == false) return true; 

        return false;
    }

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

Ответ 5

Возможно, этот ответ поможет вам. Кажется, вы хотите периодически избавляться от контекста. Это связано с тем, что контекст становится все больше и больше по мере роста прикрепленных объектов.

Ответ 6

Лучше всего пропустить Entity Framework полностью для этой операции и полагаться на класс SqlBulkCopy. Другие операции могут продолжать использовать EF по-прежнему.

Это увеличивает стоимость обслуживания решения, но в любом случае помогает сократить время, необходимое для вставки больших коллекций объектов в базу данных на один-два порядка по сравнению с использованием EF.

Вот статья, которая сравнивает класс SqlBulkCopy с EF для объектов с отношениями родитель-потомок (также описывает изменения в дизайне, необходимые для реализации массовой вставки): Как сделать Массовая вставка сложных объектов в базу данных SQL Server

Ответ 7

В среде Azure с базовым веб-сайтом, который имеет 1 экземпляр. Я попытался вставить пакет из 1000 записей за время из 25000 записей, используя для цикла, это заняло 11,5 мин, но в параллельном исполнении потребовалось меньше минуты. Поэтому я рекомендуется использовать TPL (параллельная библиотека задач).

         var count = (you collection / 1000) + 1;
         Parallel.For(0, count, x =>
        {
            ApplicationDbContext db1 = new ApplicationDbContext();
            db1.Configuration.AutoDetectChangesEnabled = false;

            var records = members.Skip(x * 1000).Take(1000).ToList();
            db1.Members.AddRange(records).AsParallel();

            db1.SaveChanges();
            db1.Dispose();
        });

Ответ 8

В настоящее время нет лучшего способа, однако может быть незначительное улучшение, перемещая SaveChanges внутри цикла для, вероятно, 10 элементов.

int i = 0;

foreach (Employees item in sequence)
{
   t = new Employees ();
   t.Text = item.Text;
   dataContext.Employees.AddObject(t);   

   // this will add max 10 items together
   if((i % 10) == 0){
       dataContext.SaveChanges();
       // show some progress to user based on
       // value of i
   }
   i++;
}
dataContext.SaveChanges();

Вы можете настроить 10, чтобы быть ближе к лучшей производительности. Это не будет сильно улучшать скорость, но это позволит вам показать некоторый прогресс для пользователя и сделать его более удобным.

Ответ 9

Попробуйте использовать Bulk Insert....

http://code.msdn.microsoft.com/LinqEntityDataReader

Если у вас есть коллекция объектов, например storeEntities, вы можете сохранить их с помощью SqlBulkCopy следующим образом

        var bulkCopy = new SqlBulkCopy(connection);
        bulkCopy.DestinationTableName = TableName;
        var dataReader = storeEntities.AsDataReader();
        bulkCopy.WriteToServer(dataReader);

Есть один ключ с этим кодом. Убедитесь, что определение Entity Framework для объекта точно совпадает с определением таблицы, убедитесь, что свойства Entity находятся в том же порядке в модели Entity, что и столбцы в таблице SQL Server. Невыполнение этого условия приведет к исключению.

Ответ 10

В вашем коде есть две основные проблемы с производительностью:

  • Использование метода Add
  • Использование SaveChanges

Использование метода добавления

Метод добавления становится медленнее и медленнее для каждого добавляемого объекта.

Смотрите: http://www.zzzprojects.com/entity-framework/how/performance-add

Например, добавив 10 000 объектов через:

  • Добавить (взять ~ 105 000 мс)
  • AddRange (взять ~ 120 мс)

Примечание. Объекты еще не сохранены в базе данных!

Проблема заключается в том, что метод Add пытается установить DetectChanges в каждом добавленном объекте, а AddRange делает это один раз после того, как все сущности добавлены в контекст.

Общее решение:

  • Используйте AddRange over Add
  • SET AutoDetectChanges to false
  • SPLIT SaveChanges несколькими партиями

Использование SaveChanges

Entity Framework не создан для массовых операций. Для каждого объекта, который вы сохраняете, выполняется обратная связь базы данных.

Итак, если вы хотите вставить 20 000 записей, вы будете выполнять 20 000 обращений к базам данных, которые INSANE!

Существует некоторая сторонняя библиотека, поддерживающая Bulk Insert:

  • Z.EntityFramework.Extensions(рекомендуется)
  • EFUtilities
  • EntityFramework.BulkInsert

Смотрите: Библиотека объемной вставки Entity Framework

Будьте осторожны при выборе библиотеки объемной вставки. Только расширения Entity Framework Extensions поддерживают все виды ассоциаций и наследования, и это единственное, что все еще поддерживается.


Отказ от ответственности. Я являюсь владельцем Расширения платформы Entity

Эта библиотека позволяет вам выполнять все массовые операции, необходимые для ваших сценариев:

  • Массовые SaveChanges
  • Массовая вставка
  • Массовое удаление
  • Массовое обновление
  • Массовое слияние

Пример

// Easy to use
context.BulkSaveChanges();

// Easy to customize
context.BulkSaveChanges(bulk => bulk.BatchSize = 100);

// Perform Bulk Operations
context.BulkDelete(customers);
context.BulkInsert(customers);
context.BulkUpdate(customers);

// Customize Primary Key
context.BulkMerge(customers, operation => {
   operation.ColumnPrimaryKeyExpression = 
        customer => customer.Code;
});

EDIT: ответьте на вопрос в комментарии

Есть ли рекомендуемый максимальный размер для каждой массовой вставки для созданной вами библиотеки.

Не слишком высокий, не слишком низкий. Существует не значение, которое соответствует всем сценариям, поскольку оно зависит от множества факторов, таких как размер строки, индекс, триггер и т.д.

Обычно рекомендуется около 4000.

Также есть способ связать все это в одной транзакции и не беспокоиться о том, что это время

Вы можете использовать транзакцию Entity Framework. Наша библиотека использует транзакцию, если она запущена. Но будьте осторожны, транзакция, которая занимает много времени, также связана с такими проблемами, как блокировка строки/индекса/таблицы.

Ответ 11

Хотя поздний ответ, но я отправляю ответ, потому что у меня была такая же боль. Я создал новый проект GitHub только для этого, на данный момент он поддерживает прозрачную загрузку/обновление/удаление для Sql-сервера с использованием SqlBulkCopy.

https://github.com/MHanafy/EntityExtensions

Есть и другие лакомства, и, надеюсь, он будет расширен, чтобы делать больше по дорожке.

Использование его так же просто, как

var insertsAndupdates = new List<object>();
var deletes = new List<object>();
context.BulkUpdate(insertsAndupdates, deletes);

Надеюсь, что это поможет!

Ответ 12

 Use : db.Set<tale>.AddRange(list); 
Ref :
TESTEntities db = new TESTEntities();
List<Person> persons = new List<Person> { 
new  Person{Name="p1",Place="palce"},
new  Person{Name="p2",Place="palce"},
new  Person{Name="p3",Place="palce"},
new  Person{Name="p4",Place="palce"},
new  Person{Name="p5",Place="palce"}
};
db.Set<Person>().AddRange(persons);
db.SaveChanges();