实体框架6代码首先多对多插入慢

时间:2016-04-29 13:22:28

标签: performance entity-framework ef-code-first entity-framework-6 many-to-many

我正在尝试使用以下代码找到改善插入性能的方法(请在代码块之后阅读我的问题):

//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优化我的软件中的任何操作。

谢谢!

2 个答案:

答案 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);

虽然它可能不优雅,但性能会有所提升。