Какова хорошая стратегия для загрузки объекта, на который ссылается Poco, при отключении от контекста в EF4?

я запустил приложение веб-проекта ASP.net, чтобы узнать, как можно использовать EF4. В моем проекте я определил 3 уровня (DAL => Entity Framework 4, BLL => Уровень бизнес-логики, пользовательский интерфейс). BLL и DAL совместно используют POCO, созданные с использованием функции шаблона EF4.

Например, у меня есть класс Poco "Пользователь", который выглядит так:

 public partial class User
{
    #region Primitive Properties

    public virtual System.Guid Id
    {
        get;
        set;
    }

    public virtual string Name
    {
        get;
        set;
    }

    public virtual string Password
    {
        get;
        set;
    }

    public virtual string Email
    {
        get;
        set;
    }

    public virtual bool MarkedForDeletion
    {
        get;
        set;
    }

    #endregion
    #region Navigation Properties

    public virtual Role Role
    {
        get { return _role; }
        set
        {
            if (!ReferenceEquals(_role, value))
            {
                var previousValue = _role;
                _role = value;
                FixupRole(previousValue);
            }
        }
    }
    private Role _role;

    public virtual ICollection<Article> Articles
    {
        get
        {
            if (_articles == null)
            {
                var newCollection = new FixupCollection<Article>();
                newCollection.CollectionChanged += FixupArticles;
                _articles = newCollection;
            }
            return _articles;
        }
        set
        {
            if (!ReferenceEquals(_articles, value))
            {
                var previousValue = _articles as FixupCollection<Article>;
                if (previousValue != null)
                {
                    previousValue.CollectionChanged -= FixupArticles;
                }
                _articles = value;
                var newValue = value as FixupCollection<Article>;
                if (newValue != null)
                {
                    newValue.CollectionChanged += FixupArticles;
                }
            }
        }
    }
    private ICollection<Article> _articles;

    public virtual Status Status
    {
        get { return _status; }
        set
        {
            if (!ReferenceEquals(_status, value))
            {
                var previousValue = _status;
                _status = value;
                FixupStatus(previousValue);
            }
        }
    }
    private Status _status;

    #endregion
    #region Association Fixup

    private void FixupRole(Role previousValue)
    {
        if (previousValue != null && previousValue.Users.Contains(this))
        {
            previousValue.Users.Remove(this);
        }

        if (Role != null)
        {
            if (!Role.Users.Contains(this))
            {
                Role.Users.Add(this);
            }
        }
    }

    private void FixupStatus(Status previousValue)
    {
        if (previousValue != null && previousValue.Users.Contains(this))
        {
            previousValue.Users.Remove(this);
        }

        if (Status != null)
        {
            if (!Status.Users.Contains(this))
            {
                Status.Users.Add(this);
            }
        }
    }

    private void FixupArticles(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.NewItems != null)
        {
            foreach (Article item in e.NewItems)
            {
                item.Author = this;
            }
        }

        if (e.OldItems != null)
        {
            foreach (Article item in e.OldItems)
            {
                if (ReferenceEquals(item.Author, this))
                {
                    item.Author = null;
                }
            }
        }
    }

    #endregion
}

Поскольку все свойства отмечены виртуальным EF4, необходимо создать прокси-класс для доступа ко всем данным POCO. Также я включил ленивую загрузку для контекста БД.

Когда я хочу отобразить информацию о пользователях, у меня есть следующий сценарий:

  • UI=> создает экземпляр класса из BLL (UsersManager) и вызывает метод GetUserByEmail (строка электронной почты), который возвращает пользователя.

  • BLL=> в методе GetUserByEmail(строка электронной почты) типа UsersManager я создаю экземпляр класса из DAL(UsersDataManager) и вызываю метод GetUserByEmailFromDAL(строка электронной почты), который возвращает пользователя.

  • DAL => в GetUserByEmailFromDAL (строка электронной почты) я создаю экземпляр контекста, запрашиваю пользователя и возвращаю его.

проблема в том, что, поскольку только DAL знает о контексте, и он удаляется после выхода из функции, отношения навигации POCO обнуляются, и у меня нет доступа ни к одному из их свойств.

если я делаю myUser.Role.Name, я получаю следующее сообщение об ошибке:

Role = 'user1.Role' threw an exception of type 'System.ObjectDisposedException'
The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.

чтобы обойти проблему, я решил изменить свои методы, чтобы сообщить им, что они должны загружать свойства навигации, когда они мне нужны. После этой модификации мой метод в DAL выглядит так:

 public User User(string email, bool loadRelationships)
        {
            User user = null;
            if (!loadRelationships)
            {
                user = (from p in dbContext.Users where p.Email.Equals(email) select p).FirstOrDefault<User>();
            }
            else {
                user = (from p in dbContext.Users.Include("Role").Include("Status") select p).FirstOrDefault<User>();
            }

            return user;
        }

с этой модификацией проблема все еще существует ... у меня нет доступа к объектам навигации связанных объектов ... так, например, если у типа роли была коллекция разрешений, если я хотел что-то вроде этого:

User user1 = UserManager("[email protected]");
foreach(Permission perm in user1.Role.Permissions)
Console.WriteLine(perm.Name); => here i'd get an error like the one mentioned earlier.

Ленивая загрузка работает, когда Poco подключен к контексту базы данных. Есть ли механизм или стратегия, которые можно использовать для загрузки свойств навигации, как в моем сценарии?

Спасибо.


person Sorin Antohi    schedule 13.09.2010    source источник


Ответы (1)


Ваша традиционная трехуровневая архитектура «Презентация/Бизнес/Данные» не поддается отложенной загрузке. Пользовательский интерфейс никогда не должен напрямую вызывать выполнение запроса.

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

Приведенный выше метод загрузки связанных данных по запросу по параметру является лучшим подходом.

person Dave Swersky    schedule 13.09.2010
comment
поэтому, чтобы загрузить навигационные свойства роли (как в моем примере), я должен запросить саму роль. что-то вроде Role myUsersRole = RoleManager.GetRoleWithId(user1.Role.id); и в DAL вызовите метод, аналогичный методу для UsersDataManager(..). Также не могли бы вы привести несколько примеров архитектур, которые можно использовать с отложенной загрузкой (чтобы их погуглить)? спасибо Дэйв! - person Sorin Antohi; 13.09.2010
comment
@Sorin: Да, я думаю, ты на правильном пути. Используемая вами трехуровневая архитектура возлагает на DAL и BLL ответственность за предоставление точно того, что нужно пользовательскому интерфейсу, и ничего более. Я использую ленивую загрузку с шаблоном репозитория в своих проектах ASP.NET MVC. Это более плоская архитектура, которая лучше подходит для отложенной загрузки. Вы по-прежнему должны быть осторожны, чтобы не злоупотреблять отложенной загрузкой, иначе вы можете столкнуться с проблемами производительности. - person Dave Swersky; 13.09.2010