Reputation: 1377
I have two one to many relations between the following classes
public class SiteMember
{
public int SiteMemberID { get; set; }
public virtual List<FriendshipInvitation> InvitationsSent { get; set; }
public virtual List<FriendshipInvitation> InvitationsReceived { get; set; }
}
public class FriendshipInvitation
{
public int InvitingUserID { get; set; }
public SiteMember InvitingUser { get; set; }
public int InvitedUserID { get; set; }
public SiteMember InvitedUser{ get; set; }
}
Then in controller I have the following code (which loads one SiteMember from db):
SiteMember sm = repoSiteMember.GetUserByName(User.Identity.Name);
at this point
sm.InvitationsReceived.InvitingUserId = 1;
sm.InvitationsReceived.InvitingUser = null
Then I have the following line (which loads all SiteMembers from db):
IEnumerable<SiteMember> all = repoSiteMember.GetAll();
after which
sm.InvitationsReceived.InvitingUserId = 1
sm.InvitationsReceived.InvitingUser = proxy to SiteMember object.
My question is: Is this normal behaviour? I'm quite sure I can "fix" null pointer with include statement, but I find it weird that loading all elements from db actually changes the state of object. (null => proxy). Should the proxy be there from the beginning?
Edit: Just to confirm, include statement does solve the problem of null reference, but that was not the question here.
DataContext.Members
.Include("InvitationsSent.InvitedUser")
.Include("InvitationsReceived.InvitingUser")
.FirstOrDefault(e => e.UserName == userName);
Edit2: Methods included now.
public SiteMember GetUserByName(string userName)
{
return base.DataContext.Members.FirstOrDefault(e => e.UserName == userName);
}
public IEnumerable<SiteMember> GetAll()
{
return base.DataContext.Members.ToList();
}
Edit3: You may need that to recreate the model.
modelBuilder.Entity<FriendshipInvitation>()
.HasRequired(e => e.InvitedUser)
.WithMany(e => e.InvitationsReceived)
.HasForeignKey(e => e.InvitedUserID)
.WillCascadeOnDelete(false);
modelBuilder.Entity<FriendshipInvitation>()
.HasRequired(e => e.InvitingUser)
.WithMany(e => e.InvitationsSent)
.HasForeignKey(e => e.InvitingUserID);
modelBuilder.Entity<FriendshipInvitation>()
.HasKey(e => new { e.InvitingUserID, e.InvitedUserID, e.InvitationNo });
Upvotes: 2
Views: 122
Reputation: 177163
Yes, the different behaviour is normal and it occurs because of Relationship Fixup.
When you load only one SiteMember
with FirstOrDefault
the two collections InvitationsSent
and InvitationsReceived
will be loaded as well as soon as you access them (or inspect them in the debugger) because they are marked as virtual
and lazy loading will apply. But the FriendshipInvitation
s in those collections won't lazily load the InvitingUser
or the InvitedUser
because these navigation properties are not marked as virtual
.
Say, SiteMember
1 that you load with FirstOrDefault
has an invitation received from SiteMember
2 and no invitation sent. So, InvitationsReceived
will contain one element and InvitationsSent
will be empty. That element - a FriendshipInvitation
entity - will have InvitingUser
as 2 and InvitedUser
as 1. The InvitingUser
will be null
in that case because it can't be lazily loaded (since it isn't virtual
) and the SiteMember
2 isn't loaded yet into the context. (InvitedUser
however will be a dynamic proxy to SiteMember
1 because you already have loaded the SiteMember
1, see next paragraph.)
However, if you load all SiteMember
s with ToList()
the SiteMember
2 will be loaded as well into the context with the query executed by ToList()
. EF will set the InvitingUser
2 automatically to the already loaded entity which is called Relationship Fixup. It is not loaded by lazy loading of the InvitingUser
navigation property because lazy loading isn't supported for a not virtual
property.
To have a consistent behaviour and not rely on Relationship Fixup alone I would suggest that you mark the InvitingUser
and InvitedUser
navigation properties as virtual
as well.
Upvotes: 1