Entity Framework медлительность, когда должна быть скорость - PullRequest
1 голос
/ 03 января 2012

У меня есть проблема, когда добавление дочернего объекта к родительскому объекту происходит исключительно медленно, когда этого не должно быть. Существуют десятки тысяч дочерних объектов (в данном случае 33 тыс. Записей), но ни один из них не является дочерним для рассматриваемого родительского объекта.

Когда я добавляю первого потомка к родителю, для его завершения требуется больше одной минуты:

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 ]

5 голосов
/ 03 января 2012

Это ваша проблема:

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

parent.ChildEntities.Add(child);

ИМХО, это все о коллекциях исправлений, скрытых в коде, сгенерированном шаблоном POCO.Fixup + отложенная загрузка = проблемы с производительностью.Fixup пытается синхронизировать все в вашей модели.Это означает, что если вы установите одну сторону свойства навигации или свойства FK, он попытается убедиться, что свойство навигации на противоположной стороне отношения также отражает изменение.Проблема в том, что если свойство навигации не загружено, оно будет вызывать отложенную загрузку.В вашем случае это выглядит как установка Warehouse сначала исправила свойство навигации в ChildEntity и после этого пыталась исправить свойство навигации в экземпляре Warehouse, но его коллекция дочерних объектов не была загружена => отложенная загрузка, вызывающая

SELECT * FROM ChildEntities where WarehouseId = some id

То же самое произошло в случае WarehouseLocation.Первый запрос является результатом добавления дочернего элемента в незагруженную коллекцию родительской сущности.

Решение состоит в том, чтобы либо изменить шаблон и избавиться от всех исправлений (например, шаблон DbContext POCO для EFv4.1 + не использоватьисправления) или отключите отложенную загрузку для этой операции, вызвав:

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
}
0 голосов
/ 03 января 2012

Это мне очень помогло:

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 внутренне обрабатывает вещи по отношению к изменениям. Я не могу найти источник по этому вопросу прямо сейчас, я постараюсь найти его позже.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...