Правильное присоединение и отсоединение сущностей от контекста в EF4.1

Я пытаюсь реализовать механизм кеширования для сущностей. А чтобы правильно и беспрепятственно использовать сущности с кешированием, мне нужно отсоединить сущность от текущего контекста, прежде чем я помещу ее в кеш, и прикреплю обратно к новому контексту, когда я получу его из кеша. (Время жизни моего контекста зависит от HTTP-запроса)

Требования таковы:

  1. Все связанные с ним свойства навигации (которые я уже заполнил) не должны удаляться при отсоединении объекта.
  2. Я могу обновить кешированные элементы, если захочу (поэтому важно правильно прикрепить их к новому контексту).

Это моя попытка создать класс EntityCache - (ServerCache - это мой класс-оболочка, который помещает объект в кеш ASP.NET)

public static class EntityCache
    {
        private static DbContext context
        {
            get
            {
                return (DbContext)HttpContext.Current.Items[ObjectKeys.ContextRequestItemKey];
            }
        }

        private static void Detach(object entity)
        {
            var trackedEntity = entity as IEntityWithChangeTracker;
            trackedEntity.SetChangeTracker(null);
            ((IObjectContextAdapter)context).ObjectContext.Detach(entity);
        }

        private static void Attach(object entity)
        {
            ((IObjectContextAdapter)context).ObjectContext.Attach((IEntityWithKey)entity);
        }

        public static void Remove(string key)
        {
            ServerCache.Remove(key);
        }

        public static object Get(string key)
        {
            object output = ServerCache.Get(key);
            if (output != null)
                Attach(output);
            return output;
        }

        public static void ShortCache(String key, object data)
        {
            if (data != null)
            {
                Detach(data);
                ServerCache.ShortCache(key, data);
            }
        }

        public static void LongCache(String key, object data)
        {
            if (data != null)
            {
                Detach(data);
                ServerCache.LongCache(key, data);
            }
        }
    }

Когда я помещаю объект в кеш, он имеет тип DynamicProxy, а НЕ настоящий класс.

Прикрепление вообще не работает - я получаю исключение, что я не могу связать объект типа Dynamic_ {blahblah} с IEntityWithKey.

Я только что видел эти примеры присоединения и отсоединения в Интернете и пробовал их, я открыт для любой новой реализации методов присоединения / отсоединения здесь.

Спасибо.

Последующий вопрос -

context.Entry(entity).State = EntityState.Detached;

Работает, но делает все загруженные свойства навигации NULL, как сделать так, чтобы он сохранял свойства навигации и НЕ заменял (или не терял) их на NULL, когда мы отсоединяемся от контекста.


person MoXplod    schedule 06.10.2011    source источник
comment
Это нормально, что во всем вопросе нет вопросительного знака? :)   -  person Patrick Desjardins    schedule 07.10.2011
comment
Это больше из - что я здесь делаю не так, отсюда и заголовок, как это сделать правильно. Думал, что это понятно - но добавлю "?" в следующий раз :)   -  person MoXplod    schedule 07.10.2011
comment
Удалите приведение к IEntityWithKey, оно вам не нужно, поскольку ObjectContext.Attach принимает object. Если вы хотите, чтобы настоящий класс отключил все, что создает прокси (отложенная загрузка, отслеживание изменений с помощью прокси), включая их возможные преимущества. Я не думаю, что вы можете получить объект как член или методом от прокси, потому что прокси наследует от объекта. Итак, прокси ЯВЛЯЕТСЯ сущностью и не ИМЕЕТ сущность. Однако у вас будет конфликт, потому что вы, очевидно, ожидаете, что ваши объекты будут реализовывать IEntityWithChangeTracker.   -  person Slauma    schedule 07.10.2011
comment
Еще я не понимаю, почему вы хотите связать контекст и кеш так близко друг к другу? Бывают ситуации - и особенно в веб-приложениях это большинство = большинство запросов GET - когда вы загружаете объекты из БД, которые вообще не привязаны к контексту (с параметрами NoTracking), поэтому вам не нужно их отсоединять . А с другой стороны, почему вы всегда хотите присоединять сущности к контексту, когда извлекаете их из кеша? Прикрепление в основном предназначено для отслеживания изменений. Но если вы можете обслуживать GET-запрос из своего кеша, зачем вообще изменять отслеживание или контекст?   -  person Slauma    schedule 07.10.2011
comment
Спасибо, я согласен, мне не нужно прикреплять его каждый раз, когда я получаю его из кеша, он нужен только тогда, когда мне нужно ОБНОВИТЬ кешированный объект. Но есть ли снижение производительности при прикреплении? почему бы мне просто не прикреплять каждый раз? Также для вашего первого комментария мне нужна ленивая загрузка, отслеживание изменений и т. Д. В целом, и НЕ хочу отключать все, что создает прокси.   -  person MoXplod    schedule 07.10.2011
comment
Да, наблюдается снижение производительности (stackoverflow.com/questions/5917478/), но, вероятно, следует учитывать только при массовом присоединении. Что касается прокси, я хотел сказать, что вы, скорее всего, не получите из них настоящие сущности (если только вы не создадите новый и не скопируете свойство по свойству, либо вы не будете их избегать в первую очередь, то есть отключите все, что создает прокси: ) Посмотрим, есть ли у кого-нибудь идея ...   -  person Slauma    schedule 07.10.2011
comment
((IObjectContextAdapter) контекст) .ObjectContext.Attach ((IEntityWithKey) entity) .. кстати. если я удалю IEntityWithKey, он не компилируется, так как принимает только IEntityWIthKey   -  person MoXplod    schedule 07.10.2011
comment
Извините, да, я перепутал это с AttachTo (но для этого также требуется имя набора сущностей в качестве дополнительного параметра).   -  person Slauma    schedule 07.10.2011


Ответы (2)


IEntityWithKey - интерфейс для других типов сущностей. Это для «больших» организаций. Например EntityObject реализовать этот интерфейс. Эти объекты не считаются POCO и не поддерживаются DbContext API.

Если вы хотите использовать IEntityWithKey, ваши классы должны реализовать это - это не произойдет автоматически.

Правильное подключение с DbContext API должно быть:

dbContext.Set(typeof(entity)).Attach(entity); 

и это, надеюсь, также должно сработать:

dbContext.Entry(entity).State = EntityState.Unchanged;

Правильное отключение с DbContext API должно быть:

dbContext.Entry(entity).State = EntityState.Detached;

Также лучше использовать общие методы вместо object.

person Ladislav Mrnka    schedule 07.10.2011
comment
Они работают - но мои навигационные свойства равны нулю, когда я отсоединяюсь, есть ли способ отсоединиться, чтобы они не становились равными нулю, поскольку я загружаю их перед отправкой в ​​кеш; var trackedEntity = entity as IEntityWithChangeTracker; trackedEntity.SetChangeTracker (ноль); НЕ помогает - person MoXplod; 07.10.2011
comment
Я думаю, dbContext.Set(typeof(entity)).Attach(entity); должно быть dbContext.Set(entity.GetType()).Attach(entity);, не так ли? - person Learner; 07.07.2014

На следующий вопрос:

... как сделать так, чтобы он сохранял свойства навигации и НЕ заменял (или не терял) их на NULL, когда мы отключаемся от контекста ...

Я считаю, что невозможно отделить граф объекта от контекста, сохранив его свойства навигации. Из MSDN:

В независимой ассоциации информация о взаимосвязи для отдельного объекта не поддерживается.

Хотя это утверждение относится к независимым ассоциациям, это не означает, что свойства навигации поддерживаются во взаимосвязи внешнего ключа (отношения, которые раскрывают свойство внешнего ключа в модели). Да, «информация о взаимосвязи» сохраняется, потому что свойства внешнего ключа (которые являются скалярными свойствами) будут активны и будут содержать правильное значение внешнего ключа после отсоединения. Но соответствующие свойства навигации по-прежнему будут null для свойств ссылки или, для коллекций навигации, ссылка будет удалена из коллекции.

Я думаю, что единственный способ отделить полный граф объекта от контекста - это либо полностью удалить контекст, либо создать копию графа перед тем, как начать отсоединение исходного графа. Для создания копии потребуется написать Clone методы, которые копируют все свойства и перемещаться по графику, или использовать «трюк» вроде this, который сериализует граф в двоичный поток, а затем десериализует его обратно в новые объекты. Для этого сущности должны быть сериализуемыми. Также могут возникнуть проблемы с циклами ссылок (которые мы часто имеем при использовании свойств двунаправленной навигации между объектами). (Также обратите внимание, если ваши объекты являются прокси-серверами, которые содержат ссылки на внутренние объекты EF и которые вы, вероятно, не хотите копировать, сериализовать и десериализовать.)

В этом аспекте Detach не является эквивалентом Attach, потому что он ведет себя совершенно иначе: Attach присоединяет весь граф объекта и поддерживает свойства навигации. Detach отделяет только отдельные объекты без связанных объектов и уничтожает свойства навигации. С той же страницы, указанной выше:

Отсоединение влияет только на конкретный объект, переданный методу. Если отсоединяемый объект имеет связанные объекты в контексте объекта, эти объекты не отсоединяются.

Я могу представить, что это основная причина, по которой DbContext API не имеет явного Detach метода (в отличие от ObjectContext) - отсоединение рассматривается как расширенная функция, которая не работает так, как можно было бы ожидать.

MSDN упоминает как единственную причину для отсоединения объекта от контекста «для экономии ресурсов» (снова в статье выше):

В приложениях Entity Framework вы можете отсоединять объекты от контекста объекта. Вы можете отсоединять объекты для экономии ресурсов, поскольку выполнение повторяющихся запросов в одном и том же контексте объекта увеличивает требования к памяти для контекста объекта.

Я думаю, что этот метод просто не подготовлен и не предназначен для работы с объектами после того, как они были отделены от контекста. Его основная цель - освободить их для немедленной сборки мусора.

person Slauma    schedule 07.10.2011