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

JK.*_*JK. 1 asp.net-mvc performance linq-to-entities entity-framework

我有一个问题,如果不应该将子对象添加到父对象异常缓慢.有成千上万个子对象(在这种情况下为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);
Run Code Online (Sandbox Code Playgroud)

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

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

SELECT * FROM ChildEntities where ParentId = id
SELECT * FROM ChildEntities where WarehouseId = id2
SELECT * FROM ChildEntities where WarehouseLocation = id3
Run Code Online (Sandbox Code Playgroud)

为什么它只为每个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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Lad*_*nka 5

这是你的问题:

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

parent.ChildEntities.Add(child);
Run Code Online (Sandbox Code Playgroud)

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

SELECT * FROM ChildEntities where WarehouseId = some id
Run Code Online (Sandbox Code Playgroud)

同样的情况发生在WarehouseLocation.第一个查询是在父实体上将子项添加到未加载的集合的结果.

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

context.ContextOptions.LazyLoadingEnabled = false;

// Your insert logic here

context.ContextOptions.LazyLoadingEnalbed = true;
Run Code Online (Sandbox Code Playgroud)

您甚至可以将代码包装在自定义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;
    }
}
Run Code Online (Sandbox Code Playgroud)

并使用它像:

using (new DisableLazyLoadingScope(context)
{
    // Your insert logic here
}
Run Code Online (Sandbox Code Playgroud)