我有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;
但它没有帮助。顺便说一下,大约有17000首歌曲和1 700张专辑。
有什么问题???
请帮忙!
PS
以下是我的完整代码:https://github.com/vjacheslavravdin/PsyTrance/blob/master/PsyTrance/Program.cs 也许你可以建议如何简化它。
谢谢!
答案 0 :(得分:8)
首先澄清一下:
对于基于批处理的操作,EF并不比其他方法慢得多。在我的测试中,使用原始SQL命令可以获得50%的改进,使用SQL批量复制可能会提高10倍,但作为一般规则,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),但还没有实现它们。
您完全正确决定关闭自动检测更改,每个.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岁左右,以完成所有17k行。
通过使用原始SQL命令执行此操作,您可以将其设置为大约12-20秒;
通过重新实现批量复制,您可以将其降低到2-5秒