0

in my controller action I'm trying to add new image to database. Image requires to be associated with the device, which is associated with the user.

So I have my entity classess:

public class Image
{
   public int Id { get; set; }
   public string Url { get; set; }

   [Required]
   public virtual Device { get; set; }
}

public class Device
{
   public int Id { get; set; }
   public string Name { get; set; }

   [Required]
   public virtual User { get; set; }
}

public class User
{
   public int Id { get; set; }
   public string Login{ get; set; }
   public virtual ICollection<Device> Devices { get; set; }

   [Required]
   [DataType(DataType.EmailAddress)]
   public virtual string Email{ get; set; }
}

Now when I'm trying to add new image like this:

var image = new Image(); 
image.Device = Db.DbSet<Device>().Find(1); 
Db.DbSet<Image>().Add(image); 
Db.DbSet<Image>().SaveChanges();

The thing is that when I use this Find to lad existing device (with user and all properties set right) I don't get the correctly filled User property in the Device. It's like the lazy loading did not work. There is an instance of an User object, but it's id is set to 0 and it have all other fields set to its default values.

I have all navigation properties set as virtual, also lazy loading works okay on my other entities so it's definitely not turned off or something.

The funny thing is that when I change my code and comparison like below before I add my image then it loads user and works fine:

if (AuthenticationHelper.CurrentUser != image.Device.User)
    return null;

Exactly like it manage to load user in this very moment. If we change this comparison order, to following image.Device.User != AuthenticationHelper.CurrentUser it do not work any more.

Any ideas what's going on with this?

2
  • 1
    Does your Device class have a default constructor that instantiates the User property? If yes, try if it works correctly without the instantiation. Commented Jul 15, 2013 at 17:27
  • @Slauma - it turned out that you are right! Please add an answer so I can accept it. Commented Jul 16, 2013 at 12:42

3 Answers 3

2

I would argue that lazy loading is working correctly. You're finding the Device with an ID of 1, then assigning it to the image without accessing the User property on the Device. Without an explicit foreign key UserID property on your Device model, the User property is never accessed thus never having a value, and inserting null.

Try adding UserID (or User_ID if you want to keep with Entity Framework's convention) to your Device model and your code should work.

Sign up to request clarification or add additional context in comments.

4 Comments

Okay, it might be a correct behaviour that it does not load User, but it should also avoid the validation of this property if it know that this is a db proxy and it's taken from db. An even if it tries to validate - why doesn't it load it in this moment, when the validatior access User?
Also I just tried to invoke image.Device.User.Email.ToString() before the Add method and it still does not loads it - it just throws Null Ref Exception, because Email is still null...
@ŁukaszW.pl I'll ask the dumb question, is there a Device with an ID of 1 in the database?
Yes it was. Anyway it turned out that Slauma was right. It was caused by instantining navigation property in the entity default constructor.
1

The problem could be caused by instantiating a reference navigation property (Device.User) in the entity default constructor. It results in side effects and unexpected behaviour when loading and saving entities. Other examples are here and here.

Comments

-1

Entity Framework is not quite the same as LINQ to SQL or NHibernate when it comes to "Lazy Loading". In EF, the design decision is to ensure that the developer know exactly when they are accessing resources, such as the database. Therefore, Lazy Loading doesn't enable "automatic" database re-querying; rather, it provides "deferred" loading of a resource. EF will never initiate a secondary database call at a later point without the developer being aware of it; instead, the developer should make an explicit call to the .Load() method to initiate the secondary call, or eagerly load the deferred resource via .Include().

1 Comment

Not true - Load and Include are used when context.ContextOptions.LazyLoadingEnabled is set to false. Otherwise it should load navigation properties when accessed.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.