我正试图用以下代码找到一种提高插入性能的方法(请在代码块后阅读我的问题):
//Domain classes
[Table("Products")]
public class Product
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Sku { get; set; }
[ForeignKey("Orders")]
public virtual ICollection<Order> Orders { get; set; }
public Product()
{
Orders = new List<Order>();
}
}
[Table("Orders")]
public class Order
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Title { get; set; }
public decimal Total { get; set; }
[ForeignKey("Products")]
public virtual ICollection<Product> Products { get; set; }
public Order()
{
Products = new List<Product>();
}
}
//Data access
public class MyDataContext : DbContext
{
public MyDataContext()
: base("MyDataContext")
{
Configuration.LazyLoadingEnabled = true;
Configuration.ProxyCreationEnabled = true;
Database.SetInitializer(new CreateDatabaseIfNotExists<MyDataContext>());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>().ToTable("Products");
modelBuilder.Entity<Order>().ToTable("Orders");
}
}
//Service layer
public interface IServices<T, K>
{
T Create(T item);
T Read(K key);
IEnumerable<T> ReadAll(Expression<Func<IEnumerable<T>, IEnumerable<T>>> pre);
T Update(T item);
void Delete(K key);
void Save();
void Dispose();
void BatchSave(IEnumerable<T> list);
void BatchUpdate(IEnumerable<T> list, Action<UpdateSpecification<T>> spec);
}
public class BaseServices<T, K> : IDisposable, IServices<T, K> where T : class
{
protected MyDataContext Context;
public BaseServices()
{
Context = new MyDataContext();
}
public T Create(T item)
{
T created;
created = Context.Set<T>().Add(item);
return created;
}
public void Delete(K key)
{
var item = Read(key);
if (item == null)
return;
Context.Set<T>().Attach(item);
Context.Set<T>().Remove(item);
}
public T Read(K key)
{
T read;
read = Context.Set<T>().Find(key);
return read;
}
public IEnumerable<T> ReadAll(Expression<Func<IEnumerable<T>, IEnumerable<T>>> pre)
{
IEnumerable<T> read;
read = Context.Set<T>().ToList();
read = pre.Compile().Invoke(read);
return read;
}
public T Update(T item)
{
Context.Set<T>().Attach(item);
Context.Entry<T>(item).CurrentValues.SetValues(item);
Context.Entry<T>(item).State = System.Data.Entity.EntityState.Modified;
return item;
}
public void Save()
{
Context.SaveChanges();
}
}
public interface IOrderServices : IServices<Order, int>
{
//custom logic goes here
}
public interface IProductServices : IServices<Product, int>
{
//custom logic goes here
}
//Web project's controller
public ActionResult TestCreateProducts()
{
//Create 100 new rest products
for (int i = 0; i < 100; i++)
{
_productServices.Create(new Product
{
Sku = i.ToString()
});
}
_productServices.Save();
var products = _productServices.ReadAll(r => r); //get a list of saved products to add them to orders
var random = new Random();
var orders = new List<Order>();
var count = 0;
//Create 3000 orders
for (int i = 1; i <= 3000; i++)
{
//Generate a random list of products to attach to the current order
var productIds = new List<int>();
var x = random.Next(1, products.Count() - 1);
for (int j = 0; j < x; j++)
{
productIds.Add(random.Next(products.Min(r => r.Id), products.Max(r => r.Id)));
}
//Create the order
var order = new Order
{
Title = "Order" + i,
Total = i,
Products = products.Where(p => productIds.Contains(p.Id))
};
orders.Add(order);
}
_orderServices.CreateRange(orders);
_orderServices.Save();
return RedirectToAction("Index");
}
此代码运行良好,但在执行SaveChanges时速度非常慢。
在场景背后,域对象上的注释创建了所需的所有关系:自动创建具有适当外键的OrderProducts表,EF正确地执行插入。
我尝试过很多使用EntityFramework.Utilities、SqlBulkCopy等进行批量插入的方法,但都不起作用。有办法做到这一点吗?理解这只是为了测试目的,我的目标是尽我所能优化使用EF的软件中的任何操作。
谢谢!
在执行插入之前,禁用上下文的AutoDetectChangesEnabled(通过将其设置为false)。执行插入操作,然后将AutoDetectChangesEnabled设置回true,例如。;
try
{
MyContext.Configuration.AutoDetectChangesEnabled = false;
// do your inserts updates etc..
}
finally
{
MyContext.Configuration.AutoDetectChangesEnabled = true;
}
您可以在这里找到更多关于这项工作的信息
我看到了代码速度慢的两个原因。
添加与添加范围
使用Create方法逐个添加实体。
您应该始终使用AddRange而不是Add。每次调用Add方法时,Add方法都会尝试DetectChanges,而AddRange只调用一次。
您应该在代码中添加一个"CreateRange"方法。
public IEnumerable<T> CreateRange(IEnumerable<T> list)
{
return Context.Set<T>().AddRange(list);
}
var products = new List<Product>();
//Create 100 new rest products
for (int i = 0; i < 100; i++)
{
products.Add(new Product { Sku = i.ToString() });
}
_productServices.CreateRange(list);
_productServices.Save();
禁用/启用属性AutoDetectChanges也可以像@mark_h建议的那样工作,但我个人不喜欢这种解决方案。
数据库往返
添加、修改或删除每条记录都需要数据库往返。因此,如果您插入3000条记录,那么将需要3000个数据库往返,这是非常缓慢的。
您已经尝试过EntityFramework.BulkInsert或SqlBulkCopy,这很好。我建议您首先使用"AddRange"修复程序重试,以查看新的性能。
以下是对EF支持BulkInsert的库的有偏比较:实体框架-批量插入库评论&比较
免责声明:我是项目实体框架扩展的所有者
此库允许您在数据库中进行BulkSaveChanges、BulkInsert、BulkUpdate、BulkDelete和BulkMerge。
它支持所有的继承和关联。
// Easy to use
public void Save()
{
// Context.SaveChanges();
Context.BulkSaveChanges();
}
// Easy to customize
public void Save()
{
// Context.SaveChanges();
Context.BulkSaveChanges(bulk => bulk.BatchSize = 100);
}
编辑:添加子问题的答案
实体对象不能由的多个实例引用IEntityChangeTracker
发生此问题是因为您使用了两个不同的DbContext。一个用于产品,一个用于订单。
你可能会在不同的线索中找到比我更好的答案,比如这个答案。
Add方法成功地附加了产品,随后对同一产品的调用不会抛出错误,因为它是同一产品。
然而,AddRange方法会多次附加产品,因为它不是来自同一个上下文,所以当调用Detect Changes时,他不知道如何处理它
修复它的一种方法是重新使用相同的上下文
var _productServices = new BaseServices<Product, int>();
var _orderServices = new BaseServices<Order, int>(_productServices.Context);
虽然它可能不优雅,但性能会有所提高。