Сущность с поддержкой ObjectContext в EF, чтобы избежать анемичной модели предметной области

В Entity Framework можно ли заставить фреймворк вводить DbContext в каждый объект (сущность), который присоединен к контексту или извлечен из него?

Я парень из NHibernate, и я знаю, что это возможно в NH, — извините, если это глупый вопрос в мире EF.

По сути, я хочу, чтобы некоторые из моих сущностей имели свойство типа DbContext, которое будет устанавливаться на экземпляр контекста самой структурой всякий раз, когда я связываю сущность с контекстом. В идеале такие классы должны быть помечены маркерным интерфейсом IContextAware или чем-то подобным.

Причина, по которой я хочу это сделать, заключается в том (= цель), что на этот раз я хочу избежать анти-паттерна Anemic Domain Model. Я подумал, что если я внедрил ObjectContext в сущности, они смогут получить доступ к БД, что позволит мне реализовывать запросы и более сложную логику прямо внутри самих доменных классов. Если вы знаете другие способы достижения моей цели (особенно в контексте веб-приложения), пожалуйста, но старайтесь избегать ответов типа «вы не должны этого делать, потому что». Спасибо!!!


comment
Почему бы не поместить текущий DbContext в локальный поток?   -  person Jeffrey Hantin    schedule 29.01.2011
comment
Честно говоря, я подозреваю, что вы неправильно понимаете значение Anemic Domain. Как вы думаете, почему наличие ссылки на объект, связанный с сохраняемостью, решит проблему?   -  person anon    schedule 29.01.2011
comment
@Джеффри, я думал об этом, это, безусловно, мой второй вариант после встроенной инъекции.   -  person zvolkov    schedule 31.01.2011
comment
@anon Потому что тогда я смогу реализовать бизнес-логику (поведение, связанное с доменом) внутри самого объекта, и во многих случаях это требует запроса или записи в базу данных. Без доступа к постоянству большая часть логики предметной области должна находиться вне сущностей, что превращает их почти в DTO. Каково ваше понимание модели анемичной предметной области?   -  person zvolkov    schedule 31.01.2011
comment
NHibernate обходит всю проблему, делая такие вещи, как подключение реализации коллекций, поддерживаемых базой данных, для ассоциаций или сканирование изменений извлеченных объектов при закрытии сеанса, поэтому объектный код домена, не обращающий внимания на базу данных, может по-прежнему проходить ассоциации, создавать/обновлять/удалять и т. д.   -  person Jeffrey Hantin    schedule 31.01.2011
comment
@Джеффри, разве EF не делает то же самое???   -  person zvolkov    schedule 01.02.2011
comment
@zvolkov Дело в том, что вам не нужна ссылка на сеанс NHibernate, чтобы делать эти вещи. Я просто пытаюсь выяснить, каков вариант использования DbContext в ваших моделях предметной области.   -  person Jeffrey Hantin    schedule 01.02.2011
comment
@zvolkov Я думаю, я веду к тому, что вы должны иметь возможность использовать LINQ поверх коллекций ассоциаций, и если реализация коллекции будет поддерживаться базой данных,   -  person Jeffrey Hantin    schedule 01.02.2011
comment
... использование .AsQueryable() в коллекции должно подключать ваши запросы обратно в ORM для выполнения в БД. Таким образом, вы можете сохранять объекты домена независимыми от сохранения.   -  person Jeffrey Hantin    schedule 01.02.2011


Ответы (3)


Вы не должны этого делать, потому что хотите, чтобы проблемы сохранения не касались объектов вашего домена =)

Но если вы ДОЛЖНЫ, вы можете подключиться к событию ObjectMaterialized, запущенному ObjectContext. В CTP5 вам нужно привести свой DbContext так в конструкторе для вашего DbContext:

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

Затем реализуйте свою функцию ObjectContext_OnObjectMaterialized (отправитель объекта, ObjectMaterializedEventArgs e). Через EventArgs вы сможете получить доступ к вашему объекту, который только что материализовался. Оттуда вы можете установить свойство ObjectContext/DbContext вашего POCO, которое должно быть общедоступным или внутренним.

person anon    schedule 29.01.2011
comment
вы хотите сохранить - я не хочу! Если все говорят, что что-то правильно, это не значит, что так оно и есть на самом деле. Особенно учитывая, как много людей склонны повторять друг друга, не понимая почему :) Спасибо, я попробую реализовать так, как вы описали, и посмотрю, не приведет ли моя идея к проблемам. - person zvolkov; 31.01.2011
comment
Я немного новичок в Entity Framework и Domain Driven Design, и я могу вспомнить некоторые случаи, когда я хотел бы, чтобы мои объекты «знали» больше о том, как запрашивать себя и другие объекты. Я хотел бы услышать больше о конкретном сценарии, с которым вы столкнулись. - person anon; 01.02.2011

Помимо привязки вашего домена к определенной технологии сохранения, существуют и другие проблемы с внедрением контекста на этом уровне. Например, каково время жизни контекста, который вы вводите, и должен ли этот контекст всегда иметь одинаковое время жизни для каждой сущности?

Насколько я понимаю, вы хотите определить свои бизнес-методы для сущностей, поэтому вы можете сказать customer.MakeCustomerPreferred. Однако есть и другие способы сделать это без необходимости писать бизнес-логику на этом уровне в приложении. Например, вы можете использовать деловые мероприятия. Вот пример:

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

Класс DomainEvents — это окружающий контекст, который позволяет вам получить все обработчики для определенного события предметной области и выполнить их.

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

Благодаря этому вы можете определить свои обработчики на более высоком уровне в вашей архитектуре и подключить ноль, один или несколько обработчиков для каждого бизнес-события. Вы можете внедрить контекст в обработчик.

person Steven    schedule 01.02.2011
comment
Это сработает, но это повредит моему эстетическому чувству OOD. +1 за участие однако - person zvolkov; 01.02.2011
comment
Пожалуйста, не вводите в заблуждение. Это полностью объектно-ориентированный дизайн. Однако этот конкретный подход просто делает роли в вашей системе более явными. Каким образом это вредит вашему чувству OOD? - person Steven; 02.02.2011
comment
Глобальные приемники событий пахнут глобальными переменными. Я не люблю глобальные вещи. - person zvolkov; 05.02.2011
comment
Да, DomainEvents должен быть контекстом окружения, чтобы предотвратить его внедрение в ваши сущности. Ambient Context должен быть предотвращен, если это возможно, но в этом случае внедрение зависимостей в ваши сущности усугубляет ситуацию. - person Steven; 05.02.2011

Мы предоставляем нашим клиентам возможность следовать подходу, запрошенному автором темы. Для этого мы даже реализовали аналогичное решение (ObjectMaterialized и другие события ObjectContext и ObjectStateManager) в нашем продукте eXpressApp Framework (XAF). Это работает без каких-либо проблем в большинстве сценариев, поскольку сущности имеют то же время жизни, что и «контекст». Это также помогает нам повысить удобство использования для наших клиентов, которые сталкиваются с такими же трудностями при разработке своих моделей данных и бизнес-логики.

В нашем случае модель предметной области не связана с конкретной технологией персистентности, потому что у нас есть специальная абстракция «ObjectSpace» в контексте ORM (в дополнение к Entity Framework наш продукт поддерживает нашу внутреннюю ORM — eXpress Persistent Objects (XPO). )).

Итак, мы предлагаем нашим клиентам интерфейс IObjectSpaceLink (с одним свойством IObjectSpace), который должен быть реализован сущностями, требующими контекста для своей бизнес-логики.

Дополнительно мы предоставляем интерфейс IXafEntityObject (с методами OnCreated, OnLoaded, OnSaving) для наиболее популярных бизнес-правил. Вот пример сущности, реализующей оба интерфейса из нашего 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() {}

В свою очередь, вот код нашей платформы, которая внутренне связывает эти части (ниже для 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;
    }

Я надеюсь, что эта информация поможет вам.

person Dennis Garavsky    schedule 27.01.2015