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 e开发者_开发问答ntities 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!!!
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.
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.
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.
精彩评论