What's the fastest way to get an ObjectContext reference from an entity object?

4.6k views Asked by At

I'm creating extensions for my EntityFramework objects as described in How to: Customize Generated Data Objects but in some of those extensions I need to get the instance's ObjectContext to look up some other values in the model. I've found Tip 24 – How to get the ObjectContext from an Entity but that was written a couple years ago, which is referenced in this similar SO question but I'm really hoping there's a better answer now.

Surely this must be something that's needed frequently enough that retrieval of an Entity's object context from the entity itself should be supported with an official method.

Thanks in advance for any more recent information on this implementation.

3

There are 3 answers

1
Ladislav Mrnka On

No there is not any such method. The described workaround looks like the only option because the entity is derived from EntityObject which is defined as:

[Serializable, DataContract(IsReference=true)]
public abstract class EntityObject : StructuralObject, IEntityWithKey,  
    IEntityWithChangeTracker, IEntityWithRelationships
{
    ...
}

As I know only IEntityWithRelationships.RelationshipManager leads to ObjectContext. This wasn't changed in EF 4.

Also it is not really common to access the context from the entity. I can imagine that this can be helpful in case of implementing Active Record Pattern on top of EF but in such case you would also have probably control over creating the context inside the static method of the entity so you should be able to set it to the entity. In other cases I would say that it is something you should avoid as much as possible.

2
Stephen Cleary On

This is what I use; it's a convention-based approach that is simple to add to a project.

First, add hooks to your object context:

public partial class Database1Entities
{
    partial void OnContextCreated()
    {
        this.ObjectMaterialized += (_, e) =>
        {
            try
            {
                dynamic entity = e.Entity;
                entity.ObjectContext = this;
            }
            catch (RuntimeBinderException)
            {
            }
        };
        this.ObjectStateManager.ObjectStateManagerChanged += (_, e) =>
        {
            if (e.Action == CollectionChangeAction.Add)
            {
                try
                {
                    dynamic entity = e.Element;
                    entity.ObjectContext = this;
                }
                catch (RuntimeBinderException)
                {
                }
            }
            else if (e.Action == CollectionChangeAction.Remove)
            {
                try
                {
                    dynamic entity = e.Element;
                    entity.ObjectContext = null;
                }
                catch (RuntimeBinderException)
                {
                }
            }
        };
    }
}

This will attempt to dynamically set a property called ObjectContext on any entity that is associated with the object context.

Next, add an ObjectContext to the entity types:

public partial class Table1
{
    /// <summary> 
    /// Gets or sets the context for this entity.
    /// This should not be set by end-user code; this property will be set
    /// automatically as entities are created or added,
    /// and will be set to <c>null</c> as entities are detached.
    /// </summary> 
    public Database1Entities ObjectContext { get; set; }
}

This solution does require an ObjectContext property to be added to each entity type.

2
Stephen Cleary On

There is another solution, using connected properties.

Using connected properties would look like this (warning: untested code):

public partial class Database1Entities
{
    private struct ObjectContextProperty { }

    partial void OnContextCreated()
    {
        this.ObjectMaterialized += (_, e) =>
        {
            e.Entity.GetConnectedProperty<Database1Entities, ObjectContextProperty>().Set(this);
        };
        this.ObjectStateManager.ObjectStateManagerChanged += (_, e) =>
        {
            if (e.Action == CollectionChangeAction.Add)
            {
                e.Element.GetConnectedProperty<Database1Entities, ObjectContextProperty>().Set(this);
            }
            else if (e.Action == CollectionChangeAction.Remove)
            {
                e.Element.GetConnectedProperty<Database1Entities, ObjectContextProperty>().Set(null);
            }
        };
    }

    /// <summary>
    /// Gets the object context for the entity. Returns <c>null</c> if the entity is detached.
    /// </summary>
    /// <param name="entity">The entity for which to return the object context.</param>
    public static Database1Entities FromEntity(EntityObject entity)
    {
        return entity.GetConnectedProperty<Database1Entities, ObjectContextProperty>().GetOrConnect(null);
    }
}

Then you can use Database1Entities.FromEntity to get the object context from an entity object. You can also define an actual property on the entity objects as well if you want:

public partial class Table1
{
    /// <summary> 
    /// Gets the object context for this entity. Returns <c>null</c> if the entity is detached.
    /// </summary> 
    public Database1Entities ObjectContext { get { return Database1Entities.FromEntity(this); } }
}

In this solution, the ObjectContext property on the entity objects is optional.