Mariusz.W
Mariusz.W

Reputation: 1377

Is this normal Entity Framework behaviour

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

Answers (1)

Slauma
Slauma

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 FriendshipInvitations 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 SiteMembers 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

Related Questions