1

Say I have the following models:

public class Subject { private List<SubjectResponse> responses; public int Id { get; private set; } public IEnumerable<SubjectResponse> Responses => responses.ToList(); public void Foo() { // How do I check here if Responses fully has been loaded? foreach (var response in Responses) { // ... } } } public class SubjectResponse { public int Id { get; private set; } } 

How do I check if all responses have been loaded in Foo()? I'd probably check for if (Responses is null), but that won't work in all cases.

Here is a minimum example of what could do wrong. In a real app the responses could be loaded at a completely different place. But h This shows how the responses could be fixed up by EF, so it could contain entries, but not all entries.

public async Task Bar() { var response = await dbContext.SubjectResponses.SingleAsync(s => s.Id == 1); var subject = await dbContext.Subjects.SingleAsync(s => s.Id == 1); subject.Foo(); // subject.Responses now has a count if 1, when there might actually be more responses. } 

I don't want to use lazy loading, because of performance implications (and because Lazy Loading won't load related entities async). Eager and Explicit loading are fine.

Edit: what I’m mainly looking for is a way to check if the navigation property has been loaded fully, so that I can load it it has not been.

10
  • 1
    Why is there a method in your entity? What are Responses, responses and the relation between dbContext.SubjectResponses and the former two? How is either populated from the latter? I guess that a navigation property is configured and that because a SubjectResponses with Id 1 belongs to a Subjects with Id 1, and EF does "fix up" the relation. Why do you load it like that? Does .Load() do what you want? Commented Aug 3, 2020 at 13:21
  • The method exists on the entity to implement it’s logic Domain Driven. The method encapsulates logic that mutates the entities, so the entity cannot be in an invalid state. I’ll update my question with the DbContext code later, but there’s basically no custom configuration. So yeah they’re navigation properties. The ids of both entities are completely unrelated, so the Id of a subject doesn’t necessarily have to be the same as the id of a response. Thanks! Commented Aug 3, 2020 at 15:58
  • I’ve updated the question to hopefully make it a bit more clear Commented Aug 3, 2020 at 16:07
  • 1
    I’m not relying on the fix up side effect, it is what is breaking my logic. Otherwise I could simply check if the field was null and throw with a message that Include should have been used beforehand. But I get what you’re saying. I’ll see if I can make my example more like the real world example and make my question clearer. Thanks for providing an answer for something that was a bit too unclear! Commented Aug 3, 2020 at 16:59
  • 1
    Sorry, that's what I missed from "In a real app the responses could be loaded at a completely different place". So some other part of the code may or may not load some or all of the SubjectResponses belonging to the Subject you're looking for. I think there's no other option than to explicitly load them where you need that Subject, because the database may contain more records than loaded, and there's no way to know that without querying anyway. Commented Aug 3, 2020 at 17:01

3 Answers 3

1

You cannot detect whether all related entities happen to have passed by Entity Framework.

What you show works because the entity from dbContext.SubjectResponses.SingleAsync(s => s.Id == 1) has a SubjectId of 1, and will be cached, and successively be attached to the result of dbContext.Subjects.SingleAsync(s => s.Id == 1).

There is no way for EF, nor for your code, to know that all SubjectResponses with a SubjectId of 1 have been loaded from the database, so you'll have to explicitly load them:

var subject = await dbContext.Subjects.SingleAsync(s => s.Id == 1); await dbContext.Entity(subject) .Reference(s => s.responses) .LoadAsync(); 

But you can't do that, as Subject.responses is private, so you'll have to do that from within your entity's Foo() method, and you'll have to inject your DbContext into your entity, and that'll just become a giant mess.

Why not just do it pragmatically, make Responses a public auto-property and Include() the related entities on beforehand:

var subject = await dbContext.Subjects.Include(s => s.Responses).SingleAsync(s => s.Id == 1); 
Sign up to request clarification or add additional context in comments.

2 Comments

There is no way for EF... Why not? It has to translate LINQ to some other query language, so why is there no way to detect that a DbSet.Include() exists and is returned? Definitionally, that's a full join at time of query. If you're working in a transactional context, this is the same thing as knowing that the navigation property is fully populated.
@CaptainPrinny the question here, which I answer, is whether Entity Framework can know it has loaded all related entities before calling Include().
1

There is the possibility to throw an exception if a related entity (or a collection of entities) has not been loaded (included), see Non-nullable properties and initialization.

Here is an example of a one-to-many relationship:

 class MyEntity { // Assume read-only access. public IReadOnlyList<MyRelatedEntity> MyRelatedEntities => _myRelatedEntities?.ToList().AsReadOnly() ?? throw new InvalidOperationException("MyRelatedEntities not loaded."); private readonly IEnumerable<MyRelatedEntity>? _myRelatedEntities = null; } class MyRelatedEntity { public MyEntity MyEntity { get => _myEntity ?? throw new InvalidOperationException("MyEntity not loaded."); set => _myEntity = value; } private MyEntity? _myEntity = null; } 

Entity Framework Core will automatically set the backing field and you can detect if the related entity was loaded.

Unfortunately this approach does not work for optional relationships, or at least I haven't figured out (yet) how to do it.

1 Comment

Really good suggestion for a required relationship. Sadly it also doesn’t work with a one-many relationship, as one of the related entries can have been loaded and automatically fixed up by EF (see question for example).
0

If you use the LazyLoader provided in EF Core it will load the navigation properties if they haven't already been loaded. The key thing to remember is if you do this inside a loop it could hit the database many times, one of the core reasons why EF Core went away from this strategy by default.

public class Subject { private ILazyLoader _lazyLoader { get; set; } public Subject(ILazyLoader lazyLoader) { _lazyLoader = lazyLoader; } public int Id { get; private set; } private IEnumerable<SubjectResponse> _responses; public IEnumerable<SubjectResponse> Responses { get => _lazyLoader.Load(this, ref _responses); private set => _responses; } public void Foo() { // Accessing the property will trigger a query to get responses if not loaded already foreach (var response in Responses) { // ... } } } 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.