我正在尝试使用以下代码找到改善插入性能的方法(请在代码块之后阅读我的问题):
//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表,并且正确地完成插入。
我尝试使用EntityFramework.Utilities,SqlBulkCopy等批量插入很多东西......但都没有用。 有没有办法实现这个目标? 了解这仅用于测试目的,我的目标是使用EF优化我的软件中的任何操作。
谢谢!
答案 0 :(得分:0)
在您执行插入操作之前,请禁用上下文的AutoDetectChangesEnabled(通过将其设置为false)。进行插入,然后将AutoDetectChangesEnabled设置为true,例如;
try
{
MyContext.Configuration.AutoDetectChangesEnabled = false;
// do your inserts updates etc..
}
finally
{
MyContext.Configuration.AutoDetectChangesEnabled = true;
}
您可以找到有关这方面的更多信息here
答案 1 :(得分:0)
我看到你的代码速度慢的两个原因。
添加与AddRange
使用创建方法逐个添加实体。
您应该始终使用AddRange而不是Add。每次在AddRange上调用add方法时,Add方法将尝试DetectChanges。
您应该添加&#34; CreateRange &#34;代码中的方法。
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,这很棒。我建议您先使用&#34; AddRange &#34;再次尝试使用它们。修复以查看新的表现。
以下是支持BulkInsert for 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);
虽然它可能不优雅,但性能会有所提升。