当应该有速度时,实体框架会变慢

时间:2012-01-03 03:30:11

标签: asp.net-mvc performance entity-framework linq-to-entities

我遇到的问题是,如果不应该将子对象添加到父对象的速度非常慢。有成千上万个子对象(在这种情况下为33k记录),但这些对象都不是父对象的子对象。

当我将第一个孩子添加到父母时,需要一分钟以上才能完成:

public class ParentEntity // POCO generated by EF TT4 templates
{
    public virtual int Id { get; set; }
    public virtual ICollection<ChildEntity> ChildEntities  {}
} 

public class ChildEntity // POCO generated by EF TT4 templates
{
    public virtual int Id { get; set; }
    public virtual int ParentEntityId { get; set; }
    public virtual ParentEntity ParentEntity { get; set; }
    public virtual Warehouse Warehouse  { get; set; }
    public virtual WarehouseLocation  WarehouseLocation  { get; set; }
}

public class Warehouse { // etc } // another POCO class
public class WarehouseLocation { // etc } // another POCO class

// somewhere in a controller action method...
var parent = _parentEntityService.GetBy(id);
var child = new ChildEntity{ ParentEntityId = id, 
                             WarehouseId = id2, WarehouseLocationId = id3 };

// ChildEntities.Add() takes more than one minute to add the 
// first and only child to this parent
// why would this be so incredibly slow?

parent.ChildEntities.Add(child);

在EntityFramework中找到速度问题的最佳方法是什么?

更新:EFProf显示它发出三个SQL查询:

SELECT * FROM ChildEntities where ParentId = id
SELECT * FROM ChildEntities where WarehouseId = id2
SELECT * FROM ChildEntities where WarehouseLocation = id3

为什么它会为每个ChildEntity加载这些,当它只应为当前子项加载它们时?

编辑2:根据@LadislavMrnka,额外的查询是由模板的Fixup方法引起的。但是当我注释掉那些方法并注释掉对Fixup的调用时,它仍然很慢。这不是删除修复的正确方法(看起来它已被删除):

public class ChildEntity {
public virtual Warehouse Warehouse
{
    get { return _warehouse; }
    set
    {
        if (!ReferenceEquals(_warehouse, value))
        {
            var previousValue = _warehouse;
            _warehouse = value;
            //FixupWarehouse(previousValue); // commented out
        }
    }
}

2 个答案:

答案 0 :(得分:5)

这是你的问题:

var child = new ChildEntity 
               { 
                   Foo = "", 
                   Bar = "", 
                   ParentEntityId = id, 
                   WarehouseId = 1, 
                   WarehouseLocationId = 1 
               };

parent.ChildEntities.Add(child);

恕我直言,这完全是关于POCO模板生成的代码中隐藏的修复集合。 Fixup +延迟加载=性能问题。 Fixup尝试使模型中的所有内容保持同步。这意味着如果您设置导航属性或FK属性的一侧,它将尝试确保关系另一侧的导航属性也反映了该更改。问题是如果没有加载导航属性,它将触发延迟加载。在您的情况下,看起来设置Warehouse首先修复了ChildEntity中的导航属性,之后尝试修复Warehouse实例上的导航属性,但未加载其子实体集合=&gt;延迟加载导致

SELECT * FROM ChildEntities where WarehouseId = some id

WarehouseLocation的情况也是如此。第一个查询是将父项添加到父实体上的未加载集合的结果。

解决方案是修改模板并删除所有修正(例如,EFv4.1的DbContext POCO模板+不再使用修正)或通过调用以下命令关闭延迟加载:

context.ContextOptions.LazyLoadingEnabled = false;

// Your insert logic here

context.ContextOptions.LazyLoadingEnalbed = true;

您甚至可以将代码包装在自定义IDisposable中,如:

public class DisableLazyLoadingScope : IDisposable
{
    private readonly ObjectContext context;

    public DisableLazyLoadingScope(ObjectContext context)
    {
        this.context = context;
        context.ContextOptions.LazyLoadingEnabled = false;
    }

    public void Dispose()
    {
        context.ContextOptions.LazyLoadingEnabled = true;
    }
}

并使用它:

using (new DisableLazyLoadingScope(context)
{
    // Your insert logic here
}

答案 1 :(得分:0)

这极大地帮助了我:

context.Configuration.AutoDetectChangesEnabled = false;

我不确定这是否适合你,但如果我正确理解你的问题,当你执行Add时,你的速度很慢?

这仍然允许您插入对象,但EF在导航对象层次结构时将花费更少的精力。我相信在更新实体时这是有问题的,但我不确定。

第二种方法(假设您使用DbContext)是:

using (var dbCtx = new MyDataContext())
{
  var ctx = ((IObjectContextAdapter)dbCtx).ObjectContext;

  var customers = ctx.CreateObjectSet<Customer>();

  customers.AddObject(customer);
}

因为ObjectContext在内部处理与更改有关的事情。我现在找不到这个来源,我会试着稍后再找到它。