32

I am trying to implement caching mechanism for entities. And to use the entities correctly and seamlessly with the caching i need to detach the entity from the current context before i put it in a cache and attach it back the the new context when i get it from the cache. (My context lifetime is per http request)

The requirements are that -

  1. All the navigational properties that are associated with it (which i have already populated) should not be removed when the entity is detached.
  2. I can update the cached items if i want ( so correctly attaching them to the new context is important).

This is my attempt at creating an EntityCache class - (ServerCache here is my wrapper class that pushes the object to ASP.NET cache)

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);
            }
        }
    }

When i put an entity in the cache it is of type DynamicProxy and NOT the real class.

Attaching doesnt work at all - i get an exception that i cannot case object that is of type Dynamic_{blahblah} to IEntityWithKey.

I just saw these examples of attach and detach online and tried them, I am open to any new implementation of the Attach/Detach methods here.

Thank you.

Follow up question -

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

Works, but makes all the navigational properties that are loaded NULL, how do we make it keep the navigational properties and NOT replace(or lose) them with NULL when we detach from context.

MoXplod
  • 3,813
  • 6
  • 36
  • 46
  • 2
    Is it normal that the whole question doesn't have any question mark? :) – Patrick Desjardins Oct 06 '11 at 20:14
  • 1
    It is more of -- what am i doing wrong here, hence the title "how to do it correctly", Thought it was clear - but will add a '?' next time :) – MoXplod Oct 06 '11 at 20:18
  • 1
    Remove the cast to `IEntityWithKey`, you don't need it since `ObjectContext.Attach` accepts an `object`. If you want the "real" class turn off everything that creates proxies (lazy loading, change tracking with proxies) - including their possible benefits. I don't think that you can retrieve the entity as a member or by a method from the proxy because the proxy *inherits* from the entity. So, the proxy *IS* the entity and does not *HAVE* the entity. You will have a conflict though because you apparently expect your entities to implement `IEntityWithChangeTracker`. – Slauma Oct 06 '11 at 22:13
  • Another thing I don't understand is why you want to tie context and your cache so close together? There are situations - and especially in web apps it's the majority = most GET requests - where you'll load entities from the DB which are not attached to the context at all (with `NoTracking` options), so you don't need to detach them. And on the other side why do you always want to attach entities to the context when you retrieve them from the cache? Attaching is mainly for change tracking. But when you can serve a GET request from you cache, why change tracking or a context at all? – Slauma Oct 06 '11 at 22:27
  • Thanks, i agree i dont need to attach it every time i get it from the cache, only need it on when i need to UPDATE the cached entity. But is there a performance hit on attaching? why shouldn't i just attach every time? Also for your first comment, i do need the lazy loading, change tracking etc in general and DONT want to turn off "everything that creates proxies" – MoXplod Oct 06 '11 at 22:56
  • Yes, there is a performance hit (http://stackoverflow.com/questions/5917478/what-causes-attach-to-be-slow-in-ef4) but probably only to consider for "mass attaches". Regarding the proxies I wanted to say that you likely don't get the "real" entities out of them (unless you create a new one and copy property by property or you avoid them in the first place, meaning you turn off "everything that creates proxies" :) Let's see if someone has an idea... – Slauma Oct 06 '11 at 23:31
  • ((IObjectContextAdapter)context).ObjectContext.Attach((IEntityWithKey)entity) .. btw. if i remove IEntityWithKey it doesnt compile as it only takes IEntityWIthKey – MoXplod Oct 06 '11 at 23:39
  • Sorry, yes, I confused it with `AttachTo` (but this also needs the entity set name as additional parameter). – Slauma Oct 07 '11 at 09:40

2 Answers2

38

IEntityWithKey is interface for other types of entities. It is for "big" entities. For example EntityObject implement this interface. These entities are not considered as POCO and are not supported by DbContext API.

If you want to use IEntityWithKey your classes must implement it - it is not something that would happen automatically.

Correct attaching with DbContext API should be:

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

and this should hopefully also work:

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

Correct detaching with DbContext API should be:

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

Also it is better to you generic methods instead of object.

Dave New
  • 38,496
  • 59
  • 215
  • 394
Ladislav Mrnka
  • 360,892
  • 59
  • 660
  • 670
  • 3
    These work - but my navigational properties are null when i detach, is there a way to detach without them becoming null since i load them before pushing to the cache; var trackedEntity = entity as IEntityWithChangeTracker; trackedEntity.SetChangeTracker(null); does NOT do the trick – MoXplod Oct 07 '11 at 18:34
  • 3
    I think `dbContext.Set(typeof(entity)).Attach(entity);` should be `dbContext.Set(entity.GetType()).Attach(entity);` isn't it? – Learner Jul 07 '14 at 14:52
10

To your follow-up question:

...how do we make it keep the navigational properties and NOT replace(or lose) them with NULL when we detach from context...

I believe it's not possible to detach an object graph from the context while maintaining it's navigation properties. From MSDN:

In an independent association, the relationship information is not maintained for a detached object.

Although this statement is about independent associations it does not mean that navigation properties are maintained in foreign key assocation (relationships which expose a foreign key property in the model). Yes, "Relationship information" is maintained because the foreign key properties (which are scalar properties) will be alive and contain the correct foreign key value after detaching. But the corresponding navigation properties will still be null for reference properties or, for navigation collections the reference will be removed from the collection.

I think the only way to detach a complete object graph from a context is either disposing the context altogether or create a copy of the graph before starting to detach the original graph. Creating a copy would require writing Clone methods which copy all the properties and navigate through the graph or using a "trick" like this which serializes the graph into a binary stream and then deserializes it back to new objects. For this the entities would need to be serializable. Also reference cycles (which we often have when using bidirectional navigation properties between entities) can cause trouble. (Also attention if your objects are proxies which contain references to EF's internal objects and which you probably don't want to copy, serialize and deserialize.)

In this aspect Detach is not the counterpart of Attach because it behaves quite differently: Attach attaches the whole object graph and maintains navigation properties. Detach detaches only single entities without the related objects and destroys navigation properties. From the same page linked above:

Detach only affects the specific object passed to the method. If the object being detached has related objects in the object context, those objects are not detached.

I could imagine that this is the main reason why the DbContext API doesn't have an explicite Detach method (in contrast to ObjectContext) - detaching is considered as an advanced feature which does not behave as one might expect.

MSDN mentions as the only reason to detach an object from the context "to conserve resources" (again the article above):

In Entity Framework applications, you can detach objects from the object context. You might detach objects to conserve resources, as executing repeated queries in the same object context increases the memory requirements of the object context.

I think this method is just not prepared and designed for working with the objects after they've been detached from the context. It's main purpose is to release them for immediate garbage collection.

Community
  • 1
  • 1
Slauma
  • 175,098
  • 59
  • 401
  • 420