0

我試圖找到一種方法來改善與下面的代碼插入表演(請閱讀代碼塊後,我的問題):實體框架6碼第一多對多插入慢

//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的軟件中的任何操作。

謝謝!

回答

0

就在插入之前,禁用上下文的AutoDetectChangesEnabled(將其設置爲false)。執行插入操作,然後將AutoDetectChangesEnabled設置爲true,例如;

 try 
     { 
      MyContext.Configuration.AutoDetectChangesEnabled = false; 
      // do your inserts updates etc.. 
     } 
     finally 
     { 
      MyContext.Configuration.AutoDetectChangesEnabled = true; 
     } 

你可以找到更多的信息,這是什麼做here

0

我看到兩個原因使您的代碼很慢。

添加對的AddRange

您可以通過一個採用創建方法添加實體之一。

您應該始終使用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建議,但我個人不喜歡這種解決方案。

數據庫往返

數據庫往返需要對每條記錄的添加,修改或刪除。因此,如果您插入3,000條記錄,則需要3,000個數據庫往返行程,這非常緩慢。

您已經嘗試過EntityFramework.BulkInsert或SqlBulkCopy,這很棒。我建議您先使用「AddRange」修復程序再次嘗試,以查看最新的效果。

這裏是圖書館支持BulkInsert爲EF的偏見比較: Entity Framework - Bulk Insert Library Reviews & Comparisons

免責聲明:我的項目Entity Framework Extensions

這個庫可以讓你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。一個用於產品,另一個用於訂購。

你可能會發現一個更好的答案比我在這個answer不同的線程。

Add方法成功附加產品,同一產品的後續調用不會因爲它是同一產品而引發錯誤。

但是,AddRange方法會將產品多次附加,因爲它不是來自同一個上下文,所以當調用Detect Changes時,他不知道如何處理它。

一個解決它的方法是通過重複使用相同的上下文

var _productServices = new BaseServices<Product, int>(); 
var _orderServices = new BaseServices<Order, int>(_productServices.Context); 

儘管它可能不是優雅,性能將得到改善。

+0

它的速度更快,但聽起來像它不喜歡兒童參考的對象。說:一個實體對象不能被多個IEntityChangeTracker實例引用。 –