0

In Entity Framework, is it possible to make the framework inject the DbContext into each object (entity) that is attached to or retrieved from the Context?

I'm an NHibernate guy and I know it is possible in NH, -- sorry if it is a stupid question in EF world.

Essentially, I want some of my entities to have a property of type DbContext that will get set to the instance of the context by the framework itself, whenever I associate the entity with the context. Ideally such classes will be marked with IContextAware marker interface or something like that.

The reason I want to do this is (=goal), I want to avoid Anemic Domain Model anti-pattern this time around. I figured if I have ObjectContext injected into entities, they will be able to access DB, thereby allowing me to implement queries and more complex logic right inside domain classes themselves. If you know other ways to accomplish my goal (esp. in context of web app) please do, but please try to avoid answers like "you shouldn't do this because". Thanks!!!

Andriy Volkov
  • 18,653
  • 9
  • 68
  • 83
  • Why not put the current DbContext into a thread-local? – Jeffrey Hantin Jan 29 '11 at 03:55
  • 1
    Honestly, I suspect that you misunderstand the meaning of Anemic Domain. Why do you think that having a reference to a persistence-related object will solve the problem? – anon Jan 29 '11 at 04:21
  • @Jeffrey I thought about that, it is certainly my second option after native injection – Andriy Volkov Jan 31 '11 at 12:43
  • @anon Because then I will be able to implement "business logic" (domain-related behavior) inside the object itself, and in many cases that requires querying from or writing to the database. Without the access to persistence most of domain logic must reside outside of entities, making them almost into DTOs. What's your understanding of Anemic Domain Model? – Andriy Volkov Jan 31 '11 at 12:48
  • NHibernate dodges the whole issue by doing things like plugging in database-backed collection implementations for associations or change-scanning retrieved objects on session close so database-oblivious domain object code can still traverse associations, create/update/delete, etc. – Jeffrey Hantin Jan 31 '11 at 20:02
  • @Jeffrey, doesn't EF do the same??? – Andriy Volkov Feb 01 '11 at 01:10
  • @zvolkov The point being, you don't need a reference to the NHibernate session to do those things. I'm just trying to figure out what the use case is for needing the DbContext within your domain models. – Jeffrey Hantin Feb 01 '11 at 05:11
  • @zvolkov I guess what I'm driving at is that you should be able to use LINQ over the association collections, and if the collection implementation happens to be database-backed, – Jeffrey Hantin Feb 01 '11 at 19:36
  • ... using .AsQueryable() on the collection should plug your queries back into the ORM for execution at the DB. That way you can keep your domain objects persistence-agnostic. – Jeffrey Hantin Feb 01 '11 at 19:44

3 Answers3

2

You shouldn't do this because you want to keep persistence concerns out of your domain objects =)

But if you MUST, you can hook into the ObjectMaterialized event fired by ObjectContext. In CTP5, you need to cast your DbContext like so in the constructor for your DbContext:

((IObjectContextAdapter)this).ObjectContext.ObjectMaterialized += 
    this.ObjectContext_OnObjectMaterialized;

Then implement your function ObjectContext_OnObjectMaterialized(object sender, ObjectMaterializedEventArgs e). Via the EventArgs, you will be able to access your object, which has just been materialized. From there, you can set your POCO's ObjectContext/DbContext property, which has to be either public or internal.

anon
  • 4,578
  • 3
  • 35
  • 54
  • "you want to keep" -- I don't! If everybody says something is right, it does not mean it actually is. Especially considering how many people tend to repeat each other without understanding why :) Thanks, I will try to implement it the way you described and see if my idea leads to problems. – Andriy Volkov Jan 31 '11 at 12:51
  • I'm a bit new to Entity Framework and Domain Driven Design, and I can think of some instances where I wish that my objects 'knew' more about how to query themselves and other objects. I would love to hear more about the specific scenario you're facing. – anon Feb 01 '11 at 03:29
1

Besides coupling your domain to a specific persistance technology, there are other conserns with injecting the context at that level. For instance, what is the lifetime of the context you inject and should that context always have the same lifetime for each entity?

I understand you want to define your business methods on the entities, so you can say customer.MakeCustomerPreferred. There are however, other ways to do this, without having to write the business logic at that level in the application`. For instance, you can use business events. Here's an example:

public class Customer
{
    public void MakeCustomerPreferred()
    {
        var e = new MakeCustomerPreferredEvent()
        {
            Customer = this
        };

        DomainEvents.Current.Handle(e);
    }
}

public interface IDomainEvent { }

public interface IHandle<T> where T : IDomainEvent
{
    void Handle(T instance);
}

public class MakeCustomerPreferredEvent : IDomainEvent
{
    prop Customer Customer { get; set; }
}

The DomainEvents class is an ambient context that allows you to get all handlers for the specific domain event and execute them.

public class DomainEvents
{
    public static DomainEvents Current = new DomainEvents();

    public virtual void Handle<T>(T instance) 
        where T : IDomainEvent
    {
        var handlers =
           YourIocContainer.GetAllInstances<IHandle<T>>();

        foreach (var handler in handlers)
        {
            handler.Handle(instance);
        }
    }
}

With this in place you can define your handlers at a higher level in your architecture and plug in zero, one, or more handlers for each business event. You can inject the context in a handler.

Steven
  • 166,672
  • 24
  • 332
  • 435
  • This will work but it hurts my aesthetic OOD feeling. +1 for participation though – Andriy Volkov Feb 01 '11 at 18:16
  • Please don't be misleaded. This is Object Oriented Design all the way. This particular approach however, just makes the roles in your system much more explicit. In what way does it hurt your OOD feeling? – Steven Feb 01 '11 at 21:15
  • The Global Event Sinks smell of global variables. I don't like global stuff. – Andriy Volkov Feb 04 '11 at 22:09
  • Yes, `DomainEvents` must be an Ambient Context to prevent it from having to inject it into your entities. An Ambient Context must be prevented if possible, but in this case, injecting dependencies into your entities makes it worse. – Steven Feb 05 '11 at 09:14
0

We provide our clients with an option to follow the approach requested by the topic starter. In order to do this, we even implemented a similar solution (the ObjectMaterialized and other events of ObjectContext and ObjectStateManager) in our eXpressApp Framework (XAF) product. This works without any issues in most scenarios since entities have the same life time as the "context". This also helps us improve usability for our clients who face the same difficulties when designing their data models and business logic.

In our case, the domain model is not coupled with a specific persistence technology, because we have a special "ObjectSpace" abstraction on the ORM context (in addition to the Entity Framework our product supports our in-house ORM - eXpress Persistent Objects(XPO)).

So, we offer our clients an IObjectSpaceLink interface (with a single IObjectSpace property) that is supposed to be implemented by entities requiring context for their business logic.

Additionally, we provide an IXafEntityObject interface (with the OnCreated, OnLoaded, OnSaving methods) for the most popular business rules. Here is an example of an entity implementing both interfaces from our BCL:

        // IObjectSpaceLink
    IObjectSpace IObjectSpaceLink.ObjectSpace {
        get { return objectSpace; }
        set { objectSpace = value; }
    }

    // IXafEntityObject
    void IXafEntityObject.OnCreated() {
        KpiInstance kpiInstance = (KpiInstance)objectSpace.CreateObject(typeof(KpiInstance));
        kpiInstance.KpiDefinition = this;
        KpiInstances.Add(kpiInstance);
        Range = DevExpress.ExpressApp.Kpi.DateRangeRepository.FindRange("Now");
        RangeToCompare = DevExpress.ExpressApp.Kpi.DateRangeRepository.FindRange("Now");
    }
    void IXafEntityObject.OnSaving() {}
    void IXafEntityObject.OnLoaded() {}

In turn, here is the code of our framework that links these pieces together internally (below is for Entity Framework 6).

        private void ObjectContext_SavingChanges(Object sender, EventArgs e) {
        IList modifiedObjects = GetModifiedObjects();
        foreach(Object obj in modifiedObjects) {
            if(obj is IXafEntityObject) {
                ((IXafEntityObject)obj).OnSaving();
            }
        }
    }
    private void ObjectContext_ObjectMaterialized(Object sender, ObjectMaterializedEventArgs e) {
        if(e.Entity is IXafEntityObject) {
            ((IXafEntityObject)e.Entity).OnLoaded();
        }
    }
    private void ObjectStateManager_ObjectStateManagerChanged(Object sender, CollectionChangeEventArgs e) {
        if(e.Action == CollectionChangeAction.Add) {
            if(e.Element is INotifyPropertyChanged) {
                ((INotifyPropertyChanged)e.Element).PropertyChanged += new PropertyChangedEventHandler(Object_PropertyChanged);
            }
            if(e.Element is IObjectSpaceLink) {
                ((IObjectSpaceLink)e.Element).ObjectSpace = this;
            }
        }
        else if(e.Action == CollectionChangeAction.Remove) {
            if(e.Element is INotifyPropertyChanged) {
                ((INotifyPropertyChanged)e.Element).PropertyChanged -= new PropertyChangedEventHandler(Object_PropertyChanged);
            }
            if(e.Element is IObjectSpaceLink) {
                ((IObjectSpaceLink)e.Element).ObjectSpace = null;
            }
        }
        OnObjectStateManagerChanged(e);
    }
    public virtual Object CreateObject(Type type) {
        Guard.ArgumentNotNull(type, "type");
        CheckIsDisposed();
        Object obj = CreateObjectCore(type);
        if(obj is IXafEntityObject) {
            ((IXafEntityObject)obj).OnCreated();
        }
        SetModified(obj);
        return obj;
    }

I hope this information helps you.

Dennis Garavsky
  • 538
  • 3
  • 18