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.