Низкая производительность системы Entity Framework 5

У меня есть 5 объектов:

public class Album
{
    public int Id { get; set; }

    public string Title { get; set; }

    public virtual List<AlbumArtist> AlbumArtists { get; set; }
    public virtual List<Artist> Artists { get; set; }
    public virtual List<Genre> Genres { get; set; }
    public virtual List<Song> Songs { get; set; }

}

public class AlbumArtist
{
    public int Id { get; set; }

    public string Title { get; set; }

    public virtual List<Album> Albums { get; set; }
    public virtual List<Artist> Artists { get; set; }
    public virtual List<Genre> Genres { get; set; }
    public virtual List<Song> Songs { get; set; }
}

public class Artist
{
    public int Id { get; set; }

    public string Title { get; set; }

    public virtual List<AlbumArtist> AlbumArtists { get; set; }
    public virtual List<Album> Albums { get; set; }
    public virtual List<Genre> Genres { get; set; }
    public virtual List<Song> Songs { get; set; }
}

public class Genre
{
    public int Id { get; set; }

    public string Title { get; set; }

    public virtual List<AlbumArtist> AlbumArtists { get; set; }
    public virtual List<Album> Albums { get; set; }
    public virtual List<Artist> Artists { get; set; }
    public virtual List<Song> Songs { get; set; }
}

public class Song
{
    public int Id { get; set; }

    public string Title { get; set; }

    public virtual List<AlbumArtist> AlbumArtists { get; set; }
    public virtual List<Album> Albums { get; set; }
    public virtual List<Artist> Artists { get; set; }
    public virtual List<Genre> Genres { get; set; }
}

Как вы можете видеть, существует много отношений "многие ко многим". Я заполняю свои объекты, а затем пытаюсь сохранить их в DbContext таким образом:

_albumArtists.ForEach(delegate(AlbumArtist albumArtist)
{
    if (albumArtist.Id == 0)
    {
            _dbContext.Entry(entity).State = EntityState.Added;
            _dbContext.SaveChanges();
    }
    else
    {
            _dbContext.Entry(entity).State = EntityState.Modified;
            _dbContext.SaveChanges();
    }
});
...

или таким образом:

_albumArtists.ForEach(delegate(AlbumArtist albumArtist)
{
    if (albumArtist.Id == 0)
    {
            _dbContext.Entry(entity).State = EntityState.Added;
    }
    else
    {
            _dbContext.AlbumArtists.State = EntityState.Modified;
    }
});
_dbContext.SaveChanges();
...

Навсегда сохранить мои объекты в DbContext. Я даже пытался сделать следующее:

Configuration.AutoDetectChangesEnabled = false;

Но это не помогло. Кстати, есть около 17 000 песен и 1 700 альбомов.

Что не так???

Пожалуйста, помогите!

PS

Вот мой полный код: https://github.com/vjacheslavravdin/PsyTrance/blob/master/PsyTrance/Program.cs Возможно, вы можете предложить, как упростить его.

Спасибо!

Ответ 1

Во-первых, несколько пояснений:

EF не значительно медленнее, чем другие методы для пакетных операций. в моих тестах вы можете получить 50% -ное улучшение с помощью исходной команды SQL и, возможно, до 10 раз быстрее с копией SQL Bulk, но EF не намного медленнее, чем сравнительные методы, как правило (хотя часто воспринимается как очень медленное). Для большинства приложений EF даст подходящие номера производительности даже в пакетных сценариях, учитывая правильную настройку. (см. мою статью здесь: http://blog.staticvoid.co.nz/2012/3/24/entity_framework_comparative_performance и http://blog.staticvoid.co.nz/2012/8/17/mssql_and_large_insert_statements)

Из-за того, как EF меняет отслеживание, его потенциал может значительно превысить производительность большинства людей, которые напишут заявления на вставку на основе SqlCommand (есть много иконов для планирования запросов, поездок в оба конца и транзакций, которые делают его довольно трудно написать оптимальное выполнение операторов объемной вставки). Я предложил эти дополнения к EF здесь (http://entityframework.codeplex.com/discussions/377636), но havent реализовал их еще.

Вы совершенно правы в своем решении отключить автоматическое обнаружение изменений, каждое .Add или .Attach действие с обнаружением изменений перечисляет граф отслеживания, поэтому, если вы добавляете дополнения 17k в том же контексте, вам нужно будет перечислить график 17000 раз в общей сложности 17000 + 16999 +... + 2 + 1 = 144 500 000 объектов, неудивительно, что он так долго остается? (см. мою статью здесь: http://blog.staticvoid.co.nz/2012/5/7/entityframework_performance_and_autodetectchanges)

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

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

Лучший способ - сохранить свой график в кусках. некоторые

//With Auto detect changes off.
foreach(var batch in batches)//keep batch size below 1000 items, play around with the numbers a little
{
    using(var ctx = new MyContext())//make sure you create a new context per batch.
    {
        foreach(var entity in batch){
             ctx.Entities.Add(entity);
        }
        ctx.SaveChanges();
    }
}

Я бы ожидал, что вам нужно ориентироваться в 17-30 секунд, чтобы сделать все строки размером 17 тыс..

делая это с помощью сырых SQL-команд, вы можете получить это примерно до 12-20 с;

с повторной реализацией с массовой копией, вы могли бы получить это до 2-5 с