3

Here is some code along with my assumptions based on playing around in LINQPad. Can anyone confirm this is how the lazy loading is working, and perhaps provide any additional insight/links so I can understand how it's working on the back end? Thanks in advance!

// Step 1.
var record = context.MyTable.First();

// Step 2.
var foreignKey = ForeignKeyTable.Where(x => x.Id == record.ForeignKeyId).Single();

// Step 3.
var entry = context.Entry(record);

// Step 4.
trace(entry.Reference(x => x.ForeignKey).IsLoaded);

// Step 5.
trace(record.ForeignKey.SomeProperty);
  1. Retrieve some record (DB is queried).
  2. Retrieve a record that happens to be a foreign key property of record without using lazy loading like record.ForeignKey to retrieve it (DB is queried).
  3. Get the details of the record entity.
  4. This is the part I'm unsure about. In my testing it outputs true. I'm guessing that IsLoaded doesn't know whether or not record.ForeignKey currently has a value, but knows that record.ForeignKey is already being tracked in the context based on it's knowledge of record.ForeignKeyId and the relationships that have been established.
  5. The db doesn't seem to be hit here, and I assume it's for the same reason IsLoaded returns true in 4. It knows that it's tracking the foreignKey object already, so it knows it doesn't have to do the lazy loading.

Edit: The actual problem I'm trying to solve can be illustrated as such:

var record = context.MyTable.First();

var foreignKey = new ForeignKey() { Id = record.ForeignKeyId, SomeProperty = 5 };
context.ForeignKeyTable.Attach(foreignKey);

var entry = context.Entry(record);

// Returns false.
trace(entry.Reference(x => x.ForeignKey).IsLoaded);

// Doesn't query for ForeignKey, so it must know it's `loaded` somehow, and
// gets SomeProperty from my new foreignKey object. What???
trace(record.ForeignKey.SomeProperty);
Ocelot20
  • 10,510
  • 11
  • 55
  • 96

2 Answers2

2

EF fixes relationships (navigation properties) automatically according to primary key and foreign key values when you load an entity from the database or when you attach it to the context.

In both code snippets you have loaded record which has a foreign key to your ForeignKeyTable. The context knows this value. (It doesn't matter btw if you have exposed the foreign key in your model. It will always be loaded, also without having a FK property in your model. You can see this when watching the SQL query.)

In both cases you attach afterwards a ForeignKey entity to the context which has as primary key the value of record.ForeignKeyId which the context already knows about. As a consequence EF will set the navigation property record.ForeignKey to this attached ForeignKey entity.

Obviously IsLoaded doesn't tell you if the entity is attached to the context because in both examples it is attached but one returns true and the other false. It also doesn't tell you if record.ForeignKeyId refers to an entity, because this is also the case in both examples.

It tells you apparently only that the entity has really been loaded from the database (and not only manually attached) (which also Intellisense says about IsLoaded). That's the only difference between your first and second example.

And it seems that lazy loading is not only controlled by the IsLoaded flag. If you attach an entity for the navigation property to the context, lazy loading doesn't happen anymore although IsLoaded is false.

What would happen if your last line in the second code snippet would actually trigger lazy loading? The ForeignKey object being loaded must have the same key as the ForeignKey object you have already attached (because record has this value as FK property ForeignKeyId). But because no two objects with same key can be attached to the context it must be the same object. But then there is no need to load it since such an object is already in memory and attached.

Slauma
  • 175,098
  • 59
  • 401
  • 420
  • So am I out of luck looking for a quick way to do something like this: `entry.Reference(x => x.ForeignKey).ExistsInContext? I kind of need that functionality, but I have no idea where to begin creating it. – Ocelot20 Dec 15 '11 at 21:42
  • 1
    @Ocelot20: You could try something like `ctx.ForeignKeys.Local.Any(f => f.Id == 1)`. `Local` provides a view on unchanged, modified and added entities in the context. Or you can go through `ctx.ChangeTracker.Entries()...`. I'm wondering why you need that, I don't see this related to your question. Perhaps create a new question more focussed on that problem and why you need such a function. – Slauma Dec 15 '11 at 21:58
1
// Step 1.
var record = context.MyTable.First();

// Step 2.
var foreignKey = ForeignKeyTable.Where(x => x.Id == record.ForeignKeyId).Single();

// Step 3.
var entry = context.Entry(record);

// Step 4.
trace(entry.Reference(x => x.ForeignKey).IsLoaded);

// Step 5.
trace(record.ForeignKey.SomeProperty);
  1. Retrieve some record (DB is queried). yes, and the resulting record is attached to the DbContext.
  2. Retrieve a record that happens to be a foreign key property of record without using lazy loading like record.ForeignKey to retrieve it (DB is queried). yes. If you had wanted to eager load the foreign key in #1, you would have used context.MyTable.Include(m => m.ForeignKey).First(); That would have retrieved the record along with the fk in 1 query.
  3. Get the details of the record entity. Kind of... it is the details of the entity in relation to the DbContext (what is attached / deleted / loaded / etc)
  4. This is the part I'm unsure about. In my testing it outputs true. I'm guessing that IsLoaded doesn't know whether or not record.ForeignKey currently has a value, but knows that record.ForeignKey is already being tracked in the context based on it's knowledge of record.ForeignKeyId and the relationships that have been established. This means that the DbContext does not need to run another query to load the data for the foreign key. If you execute record.ForeignKey, the data is already there, and no additional trip to the db is required.
  5. The db doesn't seem to be hit here, and I assume it's for the same reason IsLoaded returns true in 4. It knows that it's tracking the foreignKey object already, so it knows it doesn't have to do the lazy loading. The entitiy has already been loaded in step #2, so there was no additional trip needed to get it from the db.

Update after question edit

According to EF, the .Attach method on IDbSet:

Attaches the given entity to the context underlying the set. That is, the entity is placed into the context in the Unchanged state, just as if it had been read from the database.

danludwig
  • 46,965
  • 25
  • 159
  • 237
  • I know that if I use Include it will load record along with the foreign key. I'm more wondering *how* it knows that it's already loaded in Step 4. I ran a similar test in my application, and Step 4 returned false. The equivalent to the `foreignKey` was loaded in a way that wouldn't create the object proxy, so I'm wondering if it had something to do with that. – Ocelot20 Dec 15 '11 at 20:24
  • It knows that it is loaded because it is attached to the object context. Try some examples that invoke AsNoTracking() before the Where clause in #2. You will find then that Entry(foreignKey).State is Detached, and step #4 should trace as false. – danludwig Dec 15 '11 at 20:29
  • In my application the related entities are attached, although they weren't materialized by the context to begin with. I guess it's the equivalent of: `ctx.Attach(new foreignKey() { Id = 4, SomeProperty = 6 });` in which case the object is an actual foreignKey object and not a foreignKey proxy object. – Ocelot20 Dec 15 '11 at 20:34
  • Please see the edit to my original post for a more detailed explanation. – Ocelot20 Dec 15 '11 at 20:44