views:

48

answers:

1

i started an ASP.net web project app to learn how EF4 can be used. In my project i have defined 3 layers(DAL => Entity Framework 4, BLL => Business Logic Layer, UI). The BLL and DAL share POCOs generated using the template feature of EF4.

For example i have "User" Poco class that looks like this:

 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
}

Because all the properties are marked with virtual EF4 should create a proxy class in order to access all data of the POCO. Also i have enabled lazy loading for the db context.

When i want to display a users details i have the fallowing scenario:

  • UI=> instantiates a class from BLL (UsersManager) and calls the method GetUserByEmail(string email) that returns a User.

  • BLL=> in the method GetUserByEmail(string email) of the type UsersManager i instantiate a class from DAL(UsersDataManager) and call a method GetUserByEmailFromDAL(string email) that returns a User.

  • DAL=> in the GetUserByEmailFromDAL(string email) i instantiate a context, query for the user and return it.

the problem is that because only the DAL knows about the context and it is disposed after it exits the function the POCOs navigation relationships are Nulled and i don't have acces to any of their properties.

if i do myUser.Role.Name i get an error like this:

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.

in order to bypass the problem i decided to modify my methods in order to tell it them to eager load the navigation properties when i need them. After this modification my method in DAL looks like this:

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

with this modification a problem still exists..i don't have access to the related entities navigation entities... so for instance if a Role type had a Collection of permissions if i wanted to something like this:

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.

Lazy loading works when the Poco is attached to a db context. Is there a mechanism or strategy that can be used to load navigation properties like in my scenario?

thank you.

A: 

Your traditional Presentation/Business/Data three-tier architecture doesn't lend itself to lazy-loading. The UI should never directly cause a query to execute.

If you want your UI layer to be able to lazy-load related entities, you'd have to surface the context from the DAL through your business layer. This might not be a good idea, because if you allow this you can inadvertently end up with queries in the UI that load a lot more data that you intend. You also break your separation of concerns.

Your method above of loading related data on demand by parameter is a better approach.

Dave Swersky
so in order to load the navigation properties of a Role(like in my example) i should query for the role itself.. something like Role myUsersRole = RoleManager.GetRoleWithId(user1.Role.id); and in the DAL call a method similar with the one for the UsersDataManager(..) Also could you give some examples of arhitectures that can be used with lazy loading(in order to google them)? thank you Dave!
SorinA.
@Sorin: Yes, I think you're on the right track. The three-tier architecture you're using puts the responsibility on the DAL and BLL to expose *exactly* what the UI needs and nothing more. I use lazy loading with a Repository pattern in my ASP.NET MVC projects. This is a "flatter" architecture and better suited to lazy loading. You still have to be careful not to overuse lazy loading, or you can run into performance issues.
Dave Swersky